
Migrating a Static Site from Eleventy to Astro
We rebuilt this website on Astro. Eleventy served us well, and nothing was broken, but our other client projects already run on Astro, and one toolset for every static site means less context switching and shared components.
A framework migration is only a success if visitors and search engines don't notice it. Here is the process we followed.
1. Snapshot the Old Build First
Before touching any code, we built the existing site into a separate folder. That snapshot became the reference for everything that followed: every page, the feed, the sitemap, and the 404 page.
npx eleventy --output=../site-before
2. Keep Every URL
Broken URLs cost rankings and backlinks. Astro's file-based routing made it straightforward to match the old structure exactly:
src/pages/blog/[year]/[month]/[day]/[slug].astro -> /blog/2026/11/12/<slug>/
src/pages/services/[slug].astro -> /services/<slug>/
src/pages/feed.xml.ts -> /feed.xml
src/pages/404.astro -> /404.html
Setting build.format: 'directory' keeps the trailing-slash URLs we already had.
3. Treat Content as Data
Instead of re-rendering Markdown with a different parser (and risking small changes in output), we stored each post as HTML with a YAML header and rendered it as-is. The page shell, SEO tags, and structured data live in one Astro layout.
const postFiles = import.meta.glob('../content/posts/*.html', {
query: '?raw', import: 'default', eager: true,
});
4. Port Templates, Not Just Pages
Nunjucks includes became Astro components: header, footer, breadcrumbs, and icons. Filters such as date formatting and excerpt truncation became small TypeScript functions. One gotcha: Astro treats curly braces in markup as expressions, so raw code samples with { and } go through set:html.
5. Diff the Builds
With both builds on disk, a short script compared every page: visible text, links, images, meta tags, and the parsed JSON-LD. The diff surfaced one old template bug (service pages showed the wrong name in their breadcrumb), which we fixed on purpose. Everything else matched.
6. Switch Hosting Carefully
We moved deployment to Cloudflare Pages connected to the Git repository, so every push builds automatically and every branch gets a preview URL. Two lessons from that step:
- Ship a real
404.html, or the host may answer unknown URLs with your home page and a 200 status - Move custom domains only after the new deployment is verified, and do it at a quiet time
Was It Worth It?
For visitors nothing changed, which was the goal. For us, the site now shares tooling with our other projects, components are typed, and adding a page is a single file.
Thinking about moving your site to a new stack without losing traffic? Let's talk.