Skip to content
/ Michaël Hompus

As I continue migrating from WordPress to Astro, I am rebuilding key plugin features without third-party dependencies. In this post, I will show how I replaced syntax highlighting, recent post widgets, and external link management using Astro’s flexible ecosystem.

This post is a continuation of my migration journey from WordPress to Astro. If you have not read the previous posts, you might want to start with Why I Switched from WordPress to Astro, How I Moved My Blog Content and how I am Replacing WordPress Plugins with Astro: Acronyms, Meta Tags & Tag Clouds.

In this post, I cover how I replaced the next set of WordPress plugins with equivalent functionality in Astro:

  1. Enlighter
  2. Recent Post Widget Extended
  3. WP External Links

Enlighter

The Enlighter plugin provided syntax highlighting for code blocks in WordPress, allowing custom styles and line numbers.

Astro Implementation

I replaced Enlighter with Expressive Code. It is a powerful and flexible replacement for syntax highlighting in Astro.

Here’s how I set it up:

astro.config.mjs
import { defineConfig } from "astro/config";
import expressiveCode from "astro-expressive-code";
export default defineConfig({
integrations: [
expressiveCode({
plugins: [pluginLineNumbers()],
defaultProps: {
wrap: true,
showLineNumbers: false,
},
styleOverrides: {
codeFontFamily: "var(--font-monospace)",
codeFontSize: "0.78125rem",
codeLineHeight: "1.6",
uiFontSize: "0.78125rem",
lineNumbers: {
highlightForeground: "#85c7ebb3",
},
},
}),
],
});

I configured it to wrap long lines by default and disabled line numbers unless explicitly enabled. Additionally, I set the font family, size, and other styles to match the styling of my blog.

Using My Visual Studio Code Theme

I prefer the syntax highlighting theme in Visual Studio Code over the default GitHub styling.

Expressive Code allows you to define custom themes based on VS Code exports. So, I exported my theme and applied it in Expressive Code!

src/config/vscode-theme.jsonc
{
"$schema": "vscode://schemas/color-theme",
"type": "dark",
"colors": {
"actionBar.toggledBackground": "#383a49",
"activityBar.activeBorder": "#0078d4",
"activityBar.background": "#181818",
...
},
"tokenColors": []
}

Full VS Code theme available here: vscode-theme.jsonc

Because the theme file is a jsonc file. I had to load it in a specific manner in the astro.config.mjs file:

astro.config.mjs
import { defineConfig } from "astro/config";
import { readFileSync } from "fs";
import expressiveCode, { ExpressiveCodeTheme } from "astro-expressive-code";
const jsoncString = readFileSync(new URL(`./src/config/vscode-theme.jsonc`, import.meta.url), 'utf-8')
const vscodeTheme = ExpressiveCodeTheme.fromJSONString(jsoncString)
export default defineConfig({
integrations: [
expressiveCode({
...
themes: [vscodeTheme]
})
],
})

As you can already see in all the code samples in my posts, the syntax highlighting is working beautifully.

Full implementation on GitHub: astro.config.mjs.

Recent Post Widget Extended

The Recent Post Widget Extended plugin displayed recent posts dynamically.

Astro Implementation

In Astro, I created a simple loop using Astro’s Content Collection API.

src/components/Sidebar.astro
---
import { getCollection } from 'astro:content';
const unsortedPosts = await getCollection('posts')
const posts: { data: { permalink: string; title: string } }[] = sortedPosts(unsortedPosts);

Next, I render the recent posts in the sidebar while filtering out the current post (if applicable):

src/components/Sidebar.astro
<aside id="recent-posts">
<h2>Recent Posts</h2>
<div>
<nav>
<ol>
{
posts
.slice(0, 6)
.filter((p) => p.data.permalink !== Astro.url.pathname)
.slice(0, 5)
.map((post) => (
<li>
<a href={post.data.permalink} target="_self">
{post.data.title}
</a>
</li>
))
}
</ol>
</nav>
</div>
</aside>

Full implementation on GitHub: Sidebar.astro.

The External Links plugin manages external links, adding icons and SEO attributes.

Astro Implementation

I replaced this with rehype-external-links, configuring it in astro.config.mjs:

astro.config.mjs
import rehypeExternalLinks from "rehype-external-links";
export default defineConfig({
markdown: {
rehypePlugins: [
[
rehypeExternalLinks,
{
content: {},
rel: ["noopener", "noreferrer", "external"],
target: "_blank"
}
]
],
},
});

