When Next.js Is the Wrong Choice for a Content Site

Diagnose the Next.js-for-content mismatch with copy-paste benchmark commands, a real bundle/cost table, and a phased Astro 6 migration plan that keeps your SEO.

A content site that did not need Next.js looks identical to one that did on day one. The mismatch surfaces around month three: slower builds, a 200 KB+ JavaScript bundle on a page that is 95% text, a hosting bill driven by bandwidth you cannot tune, and a CMS-to-rebuild dance that should have been a static export. This guide gives you the exact commands to confirm the mismatch, a real cost and bundle comparison, and a phased migration to Astro that protects your rankings.

TL;DR

  • Next.js 16 (16.2.6 LTS as of May 2026) is built for hybrid app-and-content surfaces. If you ship zero 'use client' components, zero server actions, and zero middleware, you are running a static site generator with a React runtime bolted on.
  • Astro 6 (stable since March 2026) ships zero JavaScript by default and is the framework most teams move content sites to. The Astro project was acquired by Cloudflare in January 2026 and stays MIT-licensed.
  • Run the four benchmark commands below. If three or more signals match, the move pays off: smaller bundles, faster builds, and cheaper hosting.
  • Migrate URL-for-URL with 301 redirects. The SEO risk is in the URL map, not the framework swap.

The actual mismatch

Next.js 16 is optimized for surfaces that mix an application with content: per-user dashboards, authenticated flows, server actions, streaming, and the new Cache Components model (Partial Pre-Rendering plus use cache). Those features are the whole point of the framework. On a marketing site or a blog where the only dynamic parts are a search box and a contact form, none of them earn their keep, and every one of them adds build complexity and shipped bytes.

The cost is concrete. A default Next.js App Router page hydrates the React runtime even when nothing on the page is interactive. Astro inverts that: components render to HTML at build time and ship no client JavaScript unless you explicitly mark an island. In practice that is the difference between a 200 KB+ first-load bundle and a near-zero one.

How to tell you have the mismatch

Five signals. Three or more means you are likely paying the Next.js tax for nothing:

  • You have written zero 'use client' components in the last three months.
  • next build takes longer than your last three Astro projects combined.
  • You have never shipped a server action, route handler, or middleware file.
  • Your hosting bill is dominated by bandwidth, not function execution — meaning you are paying Vercel for delivery that Cloudflare Pages or Firebase Hosting would do cheaper (often free).
  • Lighthouse flags 200 KB+ of JavaScript on a plain article page, with most of it unused.

What you actually gain

This is the comparison most “Astro vs Next.js” posts skip — real numbers, not vibes. Figures below reflect typical content-page outcomes and current public pricing as of June 2026; benchmark your own pages with the commands further down.

DimensionNext.js 16 (content page)Astro 6 (content page)
Client JS on a text articleReact runtime hydrates; commonly 150-250 KBZero by default; only marked islands ship JS
Lighthouse PerformanceVaries with hydration costAstro sites typically score 95-100
Markdown build speedSlower on content-heavy sites~5x faster vs Astro 5; a 100-post site rebuilds in roughly 200 ms
Core Web Vitals pass rateDepends on tuningAstro is the only framework with over 50% of sites passing CrUX
Free commercial hostingVercel Hobby is non-commercialStatic output runs free on Cloudflare Pages (commercial OK)

The headline is the first row: a content page does not need a hydrated React tree, and shipping one is pure overhead on the metric Google actually ranks.

Hosting cost reality (June 2026)

The bandwidth signal is the one people get wrong, so price it out before you decide:

HostFree tierCommercial on free?Paid bandwidth
VercelHobby: 100 GB Fast Data Transfer/moNo (Hobby is non-commercial)Pro $20/seat, 1 TB included, then $0.15/GB
Cloudflare PagesUnlimited bandwidth, 500 builds/moYesEffectively unlimited; no overage cutoff
Firebase HostingSpark: ~10 GB storage + ~10 GB transfer/moYesBlaze: pay-as-you-go

If your site is static and traffic-heavy, Cloudflare Pages serving Astro output is hard to beat: unlimited bandwidth, commercial use allowed, no per-GB egress meter. A Next.js app on Vercel Pro that pushes past the 1 TB allowance pays $0.15/GB on top of the $20 seat — for bytes a static host serves free.

Before you migrate: record a baseline

Do not start porting until you have numbers to compare against. Five minutes now saves an argument later.

  • Capture current build time, LCP, and first-load JS for your most representative article.
  • Pick one typical article and the index page as the migration test cases.
  • Block a half-day for a parallel Astro spike — enough to port one page and measure it.

Step by step

1. Audit your dynamic surfaces

A few greps tell you the truth about how much of Next.js you actually use:

# count client components, server actions, middleware, route handlers
grep -rl "^'use client'" app/ | wc -l
grep -rl "^'use server'" app/ | wc -l
ls middleware.* 2>/dev/null
find app -name 'route.ts' -o -name 'route.tsx' | wc -l

A pure content site should have nearly zero 'use server', no middleware.*, and very few route.ts files. If those counts are high, stop here — you have a real app and Next.js is the right tool.

2. Benchmark the current build and bundle

time npm run build
# real: ?m ?s

