← Blog

Migrating a Jekyll Blog to Astro 5 — with AI as My Pair Programmer

This blog has been running on Jekyll since 2013. Pixyll theme, kramdown, GitHub Pages — the classic stack. The trigger to migrate was Ruby dependencies: the github-pages gem was stuck at v223 because commonmarker 0.23.x caps Ruby at < 4.0. I could not update the gems and I did not want to downgrade Ruby.

I had been considering Astro for a while. Static-first, Markdown-native, component islands where they are needed, Vite under the hood — what Jekyll would be if it were designed today.

The AI Experiment

For the migration I used Claude as a pair programmer across multiple sessions, not just to generate boilerplate. The whole thing took roughly a week.

What Was Done

  • The Astro project keeps the same URL structure as Jekyll — date-based paths like /{year}/{month}/{slug}.html — so existing inbound links from Stack Overflow and forums keep working. Posts keep the YYYY-MM-DD-slug.md filename convention; the date is parsed from the filename, so front-matter dates are optional.
  • scripts/update-cv.mjs fetches the HTML export of my CV from Google Docs, strips the styling, and injects clean semantic HTML into the Astro page on every build. I now edit my CV in Google Docs.
  • CSS variables (--text-primary, --bg-primary, etc.) replaced hardcoded hex values. Dark mode followed: :root.dark theme with system preference detection, user toggle, localStorage persistence.
  • Repeated markup was extracted into ProfileCard.astro and SocialLinks.astro. Float-and-clearfix layouts were replaced with flexbox.
  • Firebase authentication with Google sign-in was added on top — auth pages (/login, /account, /welcome) are React components with client:load; every other page is plain static HTML.

Killing React Where It Did Not Belong

The homepage had a CapabilityRotator — a component that fades through 291 phrases describing what I can help with. It was built as a React component because that is what you reach for when you need state and timers. It was the only React component on the homepage. Every visitor was downloading the full React runtime (~183 KB raw, ~58 KB gzipped) just to rotate some text.

We rewrote it as a vanilla JS Astro component — same behaviour, same accessibility attributes, same animation. The entire thing is now an inline <script> tag.

BeforeAfter
JS bundles on homepageReact runtime + component (191 KB)0 KB (inline script)
Gzipped transfer~62 KB~0 KB
Network requests2 extra JS files0

The auth pages still load React — they need it. The pages that 99% of visitors see are now framework-free.

What I Learned

The most productive sessions were multi-hour conversations, not prompts. “Make it dark mode” is a prompt. “We added CSS variables in phase 2, now let’s build dark mode on top of those” is a conversation. Claude is significantly better at the second.

A CLAUDE.md file in the repository root that captures architecture, conventions, and the project’s quirks does most of the heavy lifting. Every new session starts with that loaded, and I never had to re-explain why URLs use the date-based shape or why front-matter dates are optional.

Six small phases beat one big rewrite — also true when your partner is human. The site stayed deployable throughout. The React-for-a-text-rotator finding stuck with me: 191 KB of “well, that is how you do state in components”. The lesson is older than AI — question the default before reaching for the framework.

The Numbers

MetricJekyllAstro
Build time~8s~3s
Pages3235
Dark modeNoYes
CV updatesManual copy-pasteAutomatic from Google Docs
Auth systemNoneFirebase Google sign-in
React on homepageN/AEliminated
FrameworkRuby 3.x + Jekyll 4Node 22 + Astro 5

The site is live at andrius.mobi. The source tells the full story.