I’ve been blogging on WordPress for close to twenty years. It’s been a great platform, but I’d been thinking about a change for a while. WordPress had gotten heavier over the years - more plugins, more updates, more things to manage. I wanted something simpler. Something where I could just write in a text file and push it live.
Brad recently migrated his blog - Feld Thoughts - from WordPress to Hugo and that was the kick I needed to finally do this. He gave me quite a few tips and pointers that were incredibly helpful. Seeing him go through the process gave me the confidence that this was both doable and worth doing.
Why Hugo
Hugo is a static site generator. You write posts in Markdown, run a build command, and out comes a set of plain HTML files. No database, no PHP, no server-side processing. Just files.
That simplicity is the whole point. My blog doesn’t need a content management system running on a server somewhere. It needs to turn my writing into web pages. Hugo does that extremely well, and it does it fast. I also like the simplicty of the template I choose - the focus is more about the content (but still with a place to include my books and other stuff I’m working on).
The Stack
Here’s what the new setup looks like:
- Hugo generates the site from Markdown files
- GitHub stores the source code and content
- Vercel builds and hosts the site - auto-deploys every time I push to GitHub
- Cloudflare handles DNS and CDN
- Kit (formerly ConvertKit) handles email subscriptions via RSS
Publishing a new post is literally: write a Markdown file, commit it, push to GitHub. Vercel picks up the change and deploys in about 30 seconds. No logging into an admin panel. No clicking through menus. Just text files and version control.
Choosing and Customizing a Theme
I went with the Notepadium theme as a starting point. It’s clean and minimal - exactly the aesthetic I wanted. But “starting point” is the key phrase. I ended up doing extensive customization.
I worked with Claude to adjust the CSS, tweak the layout, get the typography right, and handle things like dark mode support, pagination, and how lead images display. We went back and forth on the design until it felt right. The beauty of Hugo themes is that you can override any template file by placing your own version in the layouts/ directory, so customization doesn’t mean forking the theme.
The Migration Process
Exporting from WordPress
The first step was getting all my content out of WordPress. I used the standard WordPress export tool, which gives you a big XML file with all your posts, pages, and metadata. From there I used a conversion tool that I had Claude create for me to turn those into Markdown files with the right Hugo frontmatter.
URL Mapping
This was critical. I’ve got almost two decades of posts, and there are links all over the internet pointing to them. I needed every old URL to keep working.
WordPress uses a URL structure like /archives/2024/05/some-post-title.html. I configured Hugo to use the same pattern, so every existing link still resolves to the right post. No redirects needed - the URLs just work. (side note - let me know if you find any broken links that I missed!)
Media Library
WordPress stores images and other media in its own upload directory structure. I had to port all of that over, making sure image paths in posts pointed to the right locations. This took some patience - going through posts, finding broken images, and fixing paths. We adjusted the media directory structure to work cleanly with Hugo’s static file handling.
Search and Navigation
One thing Hugo doesn’t give you out of the box is search. WordPress has search built in. I had to think about whether I needed it and, if so, how to implement it. Same with things like category pages, archive views, and pagination. Each of these required some template work.
The Email Subscription Migration
This was honestly the trickiest part.
I use Kit for bulk email for most of my work, but my blog was connected to MailChimp. I wanted to change that. The idea is simple - when I publish a new post, it shows up in my RSS feed, Kit detects it, and sends an email to subscribers with the post content. Simple in theory.
In practice, we ran into a few issues.
The Missing Content Field
Kit expects the full post body to come through in a specific RSS field called content:encoded. Hugo’s default RSS template only includes a description field with the post summary. So my first test emails went out with just a snippet instead of the full post.
The fix was creating a custom RSS template that includes content:encoded with the full post content. Straightforward once we figured out what Kit was looking for.
There were a bunch of other challenges as well. I spent more time troubleshooting this part of the migration than anything else.
The XML Parsing Bug
This one was sneaky. After adding the content:encoded field, we initially wrapped the content in CDATA tags - which is a standard XML technique for including HTML content. It looked correct. But it turns out that if your post content happens to contain the character sequence ]]> anywhere, it breaks the CDATA wrapper and produces invalid XML.
The RSS feed would load fine in a browser but fail for automated parsers. Kit silently failed to pick up new posts. We burned through several test-post cycles - publishing, checking, unpublishing, tweaking - before spotting the actual XML error.
The fix was simple: use HTML entity encoding instead of CDATA wrapping. But finding the problem took real debugging.
Test Post Cycles
We went through probably half a dozen rounds of publishing test posts, checking whether Kit picked them up, looking at the email output, and adjusting. Each cycle meant creating a post, pushing it live, waiting for Kit to poll the RSS feed, checking the email, then taking the test post down. It wasn’t glamorous work, but it’s the kind of thing that has to actually be tested end-to-end.
Working with AI
I should mention - I did this entire migration working alongside Claude in my terminal. Not as a chatbot answering questions, but as an active collaborator. Claude made the code changes, ran the git commands, debugged the RSS issues, helped customize the CSS, and handled the DNS configuration.
This is the kind of project where AI collaboration really shines. There are a hundred small technical decisions - CSS tweaks, template overrides, XML formatting, DNS records - that individually aren’t hard but collectively take hours. Having an AI partner that can read code, write code, and execute commands in real time made the whole thing dramatically faster.
The Result
The site is faster. The publishing workflow is simpler. The content is stored as plain text files in a git repo, which means I have full version history of every post I’ve ever written. And there’s no server to maintain - Vercel handles all of that.
If you’re thinking about making a similar move, I’d highly recommend it. The Hugo ecosystem is mature, the documentation is solid, and the combination of Hugo + GitHub + Vercel is a genuinely great stack for a blog.
And if you’ve been reading this blog for a while, the transition should be invisible to you. Same URLs, same RSS feed, same email subscriptions. Just a cleaner engine underneath.
Let me know what you think!