📖 11 min read

Next.js vs Astro for SEO (2026): Which Should You Choose?

A practical, no-hype comparison of Next.js and Astro for SEO — rendering, performance, i18n, and when each wins.

  • Next.js
  • Astro
  • Comparison

Both Next.js and Astro can rank perfectly well. Google indexes server-rendered HTML from either framework without complaint, and “which framework is better for SEO” is the wrong question if you read it as “which one Google secretly prefers.” The right question is narrower and more useful: given the kind of site you are building, which framework makes good SEO the path of least resistance?

This is a developer-to-developer comparison. We will look at the actual rendering pipelines, what ships to the browser by default, how Core Web Vitals shake out, and how each framework handles the unglamorous-but-decisive work of hreflang and structured data. No tribalism — just trade-offs and code.

TL;DR — the one-line conclusion

If you are building a content or marketing site (blog, docs, landing pages, programmatic SEO), Astro gives you near-zero-JS HTML out of the box and the least opportunity to accidentally tank your Core Web Vitals. If you are building an interactive application with a content layer bolted on (dashboards, SaaS with a marketing surface, e-commerce with heavy personalization), Next.js gives you one mental model for the whole app and mature SSR/ISR for pages that must be dynamic.

DimensionAstroNext.js (App Router)
Default renderingSSG (prerendered HTML)SSR / RSC (server-rendered per request)
Default JS to browser~0 KB (islands only)React runtime + RSC payload
Dynamic renderingSSR adapter, on-demandSSR, ISR, streaming, RSC — first-class
Core Web Vitals defaultExcellent (little to hydrate)Good, but easy to regress
i18nBuilt-in routing + manual hreflangi18n config (Pages) / manual (App) + hreflang
Structured dataInline in .astro templateInline in component / generateMetadata
Best forContent & marketing sitesInteractive apps with content
Learning curveLow if you know HTML/JSModerate (RSC mental model)

Neither choice is a ranking penalty. The difference is how much vigilance you need to keep your pages fast and crawlable.

🧑‍💻 Developer’s take: the framework rarely loses you rankings on its own. What loses rankings is shipping a 400 KB JS bundle to render a paragraph of text, or hydrating an entire page when you needed one interactive widget. Astro makes that mistake hard; Next.js makes it easy — but also gives you the tools to avoid it.

Rendering models

SEO starts with one question: what does the crawler get in the initial HTML response? Google does execute JavaScript, but rendering is a deferred, budgeted second wave — so content that exists only after hydration is content you are betting on a render queue to surface. (We cover that pipeline in depth in the JavaScript SEO guide.)

Astro: static-first with islands

Astro’s default is static site generation. At build time it runs your .astro components, produces complete HTML, and — this is the key part — strips the framework JavaScript unless you explicitly opt a component into hydration with a client:* directive. This is the “islands architecture”: the page is static HTML, and only the interactive bits become hydrated islands.

---
// src/pages/blog/[slug].astro — runs at build time, never ships to the client
const { slug } = Astro.params;
const post = await getPost(slug);
---
<article>
  <h1>{post.title}</h1>
  <div set:html={post.html} />
  <!-- Only this counter ships JS. The rest is inert HTML. -->
  <LikeButton client:visible postId={post.id} />
</article>

The crawler receives the fully-formed article in the first byte. There is no second render wave to wait on, because there is nothing left to render. For content, this is as good as it gets. Astro can also do SSR via an adapter (@astrojs/node, Cloudflare, Vercel) when a route genuinely needs per-request data, so “static-first” does not mean “static-only.”

Next.js: server components and per-request rendering

Next.js with the App Router defaults to React Server Components. Pages render on the server (or at build time for static routes) and send HTML to the browser, plus an RSC payload that lets React reconcile and hydrate client components. You get HTML in the initial response — good for crawling — but the page also ships the React runtime and re-hydrates on the client.