Now every link that contains an external URL will open in a new tab with the noopener, noreferrer, and external attributes.

Using CSS on a tags where the rel attribute contains external, I added an external link icon:

global.css
a[rel~="external"] span {
width: 10px;
height: 10px;
display: inline-block;
margin-left: 0.3em;
background-image: url(/icon-13.png);
}

You can see this implementation in action with various external links on this page.

If you want to exclude certain domains from being treated as external links, you can use a test function in the configuration. I used this to exclude LinkedIn links.

View the full code on GitHub: astro.config.mjs and global.css.


This was the next set of 3 plugins I replaced with Astro functionality.

In the next post, I will cover more plugins, including paging, related posts, and more.

Filed under Azure
Last update:
/ Michaël Hompus

Moving from WordPress to Astro meant rethinking how I implemented various features that were previously handled by plugins. In this post, I explain how I replaced key WordPress plugin functionalities in Astro, including acronyms, metadata, and tag clouds.

This post is a continuation of my migration journey from WordPress to Astro. If you have not read the previous posts, you might want to start with:

In this post, I explain how I replaced 3 WordPress plugin functionalities in Astro.

  1. Acronyms 2
  2. Add Meta Tags
  3. Better Tag Cloud

Acronyms 2

The Acronyms 2 plugin allowed me to define a list of acronyms, and automatically generated tooltips with their meanings when they appeared in posts.

Astro Implementation

In Astro, I created a rehype plugin that processes text and wraps recognized acronyms with an <abbr> tag containing the full text of the acronym as the title attribute.

Note

rehype is an ecosystem of plugins that work with HTML as structured data, specifically ASTs.
This allows for easy manipulation, or extension, of HTML content.

The first time an abbreviation is encountered on a page, the plugin will also wrap the <abbr> tag with an <dfn> tag. This semantic tag is used to indicate the defining instance of a term.

A special case is when an acronym is used in a <code> or <pre> block. In this case, the plugin will not wrap the acronym with an <abbr> tag.

src/plugins/rehypeAbbreviate.js
if (current.tagName === 'code' || current.tagName === 'pre') {
return; // Skip this node
}

See the full code on GitHub: rehypeAbbreviate.js.

Because there is no database to store the acronyms, I defined them in a YAML file and imported them into the plugin.

src/config/acronyms.yaml
ACRONYMS:
AST: Abstract Syntax Tree
HTML: HyperText Markup Language

The YAML file is loaded in the astro.config.mjs file and passed to the rehypeAbbreviate plugin.

astro.config.mjs
import { defineConfig } from "astro/config";
import yaml from "@rollup/plugin-yaml";
import yamlParser from "yaml";
import { readFileSync } from "fs";
import rehypeAbbreviate from "./src/plugins/rehypeAbbreviate.js";
export default defineConfig({
markdown: {
rehypePlugins: [
[
rehypeAbbreviate,
{ acronyms: yamlParser.parse(readFileSync("./src/config/acronyms.yaml", "utf8")).ACRONYMS}
],
],
},
vite: {
plugins: [yaml()],
},
});

I expect that loading the acronyms from a YAML file could be improved, but for now, it works well enough.

Add Meta Tags

The Add Meta Tags plugin generated metadata for search engines and social media previews.

Astro Implementation

Most of the metadata is defined in the BaseHead.astro file, which is included in one of the layout files that pass on contextual values.

src/layouts/BaseHead.astro
<meta property="og:url" content={Astro.url}>
<meta property="og:title" content={title}>
<meta property="og:description" content={description}>

Some properties are conditionally included based on availability. For example, the article object is passed to the layout when rendering a blog post.

Sections and tags can even occur multiple times, so I use the Array map function to generate multiple <meta> tags.

src/layouts/BaseHead.astro
{article && (
<meta property="og:type" content="article" />
<meta property="article:published_time" content={article.published} />
<meta property="article:modified_time" content={article.modified} />
)}
{article && article.sections && article.sections.map((section) =>
<meta property="article:section" content={section}>
)}
{article && article.tags && article.tags.map((tag) =>
<meta property="article:tag" content={tag}>
)}

View the full code on GitHub: BaseHead.astro.

Better Tag Cloud

The Better Tag Cloud plugin displayed a tag cloud where tags were weighted based on post frequency.

Astro Implementation

