June 15, 2026

Rebuilding christlieb.eu with Astro: from a Laravel app to a static site

By Manuel Christlieb — Staff Engineer

A while ago I revived this blog on a Laravel app with a Filament admin, and not long after wrote about hosting it with Coolify. That setup was fun, but it was also a lot of moving parts for what this site actually is: a handful of static pages and a blog. A database, a queue, Horizon, a server — none of it earns its keep here. So I rebuilt the whole thing as a static Astro site and put it on GitHub Pages.

Here is why, and the parts of the migration that were actually interesting.

Why static

The honest reason is maintenance. A server that runs my personal website is a server I have to keep patched, monitor, and pay for. For a site with no logins, no forms that hit a database, and content that changes a few times a year, that is all cost and no benefit.

A static build flips it around: the output is plain HTML, it’s free to host, it’s fast because there’s nothing to compute per request, and there is no runtime to attack or break. The trade-off is that there’s no admin UI anymore — I write Markdown in the repo and git push. For a developer, that’s not a downside. It’s the nicer workflow.

The content was already half-migrated

The lucky part: my content already lived as flat files. The Filament setup imported pages and posts from YAML, so the posts were never truly trapped in the database. Moving them into Astro’s content collections was mostly a mechanical conversion — YAML front matter plus the Markdown body becomes an .mdx file per post, validated by a small Zod schema. That schema is worth its weight: a typo in a date or a missing excerpt fails the build instead of shipping broken.

The Laravel-isms that needed replacing

A few things were doing real work in the old stack and needed a static equivalent:

  • Syntax highlighting. I was using Torchlight, which sends code to an API at build time. Astro ships Shiki built in, so highlighting now happens locally with no external call and no token to manage. I kept the copy-to-clipboard button as a tiny client script.
  • The CV PDF. The old site generated it on demand with DomPDF. On a static site there’s no “on demand”, so I render a chrome-less /cv/print page and let Playwright print it to a PDF at build time. Same source of truth (one YAML file) drives both the web CV and the PDF.
  • Email obfuscation. The contact and legal pages used a Blade trick to assemble my address from data- attributes so scrapers don’t get a clean mailto:. That became a small Astro component doing the same thing.
  • RSS, sitemap, and structured data all carried over — Astro has first-party integrations for the first two, and the JSON-LD is just a script tag in the layout.

The bleeding-edge tax

I built this on Astro 7 (beta) with Vite 8, which was mostly smooth — but not free. The one thing that bit me was astro-expressive-code: at the time it didn’t declare support for Astro 7, so it wouldn’t install. Rather than force it, I dropped it and used the built-in Shiki highlighter plus a few lines of CSS for the copy button. Lesson as old as time: on a beta toolchain, every integration is a dependency you’re betting on, so keep the bets small.

The deploy gotcha that cost me an hour

GitHub Pages with a custom domain has one ordering trap. I set the custom domain before DNS was pointing at GitHub. GitHub tried to verify the domain, failed (DNS still pointed elsewhere), and quietly never queued a TLS certificate. The site served fine over HTTP but presented the wrong *.github.io certificate over HTTPS.

The fix was simply to point DNS at GitHub’s A records first, then re-add the custom domain so verification passes and Let’s Encrypt issues the cert. Once it shows up as approved, enable “Enforce HTTPS” and you get the HTTP→HTTPS redirect for free. Order matters: DNS first, domain second.

What I gave up

To be fair about the trade-offs:

  • No admin UI. Editing is Markdown in git. I prefer it, but it’s a real change.
  • GitHub Pages can’t set custom HTTP headers, so there’s no CSP or HSTS layer. For a static personal site I’m comfortable with that; if I needed headers I’d move to Cloudflare Pages or Netlify, which serve the same dist/ with a config file for headers.

Was it worth it

Yes. The site is faster, it costs nothing to run, there’s nothing to keep patched, and writing a post is now “add a Markdown file and push.” That last part matters most — the lower the friction to publish, the more likely I am to actually do it.

Next on the list: figuring out scheduled publishing, so I can write posts ahead of time and have them go live on their own. More on that soon.