Next.js gives you a richer menu of rendering modes, and you pick per route:

  • Static (SSG)export const dynamic = 'force-static' or simply no dynamic data; prerendered at build.
  • SSR — rendered per request, for content that changes by user, geo, or time.
  • ISRexport const revalidate = 3600; serve static, regenerate in the background. Excellent for large content sites that change often but not per-request.
// app/blog/[slug]/page.tsx — Server Component, runs on the server
export const revalidate = 3600; // ISR: re-generate at most hourly

export async function generateStaticParams() {
  const posts = await getAllSlugs();
  return posts.map((slug) => ({ slug }));
}

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </article>
  );
}

💡 Tip: for SEO, the dangerous Next.js pattern is the client-side 'use client' page that fetches its main content in a useEffect. That content is invisible in the initial HTML and depends entirely on Google’s render wave. If a route’s primary content can be server-rendered, server-render it.

The practical takeaway: both deliver indexable HTML by default in their modern, recommended configurations. Astro delivers it with no client framework attached; Next.js delivers it alongside a hydration step. That single difference drives most of what follows.

Performance & Core Web Vitals

Core Web Vitals (LCP, INP, CLS) are a ranking signal, and more importantly they are a proxy for whether real users have a good time. The framework decision shows up most clearly in how much JavaScript ships and how much of it must execute before the page is interactive.

Default payload

A typical Astro content page ships only the JS for its islands — often literally zero on a pure article. A typical Next.js App Router page ships the React runtime plus the RSC flight payload plus any client component code. That is not “bloat” — it is the cost of a React app — but on a page that is 95% prose, you are paying for interactivity you do not use.

Page typeAstro typical client JSNext.js typical client JS
Static article, no widgets~0 KB~80–110 KB (runtime + RSC)
Article + one comment widget~15–30 KB (one island)~90–130 KB
Highly interactive dashboardcomparable to Next once heavily hydrated~150 KB+ (its home turf)

Numbers vary with your dependencies — measure your own build with next build output or Astro’s bundle report — but the shape is consistent: Astro starts near zero and adds JS only where you ask; Next starts with a runtime floor and grows from there.

Hydration cost and INP

LCP is mostly about getting the largest element painted fast — both frameworks do this well when they server-render. The sharper divergence is INP (Interaction to Next Paint), which punishes main-thread work. Hydrating a large React tree blocks the main thread; on low-end mobile that delays the first interaction. Astro’s islands hydrate independently and lazily (client:visible, client:idle), so the main thread stays free for longer.

⚠️ Caution: a fast framework will not save a slow site. Unoptimized hero images, render-blocking third-party scripts, and layout shift from web fonts will wreck CWV in either framework. Run a real audit (PageSpeed Insights, Lighthouse, or our Core Web Vitals guide) before blaming the framework.

Verify what actually ships with a quick check on a built page:

# Count bytes of JS the page actually requests
curl -s https://your-site.com/blog/some-post/ \
  | grep -oE 'src="[^"]+\.js"' | wc -l

# Or inspect the initial HTML the crawler sees — is the content there?
curl -s https://your-site.com/blog/some-post/ | grep -c "<h1"

Content vs app

This is where the decision usually resolves itself. Ask what your site is.

Content and marketing sites win with Astro. Blogs, documentation, changelogs, landing pages, and programmatic SEO pages (hundreds or thousands of templated URLs) are mostly text and images with occasional interactivity. Astro’s content collections give you typed frontmatter, Markdown/MDX, and zero-JS output. You get the authoring ergonomics of a CMS-lite setup with the performance of hand-written HTML.

Astro content collection — typed, build-time, zero client JS
  src/content/
    blog/        ← MDX + frontmatter schema (Zod)
    config.ts    ← defineCollection() validates every post at build