I used the PHP code from the plugin as a reference and, with some help from ChatGPT, created a function that:

  1. Gets a map of tags and their posts.

    src/js/util.js
    export function getTagsWithPosts(paths) {
    const posts = sortedPosts(paths);
    const tagsMap = new Map();
    posts.forEach(post => {
    post.data.tags?.forEach(tag => {
    if (!tagsMap.has(tag)) {
    tagsMap.set(tag, []);
    }
    tagsMap.get(tag).push(post);
    });
    });
    return tagsMap;
    }
  2. Sorts the tags by post count.

  3. Get the top 50 tags.

  4. Sort the tags alphabetically.

  5. Calculate the weight of each tag based on the post count. Using 8pt as the smallest font size and 22pt as the largest font size.

    src/components/Sidebar.astro
    return tagArray
    .sort(([tagA], [tagB]) => tagA.localeCompare(tagB))
    .map(([tag, tagPosts]) => ({
    tag,
    count: tagPosts.length,
    fontSize: calculateFontSize(tagPosts.length),
    }));
  6. Render the tags in the sidebar.

    src/components/Sidebar.astro
    <aside id="tag-cloud">
    <h2>Tags</h2>
    <div>
    {
    tags.map(tagItem =>
    <a
    href={`/tag/${urlifyToken(tagItem.tag)}/`}
    title={`${tagItem.count} posts tagged with "${tagItem.tag}"`}
    rel="tag"
    style={`font-size: ${tagItem.fontSize}pt;`}>{tagItem.tag}</a>
    )
    }
    </div>
    </aside>

The full code can be found on GitHub: Sidebar.astro and util.js.


These are the first 3 plugins I replaced with Astro functionality.

In the next post, I will cover more plugins, including syntax highlighting, paging, and more.

Filed under Azure
Last update:
/ Michaël Hompus

After 15 years of blogging with WordPress, I decided to take the plunge and migrate my blog to Astro. This post outlines how I moved my content, step by step, and the tools I used to make the transition.

After 15 years of blogging with WordPress, I decided to take the plunge and migrate my blog to Astro.

This post outlines how I moved my content, step by step, and the tools I used to make the transition.

This is a follow-up to my previous post: Why I Switched from WordPress to Astro: Faster, Cheaper, and Greener.

The Starting Point

As mentioned in my previous post, social reasons pushed me away from WordPress. However, even before these revelations, I was exploring alternatives. I liked WordPress’ editor and ease of use but found it frustrating to keep a virtual machine running 24/7 for a blog that was not updated regularly.

Initially, I considered an arrangement where the site could generate static content whenever updates occurred and shut down the VM otherwise. When some enthusiastic colleagues at Info Support introduced me to Astro, I realized it could be the perfect opportunity to rethink my blog entirely.

At Info Support, a team had recently rebuilt the Knowledge Center website with Astro, and their success inspired me to give it a try.

Why Not Use WordPress with Astro?

Astro does offer the option to render WordPress content using the WordPress API, but that solution still requires a running backend. This did not fit my use case, and the plugin security issues caused by Matt Mullenweg’s actions made the choice clear: I had to move away from WordPress entirely.

This realization motivated me to dive deeper and fully convert my blog to Markdown as the storage format. I found a helpful guide (How to Convert a WordPress blog to an Astro Static Site) and repository (okTurtles’ wordpress-to-astro) to kickstart my migration.

Steps to Migrate

1. Downloading the Site

I used the following wget command to download my entire site:

wget
wget -m -k -p -E https://blog.hompus.nl -D static.hompus.nl,blog.hompus.nl -H

Note

wget is a tool to download website content. In this command, the -m flag enables mirroring, -k converts links for local viewing, -p downloads necessary files, -E ensures proper file extensions, -D defines a list of domains allowed to be followed, and -H enables the spanning of hosts when downloading contents.

This approach was not perfect, but it got the job done and gave me a local copy of my site to work with.

2. Exporting Comments

Since comments have been disabled on my blog for a long time, I did not bother exporting them. This simplified the migration process significantly.

3. Exporting Posts

To export my posts, I used the Jekyll Exporter plugin. While the output was not perfect, it provided a solid starting point for importing my posts into Astro.

4. Starting with the wordpress-to-astro Project

I cloned the wordpress-to-astro project as a base for my new blog. This gave me a working Astro setup with my WordPress content preloaded.

Customizing the Blog

Once I had a basic version of the blog running, I completely overhauled the layouts, styles, and structure to fit my preferences. Although I maintained the look and feel of my original blog, I took this opportunity to modernize the technology stack.

