How I moved my blog from Wordpress to a static site on Next.js

Published on
Reading time
Authors

"Hey Simon, did you know your blog is a bit slow? I will try and troubleshoot why."

That's how this all started.

I guess you (and me, I think...) have to thank Shane Baldacchino for finally pushing me to do something that's been on the back of my mind for a while!

TL;DR - want to skip to the gory details and simply view the final tech stack used? Be my guest!

First a bit of history

I've been blogging for over ten years, and have mostly been happy using Wordpress.com. Along the way I've had plenty of people suggesting I self host Wordpress to which I've said "no thanks, I just want to write, not patch security holes or manage availability".

When I started blogging in 2012 the web was a very different place as can be witnessed from the look of my blog at launch (thanks Internet Wayback Machine) and the following updates over the years!

2012

Siliconvalve blog from 2012.

2016

Siliconvalve blog from 2016.

2019

Siliconvalve blog from 2019.

Over the last few years I have been watching the shift to Static Site Generators (SSGs) as well, but honestly did not fell the need to dabble in those either.

I just wanted to write.

What changed?

When you use a hosted service you agree to give up some freedom of control for simplicty's sake, and Wordpress.com is no different.

The first real issue I ever had was when Wordpress rolled out the Gutenberg / Block Editor and made it the default. Up until this point I'd always written my content using markup, and to suddenly be forced to use this block editor was frustrating. Thankfully the "Classic Editor" turned out to still be available, but you'd question how long two editors would be maintained.

I also accidentally discovered you can write markdown in Wordpress in the meantime 😂. Though it works, it feels like the resulting output is a bit hit-and-miss when it comes to styling which caused some weird layout issues. Good communication from Wordpress.com about these features... *cough*

Sticking with styling, on Wordpress.com you can select a pre-packaged theme and then customise it by adding additional CSS styles. It does work but is defintely a bit clunky, and in trying to move to a more modern feel for my blog it really started to feel like I was being limited in what I could achieve.

To top it all off, the embedded GitHub Gists on my blog started to look weird, most likely due to CSS and page layout elements that were outside of my control (or difficult to identify and change).

No, I still didn't want to self-host.

Like a lot of people in tech, the volume of markdown I have been writing is substantial, and the simplicity the format introduces in writing content is hard to beat.

Clearly I'd had an inkling for a while that maybe Wordpress wasn't the best home for my blog!

My blog also suffered a big drop in traffic during late 2022. It was crazy. Between September and November 2022 I lost over 50% of my traffic even though my content remain unchanged - you can see the impact below. Someone suggested site performance could play a part in that so that got me thinking more about the future of my blog.

Siliconvalve blog traffic for 2022.

I think Shane's comment was realy the final straw! 😂

What happened next

I'd recently seen a great talk from Microsoft's Suhas Rao on Azure Static Web Apps, and I discovered the service has quite a nice free tier that maybe made it a good place for my blog.

Additionally, through my invovlement in Serverless Days ANZ, I've had the chance to play a bit with a website built with 11ty and nunjucks that deploys to Netlify. The sourcecode lives in GitHub and I liked th PR-based review worklow, even if my CSS skills leave a bit to be desired these days...

There are also a bunch of people I know who publish blogs and they are all using generators like Gatsby or Jekyll, but I didn't really know much about these so needed to do some research first.

It seemed like a static site generator would be the go, but how to decide which one?

Thankfully the internet provided! I found the Jamstack generators page that listed all the current options, and after a bit of reading and testing I settled on Next.js as the framework I wanted to use.

It seems like Next.js is one of the more actively developed frameworks out there and it was one of the supported options for Azure Static Web Apps.

Choosing a template

I started my next step by actually looking for sample blog templates for a range of the popular SSGs. I found a few, but the one that stood out was Tailwind Nextjs Starter Blog from Timothy Lin. This really ended up selling me on Next.js and gave me a modern looking blog that was designed to be performant, responsive, SEO friendly and customisable (even with my limited CSS and JavaScript skills). Timothy's done a fantastic job building this out so I made sure to sponsor him on GitHub to support his hard work.

Migrating Content

I had a lot of content on my Wordpress site, and I wanted to keep it all. I also wanted to keep the URLs the same, so I needed to find a way to migrate the content from Wordpress to my new blog.

Wordpress provides an export function that creates an XML file containing all your content in an RSS feed format. Even though it doesn't include any images this capability is already a win! A quick snippet is shown below.

