Astro 6 Review: What's New, What Breaks, and When to Upgrade
Astro 6 is worth upgrading to. Node 22 is required, legacy Content Collections are gone, Zod 4 silently breaks schemas. Here is exactly what breaks.
By Ethan
1,676 words · 9 min read
Astro 6 ships a redesigned dev server, a built-in Fonts API, and stable Live Content Collections. The real cost is in the constraints it removes: Node 22 is now the floor, every legacy Content Collections API is gone, and Zod 4 will silently break schemas that relied on type coercion. If you run an Astro 4 or 5 site today, the upgrade is probably worth it — but you should know what you are walking into.
Who this is for
Developers already shipping Astro 4 or 5 sites who want to know whether to upgrade now or wait. This is not a “what is Astro” explainer — it assumes you already know the tool.
What we tested
Astro 6.3.1 running on Node 24.12.0 with pnpm 10.33.0, on a content-collections site (toolchew.com — 732 pages, Markdown-first, Tailwind 4 via Vite plugin, no UI framework islands). Build numbers below come from three warm runs after one cold-start discarded. Hardware: Apple M-series MacBook.
New features that actually matter
Production-parity dev server
The biggest quality-of-life change in Astro 6 is invisible during development — until it saves you from a prod-only bug. The redesigned astro dev now runs Vite’s Environment API under the hood, which means the dev server executes in the same runtime as production. The classic “it worked in dev” class of bugs shrinks substantially.
This is most impactful if you deploy to Cloudflare Workers. The rebuilt @astrojs/cloudflare adapter now runs workerd (Cloudflare’s open-source runtime) at every stage: dev, preview, and build. You get real Durable Objects, KV Namespaces, R2 Storage, and Analytics Engine bindings in astro dev — no polyfills, no surprises at deploy time. For a step-by-step Cloudflare deployment walkthrough, see How to Deploy Astro to Cloudflare.
Built-in Fonts API
Font self-hosting is now first-class. Astro 6 handles file downloading, caching, fallback generation, and <link rel="preload"> injection automatically. Providers include Google Fonts, Fontsource, Bunny, and locally installed npm packages.
// astro.config.mjs
export default defineConfig({
fonts: [{
provider: 'google',
name: 'Inter',
styles: ['normal'],
weights: [400, 700]
}]
})
If you had experimental.fonts: true in Astro 5, remove it — this is now stable and the flag will cause a config error.
Content Security Policy API
CSP is now stable — it works across static and SSR modes, all official adapters. Inline <script> and <style> tags get automatically hashed.
export default defineConfig({
security: { csp: true }
})
Remove any experimental.csp flag from existing configs.
Live Content Collections
Stable as of Astro 6 (was experimental in 5.10). getLiveCollection() and getLiveEntry() fetch content at request time, so CMS updates appear without a rebuild. Useful for inventory, gated content, or any source that changes faster than your deploy cadence. For mostly-static sites it adds latency without benefit — stick to build-time collections.
Experimental: queued rendering
The official claim is “up to 2× faster rendering.” The new algorithm replaces the recursive renderer with a depth-first queue and reuses component instances across pages via node pooling (1,000 nodes by default).
export default defineConfig({
experimental: {
queuedRendering: { enabled: true }
}
})
We ran astro build on toolchew.com (732 pages, content collections, no islands) with and without the flag on Astro 6.3.1:
| Run | Without queued rendering | With queued rendering |
|---|---|---|
| 1 | 3.21s | 6.81s (*) |
| 2 | 2.99s | 3.03s |
| 3 | 3.07s | 3.00s |
| 4 | 3.08s | 3.03s |
| Median (runs 2–4) | 3.07s | 3.03s |
(*) Run 1 discarded — cold-start variance affected both conditions identically.
Conclusion: no measurable improvement on this workload. The “2×” claim may hold for component-heavy apps with deeply nested .astro component trees. On a Markdown-first content site, the bottleneck is disk I/O and Vite transform, not the rendering algorithm. Enable it if you have lots of component nesting; skip it if your site is mostly Markdown.
Astro 6 breaking changes
Node 22 is the new floor
Node 18 and Node 20 are no longer supported. The minimum is Node 22.12.0. Check your deployment targets before upgrading.
Netlify and Vercel both support Node 22 as of early 2026. Cloudflare Pages supports it. Shared hosting and some older CI templates may still default to Node 18 — update them first or the upgrade will stop at deploy.
Removed APIs
The full list is in the official v6 migration guide, but these are the ones most likely to hit you:
| Removed | Replacement |
|---|---|
<ViewTransitions /> | <ClientRouter /> from astro:transitions |
Astro.glob() | import.meta.glob() or getCollection() |
legacy.collections flag | Content Layer API with loaders |
src/content/config.ts | src/content.config.ts |
serializeActionResult() | getActionContext() |
astro.config.cjs / .cts | Use .mjs, .js, .ts, or .mts |
If you deferred the Astro.glob() migration from Astro 5, there is no more deferring. Run astro check before upgrading — it will surface every removed API in your project.
Zod 4 migration
Astro 6 ships Zod 4. The API changes are non-trivial and several break silently.
// Error messages
{ message: "bad input" } // Zod 3 — now ignored at runtime
{ error: "bad input" } // Zod 4 — correct
// Defaults with transforms
views: z.string().transform(Number).default("0") // Zod 3 — silent failure
views: z.string().transform(Number).default(0) // Zod 4 — correct
// Import path
import { z } from 'astro:schema' // deprecated
import { z } from 'astro/zod' // correct
A community codemod is available at github.com/nicoespeon/zod-v3-to-v4. Run it before the upgrade, not after you’re already debugging mysterious schema failures.
i18n routing default change
i18n.routing.redirectToDefaultLocale changed default from true to false. If your site depends on automatic locale redirects, you need to add it back explicitly:
i18n: {
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: true // was implicit in v5; now must be set
}
}
Heading IDs change (silent link breakage)
Astro 6 stops stripping trailing hyphens from heading IDs generated from code-formatted headings. A heading like ## `<Picture />` used to generate #picture — it now generates #picture-. Any anchor links to headings containing inline code or special characters will silently break after upgrading. Audit your anchor links before going live.
import.meta.env is always a string
Environment variables are now always string-inlined and never coerced. Boolean env vars are now "true" or "false" as strings.
// v5 — worked
const enabled: boolean = import.meta.env.ENABLED
// v6 — must coerce explicitly
const enabled = import.meta.env.ENABLED === "true"
// Private vars must use process.env
const dbPassword = process.env.DB_PASSWORD
Integration compatibility
Tailwind 4: drop @astrojs/tailwind
The @astrojs/tailwind integration is deprecated. Tailwind 4 integrates directly via its Vite plugin — no Astro-specific package needed. The toolchew site already runs this way:
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
})
That is the entire Tailwind configuration. No tailwind.config.js, no @astrojs/tailwind in the integrations array.
MDX: upgrade to 5.1.0+ before running npm install
@astrojs/[email protected] shipped with a peer dependency pinned to the alpha range of Astro 6 (^6.0.0-alpha.0). npm’s semver strictly excludes stable releases from pre-release ranges, so a fresh npm install on an Astro 6.0.4 project with MDX 5.0.0 throws:
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! Found: [email protected]
npm ERR! Could not resolve dependency:
npm ERR! peer astro@"^6.0.0-alpha.0" from @astrojs/[email protected]
This is easy to confuse for an Astro 6 bug. It is a packaging issue in MDX, not in Astro. The fix is to upgrade MDX to @astrojs/[email protected]+ before running any install. If you are stuck on 5.0.0 for any reason, add an npm override:
"overrides": { "@astrojs/mdx": { "astro": "^6.0.0" } }
See GitHub issue #15924 for the full thread.
React, Svelte, Vue
No reported breaking changes in UI framework integrations. They pick up the unified dev server silently — components now render in the same runtime context as production in astro dev.
Security note
Versions before 6.1.6 had a moderate XSS vulnerability via incomplete </script> sanitization in define:vars. Current stable (6.3.1 as of writing) is safe. Do not run anything below 6.1.6 in production.
Verdict
Upgrade now if:
- You are deploying to Cloudflare Workers — the unified
workerddev runtime is the strongest reason to move. - You are starting a new project — no migration burden, and you get CSP and the Fonts API out of the box.
- Your site is blog + content collections with minimal custom schema logic — the migration is 1–3 hours, dominated by the Node 22 deploy target check.
If you are still weighing Astro against Next.js, our Next.js vs Astro comparison covers the framework-level trade-offs that Astro 6 does not change.
Wait if:
- You have an MDX-heavy site on
@astrojs/[email protected]exactly — upgrade MDX first, confirm 5.1.0+ installs cleanly, then migrate Astro. - You have heavily customized Zod schemas — run the codemod on a branch and fix all failures before touching Astro’s version.
- You have i18n routing and haven’t audited the
redirectToDefaultLocaledefault change — 10 minutes now versus a silent redirect failure in production.
Skip experimental.queuedRendering unless you have measured a specific rendering bottleneck on a component-heavy project. On content-collection sites, the overhead is not there.
Caveats
Build numbers above come from a single machine (Apple M-series) running Node 24 on a content-heavy site with no UI framework islands. The queued rendering result may differ on component-heavy apps — the official claim was based on apps with deep component trees, not on Markdown pipelines. Benchmark your own project before making decisions based on performance numbers.
We did not test the Rust compiler (experimental.rustCompiler) — it remains early and the team does not recommend it for production yet.