My old blog was still built in valid XHTML 1.1. Updating to HTML5 and CSS3 allowed me to use modern web practices and improve accessibility.

Looking Ahead

In a future post, I’ll dive into the code behind my Astro blog and share more about the specific components and customizations I implemented. If you can’t wait, you can explore the source code of my blog on GitHub: https://github.com/eNeRGy164/blog.

For now, I’m thrilled with the results. The migration process taught me a lot, and the end result is a faster, greener, and more cost-effective blog that meets my needs perfectly.

Filed under Azure
Last update:
/ Michaël Hompus

For years, WordPress was my go-to platform for blogging, but it was time for a change—one that aligns better with my values, both environmentally and financially. In this post, I share the reasons behind my decision to move to an Astro app deployed on Azure Static Web Apps.

For years, WordPress was my go-to platform for blogging, but it was time for a change—one that aligns better with my values, both environmentally and financially.

While WordPress has been a fantastic tool for blogging, offering countless features and an active community, I recently decided it was time to move on.

My blog now runs as a static web app built with Astro and published on Azure Static Web Apps.

Why make this change? Let me explain.

Social Reasons to Move Away from WordPress

It’s been 15 years since I switched my blog to WordPress. Over the years, WordPress has evolved significantly, introducing features like the Gutenberg editor and seamless automatic updates for both the core system and plugins. These updates made managing my blog easier, until I realized they also introduce new risks these days.

Matt Mullenweg, the co-founder of WordPress, is undermining the ecosystem with his actions (The Register: WordPress saga escalates as WP Engine plugin forcibly forked and legal letters fly). Automatic updates, while convenient, mean that at any time, the code running on my server could be altered. This is no longer acceptable for me.

I’ve always appreciated the WordPress community, particularly people like Joost de Valk, who have contributed so much to its ecosystem. I’ve had the pleasure of meeting Joost, and lots of his colleagues, at Yoast-hosted events and enjoyed presenting at WordCamp Nijmegen in 2018.

However, Matt’s recent behavior, including his attacks on Joost and others in the community (The Register: WordPress drama latest: Leader Matt Mullenweg exiles five contributors), reinforced my decision to move away from WordPress.

While these community concerns are troubling, they weren’t my only motivation for leaving WordPress behind.

Environmental Reasons to Move Away from WordPress

Running a WordPress blog requires a server and database running 24/7. Even when I didn’t write a blog post for months, the server has to remain operational. Beyond the VM itself, resources like storage, virtual networks, and backup services also ran continuously.

Over the course of a year, this setup consumes significant energy. Let’s calculate:

  • Estimated average power usage of a “Standard B2ls v2” VM: 50W
  • Annual power consumption: 50W × 24hours/day × 365days/year = 438kWh/year
  • Equivalent carbon emissions: 438kWh/year × 0,475kg CO₂/kWh = 208,05kg CO₂/year
    (0.475kg CO₂/kWh is an average for The Netherlands)

These figures underscore how running a VM 24/7 contributes to both higher energy consumption and a larger carbon footprint.

By switching to a static web app, I’ve significantly reduced my website’s energy consumption and environmental impact.

With Astro, the only compute power used is during content generation when I update the blog. The static web apps serves the pre-generated content, meaning no server resources are wasted on idle time—an efficient alternative to 24/7 VM hosting.

Financial Reasons to Move Away from WordPress

Yes, WordPress itself is free, but Azure resources are not.

Hosting a WordPress site requires an always-on VM, storage, networking, and backups, which quickly add up in cost.

Here’s a breakdown of my Azure expenses for the WordPress setup:

  • VM cost: €28,91/month
  • Storage and networking: €11,21/month
  • Total annual cost: €481,44/year

By switching to Azure Static Web Apps, I eliminated these recurring costs. Static web hosting is not only greener but also significantly cheaper. Actually, I run on the Free tier! (Hey, I’m Dutch after all!)

The Free tier includes SSL certificates and custom domains at no cost, perfect for hosting a personal blog.

Another cost-saving bonus: With WordPress, I used WP Rocket for optimization, which cost me approximately €59 per year. WP Rocket handled minification, gzip compression, and caching. Now, Azure Static Web Apps provide gzip and Brotli compression out of the box, while Astro automatically minifies output. No additional plugins are required.

And the results? My Google Lighthouse score is now 4 × 100! (As long as I avoid YouTube embeds, because even Google doesn’t follow its own guidelines. Go figure.)

What Did the Move Cost Me?

Astro was new to me, so building an Astro blog that matched my old site took some time and effort. Not everything is available out of the box, but the flexibility was worth it.