<rss version="2.0"
     xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wp="http://wordpress.org/export/1.2/">
    <channel>
        <title>siliconvalve</title>
        <link>http://blog.siliconvalve.com</link>
        <description>cloud tips and tricks with simon waight</description>
        <pubDate>Fri, 24 Feb 2023 00:25:28 +0000</pubDate>
        <language>en</language>
        <wp:wxr_version>1.2</wp:wxr_version>
        <wp:base_site_url>http://wordpress.com/</wp:base_site_url>
        <wp:base_blog_url>http://blog.siliconvalve.com</wp:base_blog_url>
    </channel>
</rss>

So.. a good start. But I need this content in Markdown... and I'm not going to try and do that manually with 10 years of content!

Enter the excellent wordpress-export-to-markdown project on GitHub which not only converts the XML to Markdown, but also downloads any images that are linked in the content.

I was headed in the right direction, but there were a few bumps ahead, so hang on tight!

Markdown Header Format

I didn't see when I selected the blog template that it requires the header in the markdown file to be in a specific format. While most of the header fields produced when converting from XML to markdown were valid there were small number of missing or unused fields in every single post!

I had to go ahead and manually fix this across all posts! The killer for me was the missing tags array which caused quite a few issues as it is required (even if empty) for every post. While I did have categories, I had to change them all to tags. When do this I took the opportunity to tidy up the tags as well.

Embeds

When I did my first pass of content I didn't understand how embeds work with a Next.js SSG, so I ended up simply leaving placeholders to return to. It turns out that the blog template supports MDX, so I can use that along with React components to embed content like a Tweet or a YouTube video. Here's how I embedded that tweet above in this page.

import { TwitterTweetEmbed } from 'react-twitter-embed'
;<TwitterTweetEmbed tweetId={'1631526691603619842'} />

Note: As I have discovered, it's really important to undestand that embeds can impact page performance quite substantially, so you need to test when adding them in and consider other options in the event they have a negative impact. Also look at the component you are using to see how it is implemented and if there are any performance implications.

Images

Dynamic image resizing, lazy loading and webp format support are fantastic out-of-the-box features with this blog template. They all rely on the nextjs/image API which at time of writing is not supported by Azure Static Web Apps (though it should be supported in future). One provider that does support it is Vercel, which is actually where this blog now lives. Their hobbyist tier fits my needs perfectly and integrates directly with the content GitHub repository and I can use a PR-based preview process for updates.

To take advantage of this feature I had to find all the image refrences in my content and update the format. I also had to figure out the right location of the images and reference to get the next/image API working. As an example, the first image above is referenced using the standard markdown for images.

![Siliconvalve blog from 2016.](/static/images/2023/2023-03-18_18-07-35.png)

But the actual image file lives in /public/static/images/2023. I had to learn a bit about Nextjs' static file serving and image optimization to get this working.

Note: Animated GIFs are not supported by the nextjs/image API, so the ONE I had on my site ended up being consigned to history!

Off-by-one day problem

I mentioned earlier that I wanted to maintain the URL scheme for my new blog for the purpose of SEO and historical linkage. The easiest way to achieve this with the new template is to organise content by folder that represents year/month/date and then have the markdown files in that folder.

As I was going back through and reviewing my content I discovered that some of the posts had the wrong date compared to the old blog. It appears this might have originated from the Wordpress export which seems to have used a different timezone for the export, so that in some situations a post would be from the day before (i.e. 2023/02/23 instead of 2023/02/24). I had to go back and manually review this for each post as there was no conistent pattern! I also had to manually update the date in the markdown file for each post. 😔

The path changed anyway!

The new template required all posts to sit under the root path of 'posts' and then the year/month/date folder structure. I figured I must be able to put 302 redirects in place to solve this, and this is something you can do by editing the redirects section of the next.config.js file as shown below. There might be a better / simpler syntax - let me know in the comments if there is!

next.config.js
async redirects() {
    return [
      {
        source: '/2012/:slug*',
        destination: '/posts/2012/:slug*',
        permanent: true,
      },
      {
        source: '/2013/:slug*',
        destination: '/posts/2013/:slug*',
        permanent: true,
      },
    ]
}

When did favicon become so complicated?

This used to be a simple concept! What happened?! Looking at the blog template I discovered there were a range of missing images and metadata I needed for the icon for my blog to appear correctly for as many people as possible. I found a great favicon generator that helped me generate all the different sizes, formats and metadata I needed using a sample logo I already had. I loved the time it saved me so much I donated to the project!

Testing social previews

Given the new template and framework exposed more control over how the site rendered, including social previews, I wanted to check what the social previewing looked like. Using the pre-launch Vercel site as the source I was able to use "Social Share Preview" to see what we would be displayed and validate content.

What about supporting blog features?

