Migrating My Blog to Astro 6
Astro 6 just dropped and I couldn’t resist upgrading my blog right away. I’ve been running on Astro 5.16 for a while and the release notes had a few features that caught my eye immediately. Let me walk you through the migration, what stood out, and the features I’m most excited about.
TL;DR
- Migrated from Astro 5.16 to Astro 6 in about an hour.
- The biggest wins for me: first-class Cloudflare support (migrated from Pages to Workers and consolidated a standalone likes API into the main codebase) and the built-in Fonts API (goodbye render-blocking Google Fonts imports).
- Live Content Collections are now stable, bringing request-time content fetching to Astro’s content layer.
- Zod now imports from
astro/zodinstead ofastro:content. - Enabled experimental queued rendering for faster builds.
- Zero errors on the first build. Smoothest major version upgrade I’ve done with Astro.
The Features I’m Loving
First-Class Cloudflare Support
This is the big one for me. I was previously deploying my blog to Cloudflare Pages with the old @astrojs/cloudflare adapter. It worked, but the dev experience had rough edges: the adapter didn’t run workerd locally, so I was always testing against Node.js and hoping nothing broke in production. I also had a per-post like counter running as a separate Worker with its own wrangler config and deployment pipeline. It was a small API, but maintaining it as a standalone project just to increment a number felt like overkill.
With Astro 6, the rebuilt adapter now runs workerd at every stage: dev, prerender, and production. That gave me the confidence to migrate the blog from Pages to Workers. I consolidated that standalone likes API directly into the Astro project. One codebase, one deployment, full access to bindings during development. It’s the setup I always wanted.
Built-in Fonts API
This is the other feature I was really excited about. Before Astro 6, I was loading Inter and JetBrains Mono via Google Fonts @import in my CSS. This is render-blocking, sends user data to Google, and honestly just feels wrong for a static site that cares about performance.
Now I just configure fonts directly in astro.config.ts:
import { defineConfig, fontProviders } from "astro/config";
export default defineConfig({
fonts: [
{
provider: fontProviders.google(),
name: "Inter",
cssVariable: "--font-inter",
weights: [400, 500, 600, 700],
styles: ["normal"],
fallbacks: ["sans-serif"],
},
{
provider: fontProviders.google(),
name: "JetBrains Mono",
cssVariable: "--font-jetbrains-mono",
weights: [400, 500],
styles: ["normal"],
fallbacks: ["monospace"],
},
],
});
Then add a <Font /> component in my layout’s <head>:
---
import { Font } from "astro:assets";
---
<head>
<Font cssVariable="--font-inter" preload />
<Font cssVariable="--font-jetbrains-mono" />
</head>
Astro downloads the fonts at build time, generates optimized fallbacks, and serves them from my domain. No more third-party requests. No more render-blocking imports. The fonts are just there, and they’re fast.
Experimental Queued Rendering
My blog has 25+ posts with OG image generation for each one, so build performance matters. The new queued rendering replaces Astro’s recursive rendering with a two-pass approach: first traverse the component tree, then render it in order. Early benchmarks show up to 2x faster rendering.
Enabling it is just a config flag:
export default defineConfig({
experimental: {
queuedRendering: {
enabled: true,
},
},
});
I’ve enabled this and I’m curious to see how it performs as I add more content.
Live Content Collections
Live Content Collections are now stable in Astro 6. Content collections have always required a rebuild when content changed. Live collections flip that: they fetch content at request time using defineLiveCollection() and getLiveEntry(), with no rebuild needed. Your content updates the moment it’s published.
You define a live collection in src/live.config.ts:
import { defineLiveCollection } from "astro:content";
import { z } from "astro/zod";
import { cmsLoader } from "./loaders/my-cms";
const updates = defineLiveCollection({
loader: cmsLoader({ apiKey: process.env.MY_API_KEY }),
schema: z.object({
slug: z.string(),
title: z.string(),
excerpt: z.string(),
publishedAt: z.coerce.date(),
}),
});
export const collections = { updates };
Then query it in your page with built-in error handling:
---
import { getLiveEntry } from "astro:content";
const { entry: update, error } = await getLiveEntry("updates", Astro.params.slug);
if (error || !update) {
return Astro.redirect("/404");
}
---
<h1>{update.data.title}</h1>
<p>{update.data.excerpt}</p>
<time>{update.data.publishedAt.toDateString()}</time>
I’m not using this yet since my blog posts are all Markdown files in the repo, but now that I’m running on Workers with full binding access, I can see pairing this with a CMS or D1-backed content source down the line. The fact that live and build-time collections use the same APIs (getCollection(), getEntry(), schemas, loaders) makes it easy to adopt incrementally.
Zod 4
The Zod upgrade is mostly invisible if your schemas are straightforward. The main change is where you import it from. Instead of importing z from astro:content, you now import it from astro/zod:
import { defineCollection } from "astro:content";
import { z } from "astro/zod";
If you’re using .default() with .transform(), check the Zod 4 changelog because the behavior around default values changed.
How I Migrated
The actual migration took about an hour. Here’s the rough process:
- Created a branch. Always migrate on a separate branch.
- Ran
pnpm dlx @astrojs/upgrade. This handles the package version bumps automatically. - Updated Zod imports.
zfromastro/zodinstead ofastro:content. - Migrated fonts. Removed Google Fonts
@import, configured the new Fonts API, added<Font />to my layout. - Migrated from Pages to Workers. Switched to the rebuilt
@astrojs/cloudflareadapter and consolidated my standalone likes API Worker into the Astro project. - Enabled experimental features. Queued rendering for faster builds.
- Built and tested.
pnpm build+pnpm check, zero errors on the first try.
What I’m Planning Next
Now that I’m on Astro 6, there are a few things I want to explore:
- Live Content Collections with a CMS. Now that I’m on Workers with full binding access, I want to pair live collections with a headless CMS so content updates go live without a rebuild.
- Responsive images. Astro’s image handling keeps getting better and I’m not using
srcset/sizesanywhere yet. - View Transitions. I’ve been putting this off, but Astro’s
<ClientRouter />has matured a lot since it was introduced. - Tailwind v4. The
@astrojs/tailwindintegration works fine for now, but Tailwind v4 with its native Vite plugin is the future.
If you’re still on Astro 5, I’d recommend giving the upgrade a try. The migration path is smooth and the new features are worth it. Have you upgraded yet? I’d love to hear how it went.
Resources
Share this post on: