Skip to main content

Astro 5 vs Next.js 15: Best for Content Sites 2026

·PkgPulse Team

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 5Next.js 15
JavaScript shipped (content page)0KB (default)~87KB (React runtime)
Lighthouse score (content)95–10085–95
Build speed (1k pages)~18s~52s
Hosting modelStatic files (CDN)Server compute needed
React supportVia integrationNative
TypeScriptNativeNative
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

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.