At this stage I had my content mostly sorted, but still needed to look at supporting features that make running a blog easier.

Analytics

On Wordpress you not only get a content management system, you also have access to analytics. While I'm in not writing here to know how many views or clicks I get, it defintely helps shape what you write about and when to publish if you can see the impact of your content. While Wordpress utilised Jetpack for analytics, there is no option to use this with Next.js, so I need an alternative.

I could see a few options listed in the siteMetadata.js file for the project, so I thought I'd give some a try and decide.

siteMetadata.js
const siteMetadata = {
  analytics: {
    plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app
    simpleAnalytics: false, // true or false
    umamiWebsiteId: '', // e.g. 123e4567-e89b-12d3-a456-426614174000
    googleAnalyticsId: '', // e.g. UA-000000-2 or G-XXXXXXX
    posthogAnalyticsId: '', // posthog.init e.g. phc_5yXvArzvRdqtZIsHkEm3Fkkhm3d0bEYUXCaFISzqPSQ
  },
}

I started off with Posthog. It has a free tier and is open source, however I later discovered during performance testing with PageSpeed that the call to Posthog was causing a significant delay in the page load (almost 20% slower). Part of the reason for the replatform was to improve performance, so Posthog was out at this point!

The next option I thought I'd try is Plausible, and while it's not a free service, performance testing showed little impact on page load times. I'm currently within the trial period for the service, but I'm happy to pay for it if it continues to perform well. The dashboard is quite clean and easy to use, and I like the fact that it's open source and platform independent, so if I move hosts I don't lose my stats.

Siliconvalve blog traffic on Plausible.

I did notice that Vercel as an analytics capability as well, but their audience feature is currently only in Beta. Given my comment around Plausible being platform independent I am not sure at this stage if I'd want to rely on Vercel for analytics if there's a chance I might move hosting platform in future.

Comments

Sadly there was no easy way to migrate across the comments my old blog had, and while there aren't that many, it's still a bit sad to leave them behind. I have seen a fair few people with static sites use Disqus for comments, but I wanted to see what other options are out there first. Once again I took a look at the siteMetadata.js file and found a few options.

siteMetadata.js
const siteMetadata = {
  comment: {
    provider: 'giscus', // supported providers: giscus, utterances, disqus
    giscusConfig: {
      repo: process.env.NEXT_PUBLIC_GISCUS_REPO,
      repositoryId: process.env.NEXT_PUBLIC_GISCUS_REPOSITORY_ID,
      category: 'Q&A', //process.env.NEXT_PUBLIC_GISCUS_CATEGORY,
      categoryId: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID,
      mapping: 'title', // supported options: pathname, url, title
    },
    utterancesConfig: {
      repo: process.env.NEXT_PUBLIC_UTTERANCES_REPO,
      issueTerm: '', // supported options: pathname, url, title
      label: '', // label (optional): Comment 💬
      theme: '',
      darkTheme: '',
    },
    disqusConfig: {
      shortname: process.env.NEXT_PUBLIC_DISQUS_SHORTNAME,
    },
  }
}

No reason to not start at the top (giscus), and after a quick play I was satisfied it was a good choice, with benefit being all content is stored in a GitHub Discussion that I control.

I did need to make one CSS tweak to fix a display issue, and this has been submitted as a PR to the baseline template my blog uses.

Ready to go!

With the content migrated, and the features I wanted in place, it was time to launch the new blog. I headed over to my DNS management console and updated the blog entry to point at the Vercel site. Within a few moments I had traffic landing on the new site!

I am really happy with the PageSpeed results, particularly on the main home page. Some of the posts are a little slow due to length and embeds, but overall the performance is much snappier than the old Wordpress site.

Summarising the stack

I've written a bunch in this post, but I did want to provide a "shopping list" of what I'm using now.

  • Next.js - React framework
  • Tailwind Nextjs Starter Blog from Timothy Lin
  • GitHub - Blog content and workflow (my content lives in a private repository) New post idea? Create a branch, write the content, commit and create a PR. *chef's kiss*
  • Visual Studio Code - Content Editor (Markdown / MDX). The beauty is I can also use Codespaces or github.dev to author and manage content at any time.
  • Vercel - Hosting (Production + Preview) using their Hobby tier
  • Plausible - Analytics (paid service, but likely to stay based on current features)
  • Giscus - Comments. Still under development but feels stable.

Well, we've reached their end of this epic! Overall I am really happy with the shift, and now that things are in place I can get back to concentrate on just writing. I hope this also provides you with some useful tips and insights if you are looking to migrate away from a Wordpress-hosted blog.

Happy days! 😎