Astro for Content-Heavy Sites: Islands, Partial Hydration, and When to Choose It Over Next.js
Why Astro's zero-JS-by-default architecture outperforms Next.js for marketing sites and docs. Islands architecture, hydration strategy, and the trade-offs explained.
Astro for Content-Heavy Sites: Islands, Partial Hydration, and When to Choose It Over Next.js
If you've been building content-heavy sites on Next.js for a while, you've probably felt the friction. A marketing site or documentation portal doesn't need a 300 kB JavaScript bundle hydrating the entire page on load — but that's often what you end up shipping anyway, because Next.js makes it easy to reach for client-side interactivity and hard to put it back in the box once you have.
Astro takes the opposite stance. It ships zero JavaScript by default and makes you opt in to interactivity. That philosophy is pragmatically correct for a large class of problems.
This article walks through how Astro's architecture works, when it genuinely outperforms Next.js, and when you should stay with Next.js instead.
What "Islands Architecture" Actually Means
The term "component island" was first coined by Etsy's frontend architect Katie Sylor-Miller in 2019. Jason Miller — creator of Preact — then expanded and documented the pattern in a widely-read post published in August 2020. Astro's documentation credits both: Sylor-Miller for the original concept, Miller for formalising it.
The core idea is deceptively simple: render HTML pages on the server, and inject placeholders around highly dynamic regions. Those placeholders contain server-rendered HTML from their corresponding widget. They denote regions that can be "hydrated" on the client into small self-contained widgets, reusing their server-rendered initial HTML.
In Astro's implementation, every component is server-rendered to HTML by default. If you want a React, Svelte, Vue, or Solid component to become interactive in the browser, you add a client:* directive:
ArticleBody ships as pure HTML. SearchBox hydrates immediately on load. Nothing else on the page touches JavaScript. That's the mental model: HTML by default, hydration by exception.
The Four Hydration Directives Worth Knowing
| Directive | When it hydrates | Best for |
|---|---|---|
client:load | Immediately on page load | Critical interactive UI (search, nav) |
client:idle | When the browser is idle (requestIdleCallback) | Non-urgent widgets (cookie banners, chat) |
client:visible | When the component enters the viewport | Below-the-fold interactive content |
client:media | When a CSS media query matches | Mobile-only or desktop-only UI |
For a documentation site, a sidebar table-of-contents might use client:visible. A cookie consent banner might use client:idle. A critical search input gets client:load. You're making deliberate, per-component decisions rather than hydrating everything.
Each island is fully isolated — a performance problem in one island doesn't cascade to others. Unlike progressive hydration in a React SPA (which still requires top-down rendering from a root component), Astro's islands are genuinely independent units.
Client Islands vs Server Islands
Astro ships two distinct types of islands, and the distinction matters for architecture decisions:
Client islands are interactive JavaScript UI components that hydrate separately from the rest of the page. This is what the client:* directives control. The component runs in the browser.
Server islands (introduced in Astro 4.12 as experimental, stabilised in Astro 5) are components that server-render their dynamic content separately from the rest of the page at request time — without requiring a full SSR mode for the whole site. The key use case: you want a mostly-static cached page, but one region needs to be personalised per user (a logged-in avatar, a "recommended for you" section, or live pricing).
The server:defer directive tells Astro to render the rest of the page statically and inject the server component's output separately. This is a meaningful middle ground that simply doesn't exist in the Next.js model — in Next.js, the unit of caching is the route, not the component.
The Real Performance Numbers
The performance gap between Astro and Next.js on content sites is measurable, not theoretical. In independent testing of identical blogs (roughly 30 posts, code highlighting, images), the differences were consistent:
| Metric | Next.js (SSG) | Astro |
|---|---|---|
| Homepage JS (gzipped) | ~85 KB | ~8 KB |
| Lighthouse Performance | 88 | 100 |
| First Contentful Paint | 1.0–1.5 s | ~0.5 s |
| Build time (1,000-page site) | ~52 s | ~18 s |
The JS difference is architectural. Even Next.js with static export (output: 'export') bundles the React runtime, hydration logic, and client-side router. Astro, by default, renders pure HTML — the JS payload only grows when you explicitly add client:* directives.
On slow mobile networks the gap widens further. Lighthouse's Slow 4G simulation typically keeps Astro above 95; the same Next.js SSG site drops towards 75. For content sites where a significant share of traffic comes from mobile, that's a meaningful difference in user experience and Core Web Vitals scores.
Content Collections: Astro's Answer to Type-Safe CMS Integration
One of Astro's least-discussed but most practically useful features for content sites is content collections (available since Astro 2.0). A content collection is a set of related, structurally identical data — blog posts, documentation pages, product descriptions — that Astro loads, queries, and types automatically.
You define a schema using Zod:
Astro then validates every Markdown or MDX file in src/content/blog/ against that schema at build time. If a post is missing a title or has a malformed date, the build fails with a clear error — not a runtime exception in production. You also get full TypeScript autocomplete when querying collections:
This is equivalent to what you'd build manually in a Next.js project with gray-matter, zod, and a wrapper utility — except it's built in, zero-config, and handles the edge cases for you. If you're pulling from a headless CMS, community-built loaders exist for Contentful, Sanity, Storyblok, and others, or you can write a custom loader that fetches from any API.
Where Astro Genuinely Wins Over Next.js
Pure content sites with occasional interactivity. Blogs, documentation, marketing pages, knowledge bases, changelog sites — anywhere the read-to-write ratio is heavily skewed toward reading. Astro's static output is fast, the HTML is clean, and Core Web Vitals scores tend to be strong because you're not paying the hydration tax on content that never changes.
Multi-framework teams or migrations. Astro is framework-agnostic. You can render a React component alongside a Svelte component on the same page. If you're migrating an older codebase piecemeal, that flexibility is genuinely useful rather than a novelty.
Simpler deployment story for static output. astro build produces a dist/ directory of static assets you can drop onto Cloudflare Pages, an S3 bucket, or any CDN. No Node.js server required unless you enable SSR. Serving static files from the edge is hard to beat at scale.
Documentation at scale. Astro's Starlight theme is purpose-built for documentation sites and ships with search, i18n, versioning, and accessibility baked in. Build times for a 1,000-page documentation site run roughly 3× faster than the equivalent Next.js setup. Real-world adopters include IKEA developer documentation, NordVPN's blog, and Cloudflare's developer centre.
Where Next.js Still Has the Edge
Be honest with yourself here. If your site has:
- Personalised content on most pages — dynamic dashboards, authenticated feeds, per-user recommendations — you need server-side rendering close to a data layer, and Next.js's App Router with React Server Components is better suited for the overall architecture. (Astro's server islands can handle isolated personalised regions on an otherwise static page, but if the majority of your surface area is dynamic, you're fighting the tool.)
- Complex data fetching patterns — nested layouts that each fetch data, route-level cache invalidation, streaming responses with fine-grained Suspense boundaries. Next.js has years of production hardening and a mature cache model here. Astro's SSR mode via adapters adds a Node.js (or Deno/Bun/Cloudflare Workers) runtime, but the caching story is less opinionated — you're responsible for your own cache invalidation logic.
- A large existing React codebase. Astro can use React components, but if your entire product is React — shared state, context, complex hooks — you're adding conceptual overhead for marginal gain.
- Fine-grained ISR (Incremental Static Regeneration). Next.js lets you set
revalidateat the page level and revalidate on-demand withrevalidatePath. Astro with SSR can do request-time rendering, but the equivalent of ISR requires a third-party caching layer at the CDN or Cloudflare Worker level. Not impossible, but more manual.
A marketplace with user-specific deal feeds, authentication flows, cart state, and real-time inventory is firmly in Next.js territory. A companion documentation or blog property, though? That's exactly where Astro fits.
SSR Adapters: What You're Trading
When you need Astro to render pages at request time (rather than at build time), you add an SSR adapter. Adapters exist for Node.js, Cloudflare Workers, Vercel Edge Functions, Deno, and Netlify.
This adds a wrangler-based build output that runs on Cloudflare's edge network. The tradeoff versus Next.js SSR is worth being explicit about:
- Cold starts: Astro's edge-deployed SSR has no cold start problem on Workers (they boot in milliseconds). Node.js adapter deployments on serverless platforms behave similarly to Next.js on Vercel.
- Caching: You're opting out of Astro's best feature — prebuilt HTML. SSR Astro pages need explicit cache-control headers and CDN configuration that Next.js handles more opinionatedly via its cache layer.
- Bundle overhead: Astro SSR pages still ship minimal JS to the client. The server-side overhead is roughly comparable to any Express-style Node.js handler, but you lose the "free" static file serving that makes Astro shine.
If you're reaching for SSR adapters heavily, revisit the decision heuristic below. Heavy SSR usage is a signal that Next.js might be the more natural fit for your overall project.
A Practical Decision Heuristic
Ask yourself: what percentage of your page surface area is static at request time?
- >80% static → Astro is worth serious consideration. The build-time output and islands model will give you strong performance with minimal infrastructure.
- 50–80% static → Astro with SSR adapters or server islands can work, but weigh the caching complexity and less-opinionated server story against Next.js's maturity.
- <50% static → Stick with Next.js or a similar full-stack framework.
A secondary question: how much of the interactive surface is truly critical at load time? If most of your JavaScript is analytics, cookie consent, and a single search box, client:idle and client:load on those specific components will serve you better than hydrating the entire page.
Getting Started Quickly
For a documentation site specifically, Starlight is worth evaluating before building from scratch:
It ships with full-text search, i18n, dark mode, keyboard navigation, and a sensible content collection structure out of the box.
If you're fetching from a headless CMS, content collections give you type-safe access to Markdown, MDX, Markdoc, or remote API data with minimal boilerplate. Define a Zod schema, point Astro at your content source, and you have validation and TypeScript types for free.
Migrating from Next.js: What to Expect
A weekend spike is realistic for a straightforward content site. The migration path generally looks like:
- Audit your pages: Identify what percentage is truly static. Any
getServerSidePropspages that need personalisation require a decision — server island, SSR route, or client-side fetch after load. - Replace
pages/withsrc/pages/: Astro uses file-based routing with.astrofiles. React components slot in withclient:*directives where needed. - Convert data fetching:
getStaticPropsbecomes frontmatter or a content collection. Anything hitting an API at build time moves to the component's frontmatter block. - Check your images: Astro has a built-in
<Image />component with optimisation similar tonext/image. - Validate performance: Run Lighthouse before and after. The improvement on content-heavy pages is usually immediate and substantial.
The largest friction point is typically shared state across islands — if you've been relying on React Context to pass data between components, you'll need to move that to a client-side store (nanostores is the community default for Astro) or restructure so the data flows from the server.
The Bottom Line
Astro for content-heavy sites is a clear architectural win. The islands model isn't a gimmick — it's a sensible default that forces you to justify every byte of JavaScript you ship. For content sites, that constraint produces better outcomes: smaller bundles, stronger Core Web Vitals, faster builds, and a simpler deployment story.
If your use case fits, the migration path from a Next.js static site is straightforward enough to be worth a weekend spike. Just don't let the framework switch distract you from shipping actual content.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.