# What ships on a typical article page?
ls -lh .next/static/chunks/ | sort -k5 -h | tail
# the largest chunks loaded on a page

3. Run Lighthouse on a typical article

npx lighthouse https://yourdomain.com/articles/sample-slug/ \
  --only-categories=performance,seo \
  --chrome-flags="--headless" --quiet --output=json --output-path=./lh-next.json

jq '.audits."largest-contentful-paint".numericValue,
    .audits."total-blocking-time".numericValue,
    .audits."total-byte-weight".numericValue,
    .audits."unused-javascript".details.overallSavingsBytes' lh-next.json

Record LCP (ms), TBT (ms), total bytes, and unused JS. The unused-JavaScript number is the smoking gun: on a content page it should be near zero, and on a Next.js blog it usually is not.

4. Spin up a parallel Astro 6 spike

Port one article plus the index page from scratch:

npm create astro@latest blog-spike -- --template minimal --install --yes
cd blog-spike
npm install @astrojs/mdx @astrojs/sitemap
# add astro.config.mjs with site + sitemap; drop one .mdx article into src/content
npm run build
ls dist/articles/sample-slug/   # confirm output

Astro 6 uses the Content Layer: a loader defines where each collection comes from (local files, an API, or a headless CMS), with a type-safe schema. Most existing MDX frontmatter ports with only schema edits.

5. Compare the two builds

# Astro build time
time npm run build

# Astro bundle on the same article URL via Lighthouse
npx lighthouse http://localhost:4321/articles/sample-slug/ \
  --only-categories=performance --chrome-flags="--headless" --output=json \
  --output-path=./lh-astro.json
jq '.audits."largest-contentful-paint".numericValue,
    .audits."total-byte-weight".numericValue' lh-astro.json

Astro content pages should ship near-zero JS, with total byte weight dropping substantially versus the Next.js page.

6. Plan the migration in phases

A workable order with a reversal path at every step:

Phase 1 (1-2 weeks): Astro project reaches parity for the blog
Phase 2 (1 week):    Marketing pages migrated
Phase 3 (3-5 days):  301 redirects mapped from old Next.js URLs to new
Phase 4 (1 day):     Cut DNS / hosting; freeze (do not delete) the Next.js repo

7. Write the 301 redirects

On Vercel, in vercel.json:

{
  "redirects": [
    { "source": "/old/(.*)", "destination": "/articles/$1", "permanent": true }
  ]
}

On Cloudflare Pages or Netlify, in a _redirects file:

/old/*  /articles/:splat  301

Map every old URL to exactly one new URL. A redirect chain (old → temp → new) leaks PageRank, so keep it one hop.

8. Verify SEO after launch

Two to four weeks post-launch, use Search Console URL Inspection plus the Performance report. Average position should hold or improve, and LCP/INP should improve materially on article pages. If a cluster of URLs drops out of the index, check that their 301s resolve in a single hop and return 301 (not 302).

Post-migration checklist

  • LCP drops by 30% or more on typical article pages.
  • Total page bytes drop 60%+ on articles with no client islands.
  • Search Console “Indexed” count returns to baseline within 4-8 weeks.
  • No new 404s in Search Console for old URLs — the 301s are catching them.
  • Old Next.js repo frozen, not deleted, for at least 30 days as a rollback option.

Common pitfalls

  • Migrating over one slow build. Profile first — slowness is often a bad image pipeline that any framework would suffer. Fix that before blaming Next.js.
  • Rebuilding Next.js in Astro. Marking every island client:load ships the same JavaScript you were trying to shed. Reach for client:visible or client:idle, and only on components that genuinely need interactivity.
  • Underestimating the URL map. Every old URL needs a 301 to its new home, or you lose the rankings those pages earned.
  • Switching tooling mid-launch. Pick one framework, ship it, and change later only if the data supports it.
  • Deleting the old repo too early. Keep it 30+ days so you can roll back DNS in an afternoon if traffic craters.

FAQ

  • Will I lose SEO if I migrate?: Only if you change URLs without 301s, or if Core Web Vitals regress. A well-planned Next.js to Astro move usually holds or improves rankings within a month because content pages load faster.
  • Is Astro actually faster than Next.js for content?: For text-heavy pages, yes — shipping zero JavaScript by default removes the hydration cost that drives up TBT and INP. Astro is also the only framework where over 50% of live sites pass Google’s Core Web Vitals assessment.
  • Can I keep my MDX during migration?: Yes. Astro 6 supports MDX natively via @astrojs/mdx. Most frontmatter and components port with schema edits in the Content Layer collection definition.
  • What about hosting after I move?: Static Astro output runs anywhere — Cloudflare Pages (unlimited bandwidth, commercial use allowed on the free tier), Firebase Hosting, Vercel, Netlify, or plain S3. You stop paying for SSR you were not using.
  • Should I wait because Next.js 16 added Cache Components?: Cache Components (PPR plus use cache) speeds up dynamic, app-like surfaces. A static content site has nothing to cache at request time, so it does not change the calculus.
  • How long does a typical migration take?: For a 100-200 article site, 2-4 weeks part-time. Most of the time goes to content QA and redirect mapping, not framework code.

Tags: #Indie dev #Next.js #Comparison #Website planning