I write this blogpost on my laptop, using Visual Studio Code and can run the whole blog in seconds by just executing npx astro dev.

I’ll dive into the details of my Astro setup, web components, and WordPress plugin replacements in a future blog post.

For now, I’m thrilled with the move. My blog is faster, cheaper, greener, and no longer reliant on WordPress updates. If you’re considering a similar switch, I’d love to hear about your experience!

Filed under Azure
Last update:
/ Michaël Hompus

In the past few years, I have presented multiple sessions all around the world, although mostly virtual. Presenting for a live crowd was something I only did inside The Netherlands before. With restrictions being lifted during 2022, new opportunities presented themselves and I was able to be in front of a live audience again, even outside the country!

In the past few years, I have presented multiple sessions all around the world, although mostly virtual. Presenting for a live crowd was something I only did inside The Netherlands before COVID-19 struck the world.

With restrictions being lifted during the year of 2022, new opportunities presented themselves and I was able to be in front of a live audience again and on some occasions, even outside the country!

dotNed meeting February

This was not my first time doing a dotNed user group session. The session was still only online. It was interesting for me, because it was the first time, I would give my Azure cost management related session.

In Dutch it was called Ons bint zuunig, setting the theme with Zeeuws meisje.

AzureLive

Still in a hybrid setup, the audience of AzureLive could watch the streams from home. However, half of the presenters joined physically at the same location where the recordings took place. It was nice to meet new people after doing everything virtually.

This was also my first time meeting Henk Boelman and Suzanne Daniels (my MCs), Stacy Cashmore, and Barbara Forbes as fellow speakers, which I would join again at later conferences.

dotNed Saturday

dotNed Saturday is an event where I have had the honor of speaking for multiple years now. I already did a talk about Azure Key Vault at the 2018 edition, but for this year the content was updated to reflect the current state of the service.

I was joined by (amongst others) Sander Molenkamp, Roland Guijt, Marcel de Vries, and Dennis Doomen as fellow speakers.

IglooConf: Midsummer

The first time I had to travel by plane to get to my speaking gig!

Not only was IglooConf a very nice 2-day conference in Helsinki 🇫🇮, it also meant multiple days being highly involved with the other speakers. My session was about Dapr, and how it can let you postpone architectural choices to a later moment in time.

I really had a blast together with Magnus Mårtensson, Glenn Colpaert, Alan Smith, Karl Ots, Mark Brown, Roberto Freato, Sakari Nahi, Rik Hepworth, Andreas Erben, Pieter Vandenheede, and Stacy. Many of whom I will join at later conferences again.

Developer Week

This time I could take the train to the conference. Joined by my colleagues Hanno Embregts and Peter Wessels we traveled to Developer Week in Nürnberg 🇩🇪.

Originally, I would only do my session on Dapr. Due to many speaker cancellations, I also presented my sessions about Azure cost management and Using your source code to generate documentation.

During the conference I was able to join up with Dennis and Roland again and have the privilege to spend some time with Rob Richardson, Matteo Emili, Salvatore Merone, Scott Guymer, and Niek Palm. Specially meeting Niek was fun as we went to school together over 25 years ago.

Azure Fest

For this one I did not have to travel far as Azure Fest was hosted at the Info Support office. My session was about Azure cost management again, apparently a hot topic these days.

This was the first time meeting Sam Vanhoutte as speaker, but also joining up again with Sander, Henk and Barbara for a second time.

K!K live

K!K live is a smaller meet-up in the Netherlands. Still, I had some nice interaction with the audience during and after my session about Azure cost management.

CloudBrew

This was my last session of 2022. (I had to cancel the Dev-Cloud and .NET Developer conferences in Köln 🇩🇪 due to personal circumstances).

I traveled, by car this time, to the nice city of Mechelen 🇧🇪.

Besides my session about Azure cost management, CloudBrew felt like a nice reunion meeting Rik, Barbara, Roberto, Sam, and Pieter again.

2023

Will I be speaking as often in 2023? I do not know; it is up to the people judging CFPs and hoping my talks fit in with their programs. But in January I am certain to hit the stage again.

NDC London

After being a speaker at the NDC London 2021 online edition, this time I will be traveling by train to London 🇬🇧, together with my colleague Sander.

I am looking forward to meeting Andreas, Magnus, Marcel, Pieter, Rob, Sander, Stacy, and Suzanne again on stage!

Filed under Azure, C#
Last update: