· astro / ssg / review

Astro 5 review — what changed and whether to upgrade

Upgrade Astro 4 now. Content Layer delivers 5× faster Markdown and 25–50% less memory. Hold if you use @astrojs/lit or need Cloudflare adapter v13+ features.

By

1,658 words · 9 min read

Upgrade Astro 4 sites now. Content Layer is the headline feature and it delivers: up to 5× faster Markdown builds, 2× faster MDX, 25–50% less memory. Server Islands and astro:env are clean additions with no complexity tax for most projects. Hold only if you use @astrojs/lit (removed with no replacement), depend on dynamic prerender env var injection, or need the Cloudflare adapter features that only landed in Astro 6. One context note: Astro 6 is now current (v6.4.4), so Astro 5 is the documented stepping-stone—still fully supported, not legacy, but not the latest.

Who this is for

Astro 4 users deciding whether to migrate a live content site. If you’re on Astro 2 or 3, this article won’t cover all the gaps—start with the full migration guide. If you’re starting a new project today, go directly to Astro 6.

How we tested

Astro 5.11.0 on an M2 MacBook Pro, Node 22. Test project: an existing Astro 4.16 content blog with 250 Markdown posts and 50 MDX posts. Build time figures come from the official Astro 5.0 and 5.3 blog posts (version-pinned to those releases). We did not run independent SSR cold-start benchmarks—no reliable published third-party comparison exists for Astro 4 vs. 5 on that dimension, and we won’t substitute guesses.

Content Layer

The biggest architectural change in Astro 5. Content Layer replaces the file-system-convention API with an explicit loader model. Collections are defined in src/content/config.ts with a loader field:

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const posts = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    publishedAt: z.coerce.date(),
  }),
});

export const collections = { posts };

The slug field is gone. Collections now use id (the file path relative to the base). If you reference post.slug anywhere in templates, change it to post.id. The migration script handles most of this—review the output before committing, because the script doesn’t always catch slug used inside MDX or dynamic route params.

Performance gains from the Astro 5.0 release post: up to 5× faster Markdown builds, 2× faster MDX, 25–50% less memory. The gains come from processing entries on demand rather than eagerly loading the entire collection at startup. On the test project (250 Markdown + 50 MDX posts), we saw similar order-of-magnitude improvements, consistent with Astro’s published figures.

Migration effort: budget an hour for a 200-post site with simple schemas, three hours if you have custom schema types and a lot of slug references spread across templates.

Server Islands

Server Islands defer server-rendered components to after the static shell loads. The API is a single directive:

---
import Cart from '../components/Cart.astro';
---
<Cart server:defer />

The component renders on the server and is injected via a fetch after the initial HTML is served. The canonical use case: personalized content (cart totals, user names, notification counts) on otherwise static pages, without hydrating a full client-side framework just for a small dynamic block.

Constraints worth knowing before you adopt it:

  • Props are URL-serialized. The limit is 2,048 bytes. When props push the URL past this limit, Astro switches to POST requests, which browsers don’t cache—defeating the purpose of static-shell serving. Design deferred components to fetch their own data rather than receive it as props.
  • Requires an adapter. Fully static deployments (no adapter configured) don’t support Server Islands.
  • Props must be serializable. Functions, class instances, and non-JSON types are out.

For content-heavy sites with a few personalized widgets, Server Islands are a clean solution. For fully static deployments, the feature doesn’t apply.

astro:env

Type-safe environment variables. You define a schema in astro.config.ts and Astro enforces it at build time:

import { envField } from 'astro/config';

export default defineConfig({
  env: {
    schema: {
      API_KEY: envField.string({ context: 'server', access: 'secret' }),
      PUBLIC_SITE_URL: envField.string({ context: 'client', access: 'public' }),
      RESULTS_PER_PAGE: envField.number({ context: 'server', access: 'public', default: 10 }),
    },
  },
});

Accessing a secret variable from client-side code is a compile error. Missing required variables throw at startup, not at runtime. Types: string, number, boolean, enum. Context: client or server. Access: public or secret.

No performance impact. Pure DX improvement. If you’ve ever had a deploy silently break because a variable wasn’t set in the preview environment, this is the fix.

Breaking changes

The breaking changes are real and will take some time. The ones that will slow you down most:

@astrojs/lit removed. Hard blocker. If you use Lit components via this integration, you cannot upgrade until a compatible alternative exists. There is no official replacement in Astro 5. Check the Astro integrations catalog for community adapters, or hold on Astro 4.

Astro.glob() deprecated. Replace with import.meta.glob() or Content Layer loaders. The old API still works in Astro 5 but will be removed in Astro 6. Fix it now to avoid doing it twice.

output: 'hybrid' removed. Static-by-default with per-page SSR opt-in is now the behavior under output: 'static'. If you had output: 'hybrid' set explicitly, remove it. The behavior is identical; the key is gone.

render() is now an imported function. Entries no longer have a .render() method—they are plain objects. Import render from astro:content and call await render(entry) instead of await entry.render(). Search for \.render\( to find every call site.

compiledContent() is now async. Add await to every compiledContent() call.

Squoosh removed. Switch to Sharp. Install sharp, uninstall squoosh. One config change in astro.config.ts if you had explicit image service configuration.

CSRF protection on by default. If you have form POST handlers, you’ll see 403s in development. Add the X-CSRF-Token header to your forms or disable CSRF protection explicitly if your threat model doesn’t require it.

Route params no longer auto-decoded. If your routes matched special characters and you relied on automatic decodeURIComponent, add it yourself.

ViewTransitionsClientRouter. One import rename. No behavior change.

Content collections slugid. Mechanical, but touches many templates. The migration script covers most of it; audit the output for MDX files and dynamic routes that reference the field directly.

The @astrojs/lit removal is the only hard blocker. Everything else is afternoon work.

Performance

Build numbers from official Astro release posts:

ChangeMeasured impact
Content Layer (Astro 5.0)Up to 5× faster Markdown, 2× faster MDX, 25–50% less memory
Sync rendering (Astro 5.3)10–15% faster static builds
Sync rendering, large component trees (Astro 5.3)4× faster SSR for 3,000+ components
General SSR (Astro 5.3)1.5–2× faster

Sources: Astro 5.0 blog, Astro 5.3 blog.

These are Astro’s own benchmarks against their content test suite, not independent measurements. The Content Layer gains are consistent with what we observed on the test project. The 5.3 sync rendering improvement is the underappreciated win—if you’re running large server-rendered component trees, 5.3 is meaningfully faster than 5.0.

Ecosystem

IntegrationStatus in Astro 5
Tailwind 4 via @tailwindcss/vite✅ Astro 5.2+; @astrojs/tailwind deprecated
Starlight✅ Astro ≥5.5.0 (latest Starlight targets Astro 6)
Markdoc (@astrojs/markdoc)✅ v0.15.x for Astro 5
Partytown✅ v2.1.7
Cloudflare adapter⚠️ v12 = Astro 5; v13 = Astro 6 (Cloudflare Pages deployment dropped in v13)
@astrojs/lit❌ Removed

The Tailwind 4 migration is the ecosystem change most likely to catch you off guard. If you’re on Tailwind 3 via @astrojs/tailwind, that integration is deprecated and won’t receive Astro 5.2+ updates. The move to @tailwindcss/vite is real migration work—Tailwind 4 uses a different configuration model (CSS-first config, no more tailwind.config.js by default).

For Cloudflare Pages users: adapter v12 works with Astro 5. Adapter v13 requires Astro 6 and drops direct Cloudflare Pages support in favor of the Workers runtime. If you’re on v12 and happy, Astro 5 is fine. If you need v13 features, go to Astro 6 directly. Our Astro Cloudflare Pages guide covers both adapter versions.

Astro 5 to 6: the upgrade path

Astro 6 is current (v6.4.4 as of June 2026). Astro 4 documentation is now marked unmaintained. Astro 5 is supported, receiving maintenance releases, but active feature development is on 6.

The practical frame: Astro 5 is the documented stepping-stone to 6. The 5→6 migration is smaller than the 4→5 migration. If you’re maintaining an Astro 4 site, upgrading to 5 now gets you the Content Layer performance gains immediately and positions you for a smaller jump to 6 at your next maintenance window. If you’re starting fresh, skip 5 and deploy on 6.

For a detailed look at what’s new in Astro 6, see our Astro 6 review.

Verdict

Upgrade now if you’re on Astro 4 and your project doesn’t use @astrojs/lit. Content Layer justifies the migration work by itself.

Hold if:

  • You use @astrojs/lit — removed, no replacement available
  • You depend on dynamic prerender env var injection — behavior changed
  • You need Cloudflare adapter v13+ features — requires Astro 6

Start on Astro 6 for any new project.

If you’re deciding between Astro and another framework entirely, our Next.js vs Astro comparison covers when each is the better fit.

Caveats

Testing covered a standard content blog. Sites with heavy client-side islands, complex SSR, or many third-party integrations may hit migration issues we didn’t encounter. The @astrojs/lit blocker is confirmed from official documentation—we didn’t test Lit components directly.

No affiliate links in this article. Cloudflare has no public affiliate program; the adapter is mentioned because it’s the most common source of upgrade friction for Cloudflare Pages users.

References