Interactive applications win with Next.js. If the core product is an app — authenticated dashboards, real-time data, complex forms, stateful flows — and SEO matters for a marketing or content surface around it, Next.js lets you build the whole thing in one framework and one deploy. You avoid running two stacks (an Astro marketing site plus a separate React app) and the routing/auth seams between them. App Router’s ISR and SSR also handle the genuinely dynamic, SEO-relevant pages (product pages with live pricing, location pages) that a pure SSG model would force you to rebuild constantly.

The grey zone is e-commerce and large content sites with some dynamism. Both work. Choose Next.js if pages must reflect per-request state (inventory, personalization) at scale; choose Astro if the catalog is mostly static and you can rebuild or use on-demand SSR for the few dynamic routes.

i18n & structured data

International SEO lives or dies on hreflang, and rich results live on JSON-LD. Neither framework does this for you completely — you own the markup — but the routing support differs.

hreflang

Both frameworks expect you to emit reciprocal hreflang link tags. Astro has built-in i18n routing (locale prefixes, default-locale config) that makes generating the alternate URLs straightforward; Next.js Pages Router had an i18n config, while the App Router pushes you toward folder-based locale segments (app/[locale]/...) plus a library like next-intl. In both cases you generate the tags yourself:

<!-- The same set of tags belongs on every language version of a page -->
<link rel="alternate" hreflang="en" href="https://ex.com/en/blog/post/" />
<link rel="alternate" hreflang="fr" href="https://ex.com/fr/blog/post/" />
<link rel="alternate" hreflang="x-default" href="https://ex.com/en/blog/post/" />

💡 Tip: the single most common hreflang bug is non-reciprocal annotation — page A points to B, but B does not point back to A. Generate the full cluster from one source of truth (a map of locale → URL) and render it on every variant. Our deep dive lives in the international SEO guide.

Structured data (JSON-LD)

JSON-LD is just a script tag, so both frameworks handle it identically in spirit — the difference is only where you put it. In Astro you inline it in the template; in Next.js you render it from a component or build it in generateMetadata.

---
// Astro: build the object in frontmatter, render as a script tag
const schema = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  headline: post.title,
  datePublished: post.pubDate.toISOString(),
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
// Next.js: render JSON-LD from a Server Component
export default function Page({ post }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.title,
    datePublished: post.pubDate,
  };
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

Validate the output with Google’s Rich Results Test regardless of framework — a missing required property fails silently and you never get the rich result.

When to pick which

Concrete guidance by scenario:

  • Blog, docs, or marketing siteAstro. Zero-JS default, content collections, best-effort CWV with the least discipline required.
  • Programmatic SEO at scale (thousands of templated pages) → Astro if pages are static-ish; Next.js with ISR if they need frequent or per-request freshness.
  • SaaS with a marketing site and an appNext.js if you want one stack; Astro for marketing + your app framework if you want maximum marketing-page speed and don’t mind two deploys.
  • E-commerceNext.js for per-request inventory/pricing/personalization at scale; Astro for mostly-static catalogs.
  • Highly interactive app where SEO is secondaryNext.js. SEO is a non-issue here; pick for DX.
  • Team that knows React deeply and ships fastNext.js lowers friction even for content, as long as you stay disciplined about client JS.

🧑‍💻 Developer’s take: a popular and entirely valid pattern is both — Astro for the marketing/blog subdomain or /blog path, Next.js (or your SPA) for app.*. You get Astro’s content performance where SEO matters and React’s interactivity where it doesn’t. The cost is two builds and a shared design system.

Wrapping up

Stop looking for the framework Google rewards — there isn’t one. Both Next.js and Astro produce crawlable, rankable HTML when configured the modern way. The real decision is about default behavior and failure modes: Astro makes fast, zero-JS content pages the default and the lazy path; Next.js makes a unified, interactive application the default and gives you the rendering modes to keep dynamic pages SEO-friendly when you invest the discipline.

Pick Astro when the site is content. Pick Next.js when the site is an app that also has content. And when in doubt, measure the initial HTML and the shipped JS on a real build before you argue about it.

Next steps: