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 theYYYY-MM-DD-slug.mdfilename convention; the date is parsed from the filename, so front-matter dates are optional. scripts/update-cv.mjsfetches 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.darktheme with system preference detection, user toggle,localStoragepersistence. - Repeated markup was extracted into
ProfileCard.astroandSocialLinks.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 withclient: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.
| Before | After | |
|---|---|---|
| JS bundles on homepage | React runtime + component (191 KB) | 0 KB (inline script) |
| Gzipped transfer | ~62 KB | ~0 KB |
| Network requests | 2 extra JS files | 0 |
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
| Metric | Jekyll | Astro |
|---|---|---|
| Build time | ~8s | ~3s |
| Pages | 32 | 35 |
| Dark mode | No | Yes |
| CV updates | Manual copy-paste | Automatic from Google Docs |
| Auth system | None | Firebase Google sign-in |
| React on homepage | N/A | Eliminated |
| Framework | Ruby 3.x + Jekyll 4 | Node 22 + Astro 5 |
The site is live at andrius.mobi. The source tells the full story.