Astro 5 vs Next.js 15: Best for Content Sites 2026
Two frameworks are competing for the content-site crown in 2026, and they've made fundamentally different bets. Astro bet on shipping zero JavaScript by default and treating content as a first-class primitive. Next.js bet on React Server Components bringing server-side rendering to the component level. For blogs, documentation, marketing sites, and content-heavy applications, these bets produce measurably different outcomes.
We benchmarked both on the same content workloads. The performance gap is real, the right choice depends on your content-to-interactivity ratio, and there's a specific category of site where Next.js 15 is the objectively better call.
TL;DR
Content-first sites (blogs, docs, marketing, portfolios): Astro 5 — 40% faster Time to Interactive, Lighthouse scores of 95-100 vs 85-95, build times 3x faster for 1,000-page sites, and hosting costs that are literally a fraction (static files vs server compute). Full-stack web applications that happen to have content pages: Next.js 15 — React 19, authentication, API routes, database connections, and the full RSC model mean you can build a complete product in one framework. The mistake is using Next.js for a blog, or Astro for a SaaS product.
Key Takeaways
- Astro Lighthouse: 95-100 — Next.js 15: 85-95 out of the box for content pages
- Build speed (1,000-page docs): Astro ~18 seconds vs Next.js ~52 seconds — 3x faster
- Zero-JS default: Astro ships no JavaScript unless you explicitly add interactive components; Next.js always includes the React runtime
- Astro 5 adds Server Islands — dynamic personalized content can hydrate individual components while the rest of the page stays fully static
- Cloudflare and Astro have strengthened their integration story; Cloudflare Workers + Astro is a major deployment target
- Next.js 15 adds partial prerendering — static shell + streaming dynamic content, closing the performance gap for mixed pages
- For developer experience on React teams: Next.js wins if your team knows React; switching to Astro means learning a new mental model
At a Glance
| Astro 5 | Next.js 15 | |
|---|---|---|
| JavaScript shipped (content page) | 0KB (default) | ~87KB (React runtime) |
| Lighthouse score (content) | 95–100 | 85–95 |
| Build speed (1k pages) | ~18s | ~52s |
| Hosting model | Static files (CDN) | Server compute needed |
| React support | Via integration | Native |
| TypeScript | Native | Native |
| MDX support | ✅ Native | ✅ Via plugin |
| Content collections | ✅ (Content Layer API) | ❌ (DIY) |
| Image optimization | ✅ | ✅ |
| Auth/database | ❌ (Astro-only) | ✅ Full-stack |
| API routes | ✅ (endpoints) | ✅ (route handlers) |
| Server Islands | ✅ (Astro 5) | Via partial prerendering |
| Framework-agnostic | ✅ React/Vue/Svelte/Solid | ❌ React-only |
Why Content Sites Have Different Requirements
The core difference comes down to what a content page actually needs to do. A blog post needs to load text and images fast. It does not need React reconciliation, component hydration, or a client-side router for users who immediately scroll and leave.
Typical blog post (Astro 5):
HTML document: 12KB
CSS: 8KB
JavaScript: 0KB (no interactive components)
Images: optimized per viewport
Total blocking: 0 JS bytes
Time to Interactive: same as First Contentful Paint
Typical blog post (Next.js 15, App Router):
HTML document: 14KB (server-rendered)
CSS: 12KB
React runtime: 38KB min+gzip
Next.js client: 49KB min+gzip
JavaScript: ~87KB total
Time to Interactive: delayed by JS parse + hydration
That 87KB of JavaScript isn't doing anything for a reader who wants to read an article. It's framework overhead that every content page pays, whether or not there's any interactivity.
Astro's island architecture means you pay the JS cost only for the components that need it:
---
// src/pages/blog/[slug].astro
import { getEntry } from "astro:content";
import NewsletterSignup from "../components/NewsletterSignup.tsx";
const { slug } = Astro.params;
const post = await getEntry("blog", slug);
---
<article>
<!-- Static HTML — zero JavaScript -->
<h1>{post.data.title}</h1>
<div set:html={post.body} />
<!-- This React component IS interactive — ships its JS -->
<!-- Only this component's JavaScript is sent to the browser -->
<NewsletterSignup client:visible />
</article>
The client:visible directive tells Astro to hydrate the React component only when it enters the viewport. Everything else is static HTML with no client-side overhead.
Astro 5's Content Layer API
Astro 5's headline feature is the Content Layer API — a redesigned system for managing content collections with loader support for any data source:
// src/content/config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
export const collections = {
// Local markdown files
blog: defineCollection({
loader: glob({ pattern: "**/*.mdx", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
author: z.string(),
tags: z.array(z.string()).optional(),
}),
}),
// Remote CMS data
products: defineCollection({
loader: async () => {
const res = await fetch("https://api.yourstorefront.com/products");
const data = await res.json();
return data.products; // Array of objects with id field
},
schema: z.object({
name: z.string(),
price: z.number(),
slug: z.string(),
}),
}),
};
Any data source — local files, headless CMS, database, remote API — becomes a typed collection. The schema is validated at build time, not runtime:
// Build error if data doesn't match schema — not a runtime 500
// "src/content/blog/post.mdx: Expected string, received number at 'date'"
This catches content errors before deployment rather than in production.
Astro 5's Server Islands
The classic problem with static sites: personalization requires dynamic content, which historically meant either full SSR (losing the static performance) or client-side fetching (FOUC and CLS issues).
Server Islands solve this by making individual components server-rendered on-demand:
---
import ProductCard from "../components/ProductCard.astro";
import { getUserRecommendations } from "../lib/ml";
---
<!-- Static content — cached, instant, no server needed -->
<main>
<h1>Shop Our Products</h1>
<StaticProductGrid />
<!-- Server Island — renders on each request, gets personalized data -->
<!-- The rest of the page is fully static -->
<ProductCard server:defer>
<span slot="fallback">Loading recommendations...</span>
</ProductCard>
</main>
// ProductCard.astro — renders on the server per request
---
const userId = Astro.cookies.get("session")?.value;
const recommendations = await getUserRecommendations(userId);
---
<div class="recommendations">
{recommendations.map(p => <Product item={p} />)}
</div>
The page loads instantly as static HTML, then the Server Island component makes a separate server request to inject personalized content. No full SSR, no client-side data fetching with loading states — the dynamic content arrives server-rendered and slots into the page.
Next.js 15's Approach: Partial Prerendering
Next.js 15 takes a different angle on the same problem with Partial Prerendering (PPR):
// app/blog/[slug]/page.tsx
import { Suspense } from "react";
import { unstable_noStore as noStore } from "next/cache";
// Static part — prerendered at build time
export default async function BlogPost({ params }) {
const post = await getPost(params.slug); // Cached, statically generated
return (
<article>
<h1>{post.title}</h1>
<MDX content={post.content} />
{/* Dynamic part — streamed on request */}
<Suspense fallback={<CommentSkeleton />}>
<Comments postId={post.id} />
</Suspense>
</article>
);
}
// Comments component — always dynamic
async function Comments({ postId }) {
noStore(); // Opt out of caching
const comments = await getComments(postId); // Fresh per request
return <CommentList comments={comments} />;
}
PPR sends a static shell immediately (the article content), then streams in the dynamic parts (comments, user-specific content) as they resolve. The architecture is different from Server Islands but solves the same problem: mixing static and dynamic content efficiently.
Build Performance
For sites with hundreds or thousands of pages, build time is a developer experience issue that compounds with every deploy:
Benchmark: 1,000-page documentation site
Astro (Starlight): ~18 seconds
Next.js (Nextra): ~52 seconds
Difference: 3x faster
Benchmark: 5,000-page content site
Astro: ~65 seconds
Next.js: ~190 seconds
Difference: ~3x faster
Benchmark: Incremental rebuild (1 page changed)
Astro: ~3 seconds
Next.js: ~8 seconds
The gap is consistent across scales and narrows only when a significant proportion of the site requires dynamic rendering (which typically favors Next.js anyway).
Hosting Cost Reality
Astro static sites can be hosted on Cloudflare Pages, Netlify, or Vercel's free tier indefinitely — they're just files on a CDN.
Traffic: 100,000 page views/month
Astro (static, Cloudflare Pages):
Cost: $0 (free tier, no server compute)
Latency: edge-cached globally
Next.js with SSR (Vercel):
Cost: ~$20-40/month (function invocations + compute)
Latency: server region + network
Next.js fully static (no dynamic features):
Cost: $0 on Vercel free tier
Note: loses most Next.js advantages over Astro
For content sites that don't need SSR, running Next.js with SSR enabled is paying server compute for features you're not using.
When to Use Each
Choose Astro 5 when:
- Primary use case: blog, documentation, marketing site, portfolio
- Core metric is Lighthouse score and Core Web Vitals (SEO-critical)
- Content is mostly static with isolated interactive widgets
- Deploying to Cloudflare Workers or edge CDN
- Team wants framework-agnostic component islands (mix React + Svelte + Vue)
- Hosting cost matters at scale
Choose Next.js 15 when:
- Building a SaaS application with content pages (not a content site with app features)
- Needs: authentication, user accounts, database connections, API routes, real-time features
- Team is React-fluent and doesn't want to learn Astro's mental model
- Using Vercel's platform features (Analytics, Flags, ISR, Edge Config)
- App has high interactivity — the zero-JS default of Astro becomes a configuration burden
- You need React Server Components for data-fetching patterns
Compare Astro and Next.js download trends at PkgPulse.
Related: Astro vs SvelteKit 2026 · Next.js vs Remix 2026 · Gatsby vs Astro 2026
See the live comparison
View astro vs. nextjs on PkgPulse →