Migrating This Blog from Jekyll to Astro
After years of running this site on Jekyll and GitHub Pages, I decided it was time for a change. This post walks through why I migrated to Astro, what the process looked like, and how the new site is structured.
Why Leave Jekyll?
I did not leave Jekyll because it was broken. I left because I wanted a modern stack and, with AI agents now widely available, I wanted to experiment, learn in public, and share what I learned. I had not updated this site in years. To break that cycle, I used this migration as a practical learning project: define the work with Spec Kit, then execute it end-to-end with Copilot CLI using GPT and Claude models as implementation partners. Astro was the right fit for that goal: modern developer experience, static-first output for GitHub Pages, and a clean content model that made it easy to restart and iterate quickly.
The Migration Plan
I took a deliberate, structured approach rather than hacking things together:
- Defined requirements with Specify Kit (spec-kit) β all requirements were generated using Specify Kit, then refined to keep the same navigation (Home, About, Archive, Readings), keep only GitHub and LinkedIn contact links (removing email and Keybase), and start fresh without migrating legacy posts.
- Set up the Astro project β configured
astro.config.mjsfor static output targetingzeinsleiman.com, added TypeScript support, and created thepackage.jsonwith dev/build/preview scripts. - Preserved the visual identity β the original site had a distinctive dark sidebar (
#2e2e3a), an orange accent color (#f64929), and Roboto Slab / Roboto font pairing. All of that carried over into the newsite.css. - Built the component architecture β a shared
BaseLayout.astrohandles the sidebar, navigation, contact links, and footer. Individual pages just slot their content in. - Created content infrastructure β Astroβs content collections with a Zod schema give me type-safe frontmatter validation for blog posts. No more silent failures from a typo in YAML.
- Validated the build β the final build produces 5 static pages in about 1.2 seconds with zero client-side JavaScript.
What the New Stack Looks Like
src/
βββ content/blog/ # Markdown posts go here
βββ components/ # ContactBlock, ReadingCard
βββ data/ # contact-links.ts, navigation.ts, readings.ts
βββ layouts/ # BaseLayout, PostLayout
βββ pages/ # index, about, archive, readings, blog/[slug]
βββ styles/site.css # All styles in one file
Key technical choices:
- Astro 5.x with static output β no SSR, no adapter, just HTML files in
dist/. - Content collections with
content.config.tsdefining the blog schema (title, date, tags, published, description). - Dynamic routing via
src/pages/blog/[slug].astroβ Astro generates a page for each published post at build time. - No external icon libraries β contact link icons are inline SVGs, keeping the dependency count minimal.
- GitHub Pages for hosting β same as before, just deploying the
dist/folder instead of relying on Jekyllβs built-in GitHub Pages integration.
Publishing a New Post
The workflow is straightforward:
- Create a Markdown file in
src/content/blog/with the required frontmatter:
---
title: "Post Title"
date: 2026-02-20
tags: [tag1, tag2]
published: true
description: "A short summary."
---
- Write the post content in Markdown below the frontmatter.
- Run
npm run devto preview locally atlocalhost:4321. - Push to GitHub β the site builds and deploys.
The archive page automatically groups posts by year, and the homepage lists them in reverse chronological order. Setting published: false in the frontmatter hides a post from the build without deleting it.
What I Didnβt Migrate
I intentionally left all legacy Jekyll posts behind. The old _posts/, _layouts/, _includes/, and _config.yml files still exist in the repository history, but the Astro build ignores them entirely. This was a clean-slate decision β the old content had served its purpose, and starting fresh felt right.
Final Thoughts
The migration took a single focused session. Using Copilot CLI with Opus 4.6 made the migration seamless and helped me move this simple site to Astro in about one hour, even with minimal Astro knowledge. Astroβs learning curve is gentle if youβre already comfortable with component-based frameworks, and the static output model maps perfectly to GitHub Pages.
If youβre running a Jekyll blog and want to experiment with AI Agents, this might be a cool experiment.