Why migrate?

My blog has been running on Jekyll with the Chirpy theme for a few years. It worked fine, but I had a growing list of annoyances:

  • Ruby and Bundler dependencies that I never touched outside of the blog
  • The build pipeline relied on a custom deploy script and Ruby 2.7
  • Updating the theme meant dealing with gem version conflicts
  • I wanted something I fully understood and controlled, with zero magic

The blog is 11 posts. It doesn't need a static site generator. It needs HTML files.

What I ended up with

A pure static site. No build step, no framework, no dependencies. Just HTML, CSS and a small JS file for search, category filtering and theme toggling.

.
├── index.html          # Post listing with search & categories
├── about.html          # About page
├── posts/              # 11 HTML files
├── css/style.css       # Dark/light mode
├── js/main.js          # Search, filter, theme toggle
├── assets/images/      # All images
├── sitemap.xml         # SEO: all posts with last modified dates
├── robots.txt          # SEO: points crawlers to sitemap
└── .nojekyll           # Tells GitHub Pages to skip Jekyll

GitHub Pages serves it directly. The GitHub Actions workflow went from "install Ruby, bundle gems, run deploy script" to "upload files, deploy". That's it.

For SEO, the site includes a sitemap.xml that lists all posts with their last modified dates and a robots.txt that points search engines to the sitemap. With Jekyll, the jekyll-sitemap plugin generated these automatically. Without a build step, they're just static files that I update manually when adding new posts.

The migration process

I used Claude Code to do the heavy lifting. The whole thing happened in a single session.

1. Analyze the old site

First step was reading through the existing Jekyll structure: _config.yml, all the markdown posts, the about page, and the image assets. This gave Claude the full picture of what content existed and how it was structured.

2. Build the new site in a staging folder

Everything was built under a New Blog/ folder so I could preview it before replacing anything. The design was written from scratch: a modern dark theme with Inter font, code highlighting via highlight.js, and responsive layout.

Each markdown post was converted to a self-contained HTML page with proper SEO meta tags (Open Graph, Twitter Cards, canonical URLs), Google Analytics, and syntax-highlighted code blocks.

3. Add features iteratively

Once the base was working, I kept adding things through conversation:

  • Draft system: A simple way to preview unpublished posts before going live
  • Search: Client-side text search across titles, descriptions and tags
  • Category filtering: Buttons that filter posts by topic (Azure, PowerShell, Security, etc.)
  • Dark/light mode: System preference detection with manual toggle, saved to localStorage

4. Review and polish

This is where most of the back-and-forth happened. Things like:

  • Making sure Swedish characters rendered correctly with proper UTF-8 encoding
  • Adjusting the dark theme to be less aggressively dark
  • Getting the avatar and favicon right
  • Verifying all image paths resolved after moving files around

5. Replace and deploy

Once I was happy with the preview, we removed all the old Jekyll files, moved the new site to root, updated the GitHub Actions workflow, and pushed. One commit, clean swap.

The old vs new build pipeline

Before:

# Old - Jekyll build
steps:
  - uses: actions/checkout@v2
  - uses: ruby/setup-ruby@v1
    with:
      ruby-version: 2.7
      bundler-cache: true
  - run: bash tools/deploy.sh

After:

# New - just upload and deploy
steps:
  - uses: actions/checkout@v4
  - uses: actions/configure-pages@v5
  - uses: actions/upload-pages-artifact@v3
    with:
      path: '.'
  - uses: actions/deploy-pages@v4

No Ruby. No bundler. No deploy script. The build is basically "copy files to GitHub Pages".

One thing to remember: you need a .nojekyll file in your repo root. Without it, GitHub Pages assumes your site is Jekyll and tries to build it, which can cause files to be ignored or processed incorrectly. It's just an empty file, but forgetting it will cause headaches.

The polish loop

What surprised me the most was what happened after the initial migration. The site was functional, but the real value came from the rapid iteration that followed. In the same session, I kept throwing requests at Claude Code and watching them land within seconds:

  • The dark mode felt too dark, so we lifted the background colors a few notches until it felt right
  • Light mode had grey icons and nav text that should have been black. Fixed across all 13 pages instantly
  • Hover states were inconsistent between nav links and social icons. Made them all go accent blue
  • Swapped the font from Inter to Plus Jakarta Sans across every page
  • Added a floating "back to posts" button that sits next to the scroll-to-top button, so readers can always navigate back without scrolling to the header
  • Generated an avatar using ChatGPT and replaced the favicon and nav logo with it
  • Dark/light mode automatically follows your OS preference out of the box, with a manual toggle that saves to localStorage. I didn't even realize this was already working until I asked about it

Each of these was a one-line request from me and a near-instant change across the whole site. The kind of tweaks that would normally pile up in a "I'll fix it later" list actually got done on the spot. That feedback loop is what made this feel less like a migration project and more like a conversation with a very fast colleague.

Takeaway

Sometimes the best framework is no framework. My blog loads instantly, I can edit it in any text editor, and the deployment pipeline is 4 lines of YAML. That's hard to beat.

But the real takeaway for me was how well AI-assisted development works for this kind of project. Not just the initial build, but the ongoing back-and-forth of "this doesn't feel right, change it" until it does. The entire migration, including all the polish, happened in a single sitting.

//Sebastian