WB × The Multiplier / Web & development

Comparison

Astro vs Next.js for a Content Site in 2026: Notes From Running One of Each

One is a 92-piece publication on Cloudflare Pages. The other is my own studio site with a CRM behind it. The framework choice followed the second half of each sentence.

By Weston BeckwithUpdated 9 min read

A stone bridge and a steel bridge crossing the same river at dusk, lit teal.
Illustration for WBX Creative

If the site is mostly pages, build it on Astro. If the site is also an application, build it on Next.js. That is the answer as of September 2026, and the rest of this piece is what the difference looked like on two real builds: The Outpost, a programmatic SEO publication WBX Creative built for Power Lodge Florida on Astro 7 and Cloudflare Pages, and wbxcreative.com, the Next.js site you are reading now.

The Outpost went to production on 2026-09-17 with a content plan of 92 pieces. This site has run on Next.js since 2025 and this month got a full SEO rebuild and the blog engine rendering this page. As of 2026-09-23 it runs Next.js 15.5.26 while 16.3.6 is current; the upgrade is planned before Next 15 maintenance ends on 2026-10-21.

Neither choice was wrong. They were different answers to a question about the site, not the framework: does anything on it have to happen at request time?

The short version

  • Build model. Astro prerenders everything and ships no client JavaScript unless you add an island. Next.js renders Server Components and wants a server runtime for anything dynamic.
  • Content. Astro validates frontmatter with a Zod schema at build. Next.js has no content layer.
  • Images. We skipped Astro's image component and pre-sized with sharp. On Vercel, next/image transformations are metered.
  • Hosting. Cloudflare Pages serves the Outpost's static requests free and unlimited. Vercel's Hobby plan forbids commercial use, so a studio site belongs on Pro.
  • Dynamic needs. This site runs a CRM, a REST API, and an MCP endpoint. The Outpost runs nothing at request time.
  • Gates. The same quality gate runs on both. I wrote it for Astro and ported it to Next.js.

The two builds side by side

The Outpost (Astro)wbxcreative.com (Next.js)
Version, September 2026Astro ^7.2.10 (7.3.4 current)Next.js 15.5.26 (16.3.6 current)
RenderingFully prerendered, no SSR adapterServer Components; static pages plus Node API routes
ContentMDX collections, glob() loader, Zod schemaMDX via gray-matter and next-mdx-remote, no schema at build
Imagessharp scripts pre-size; plain <img> with dimensionsnext/image, AVIF and WebP, sources capped at 1,600 px
Sitemap@astrojs/sitemap, production only, truthful lastmodapp/sitemap.ts, lastModified only where a date exists
Quality gate10 checks over dist/, no bypassSame gate, reads .next/server/app
Dynamic needsNone at request time; the lead relay is written as a separate WorkerCRM at /admin, REST API, MCP endpoint, Resend

How does each one build a page?

The Outpost has no SSR adapter in astro.config.mjs, so every route is HTML on disk. Astro 7, released 2026-06-22, compiles .astro files with a Rust compiler and renders Markdown and MDX through its own Rust pipeline by default instead of the remark and rehype chain, and Astro reports builds 15 to 61 percent faster in its own benchmarks. The Outpost passes no remark or rehype plugins, so the change was a non-event; if yours does, the unified pipeline is still an opt-in.

This site is React Server Components. Post pages are static at build through generateStaticParams, but the same app serves the CRM API, the MCP endpoint, and the form routes, so it needs a Node runtime. That single fact is most of the decision. The blog could be Astro. The site around it could not.

Next.js 16 changed the build itself. Turbopack is the default, and a custom webpack function in next.config.ts makes next build fail unless you pass --webpack. This site has one, a short rule for PDF, AI, and EPS files, so porting it to turbopack.rules is on the upgrade list.

Content collections: Astro has them, Next.js does not

This is where Astro earned its keep. The Outpost defines its guides in src/content.config.ts with a glob() loader and a Zod schema. Every page defaults to status: 'draft', and a superRefine refuses to build a page marked approved unless its qc array holds a passing fact-check and a passing editorial review:

// powerlodge-pseo/src/content.config.ts (excerpt)
.superRefine((d, ctx) => {
  if (d.status !== 'approved') return;
  const passed = (stage: 'fact-check' | 'editorial') => d.qc.some((q) => q.stage === stage && q.verdict === 'pass');
  if (!passed('fact-check') || !passed('editorial')) {
    ctx.addIssue({
      code: 'custom',
      message:
        'status: approved requires qc entries with verdict: pass for BOTH fact-check and editorial (AGENTS.md rule 1, amended 2026-09-02)',
    });
  }
  if (d.qc.some((q) => q.verdict === 'fail')) {
    ctx.addIssue({ code: 'custom', message: 'status: approved but a qc entry has verdict: fail — fix the page and re-run QC' });
  }
})

That rule is why a machine-drafted page cannot reach production by accident. The workflow behind it is its own piece: the editorial quality gate behind The Outpost.

Next.js has nothing equivalent. This site's lib/blog.ts reads content/blog/*.mdx with gray-matter, hides drafts unless SHOW_DRAFTS=1, and gives the sitemap and RSS feed a separate function that only returns approved posts. The frontmatter check is a script I run by hand, and it does not block the build. It works. It is also two places where Astro has one.

Images and the cost of an image service

Astro's <Image> runs sharp at build and sets width and height automatically so nothing shifts. We did not use it. The Outpost mirrors dealer photos with its own sharp scripts, serves plain <img> tags with width, height, loading="lazy", and decoding="async" on cards, and puts fetchpriority="high" plus a <link rel="preload"> on heroes from src/layouts/Base.astro. No image service, no quota. This site uses next/image with AVIF and WebP, which is right for an image-heavy portfolio, but the sources still matter: capping every file at 1,600 px with sharp took the image set from 216 MB to 42 MB, and Vercel meters transformations. The image case study has the numbers.

Where does each one host, and what does it cost?

The Outpost deploys from GitHub Actions: Node 22, npm ci, mirror images, build, gate, then wrangler pages deploy dist. As of September 2026, Cloudflare Pages on the free plan allows 500 builds a month, one at a time, with a 20-minute timeout, 20,000 files, and 25 MiB per file, and static requests are free without limit.

Two things about Cloudflare before you copy this. First, the Astro Cloudflare adapter now targets Workers and no longer supports Pages for server-rendered routes. We chose Pages anyway: the dealership keeps its DNS zone with another provider, and a Pages custom domain on external DNS verifies with one CNAME, while a Workers custom domain needs an active zone in our Cloudflare account. Second, Pages serves the most recently created deployment, not the newest commit. On 2026-09-04 three overlapping CI runs finished out of order and production showed the oldest one. The fix was a concurrency group with cancel-in-progress: false, so runs queue.

This site deploys through Vercel's Git integration, and the gate runs inside npm run build. Vercel's Hobby plan is non-commercial only, and its fair-use page counts "receiving payment to create, update, or host the site" as commercial, so a client or studio site belongs on Pro, which as of September 2026 is $20 per seat per month with a $20 usage credit. That is the hosting delta for a commercial site: roughly $0 against roughly $20 a month, before image transformations.

What happens when the site needs to do something dynamic?

Then you are on Next.js, or you are bolting a service onto Astro. This site's contact and intake forms post to API routes, the CRM lives at /admin on Supabase, and an MCP endpoint lets an assistant read it. Next.js keeps all of it in one repo and one deploy. The Outpost's only dynamic need is a lead form, and that is written as a separate Cloudflare Worker (workers/lead.mjs) the form posts to, deployed on its own outside the Pages project. Fine for one form. Miserable for a dozen.

The same quality gate on both

The Outpost's scripts/gate.mjs runs 10 checks over dist/, from thin pages and near-duplicates above 0.85 cosine similarity to draft leakage in production and a robots inversion check between staging and production. Production fails on any failure, with no bypass flag, by design. The Next.js port reads .next/server/app, drops the inventory and staging-versus-production robots checks, and adds a noindex-versus-sitemap check plus a test that robots.txt and the sitemap were emitted. Its first green run covered 23 pages with zero failures.

A gate only needs HTML on disk, so the framework does not matter to it. The Next.js SEO checklist lists what it enforces, and it is what I set up under technical services for sites I did not build.

Which one for which business

Project typePickWhy
Blog, docs, or marketing siteAstroKnown at build; free static hosting.
Programmatic SEO publication from a data feedAstroBuild-time schema, committed snapshot, experimental incremental builds since 7.2.
Image-heavy portfolio, nothing elseAstroPre-size with sharp; no image service to meter.
Studio or agency site with forms, payments, and an adminNext.jsThe API routes and the pages share one deploy.
SaaS, dashboards, anything with a loginNext.jsSessions and an API in one place.
Headless e-commerce front endNext.jsOnly once you have outgrown the platform's own theme.
Local business site, three to eight pagesEither; I lean AstroA Quick Launch site is mostly pages; Next.js if a portal is coming.

Build speed did not decide any of it. Vercel's own comparison page says Astro ships less client JavaScript on average and recommends it for blogs, docs, marketing sites, and portfolios, keeping Next.js for SaaS, e-commerce, and authenticated apps. The build-time benchmarks I have seen are split, and none has been re-run on Astro 7 or Next 16.3. What decided it, both times, was the second half of the sentence: a publication that needs no server, and a studio site that is also the business's back office.

Bottom line

Astro for a site that is mostly pages: blogs, docs, marketing, portfolios, and programmatic SEO. Next.js when the site is also an application: forms with logic, payments, an admin, an API. If you are not sure which yours is, it is mostly pages.

Not sure which one your site is?

Send me the URL and what it has to do. I will tell you which framework and what it costs to host.

Start a conversation

Questions people ask

Is Astro or Next.js better for SEO in 2026?
Neither wins on SEO by itself. Both can emit canonical tags, a sitemap, JSON-LD, and Open Graph images at build time. Astro ships less client JavaScript by default, which helps Core Web Vitals on content pages. Next.js needs discipline around client components and the Metadata API. The quality gate you run on the output matters more than the framework.
Can Astro handle a site with forms, payments, or a login?
Only with a server-side rendering adapter, and on Cloudflare that adapter now targets Workers rather than Pages. One form can live in a separate Worker or a form service. If the site has an admin, payments, or an API, Next.js keeps all of it in one codebase and one deploy.
Which Astro and Next.js versions are current in September 2026?
As of 2026-09-22, Astro 7.3.4 is current and needs Node 22.12 or newer. Next.js 16.3.6 is the Active LTS release and 15.5.26 is Maintenance LTS; both shipped that day as a security patch. Next.js 15 maintenance ends on 2026-10-21.
Is Cloudflare Pages still safe to use for a new Astro site?
For a fully prerendered site, yes. Cloudflare has not deprecated Pages, but the Astro Cloudflare adapter no longer supports it for server-rendered routes. Build static on Pages if DNS or account ownership favors it, and plan on Workers the day you need request-time rendering.
Does Next.js 16 break a Next.js 15 site?
Not automatically, but a custom webpack function in next.config makes the build fail unless you pass the webpack flag, synchronous params and searchParams are gone, and middleware moves to a proxy file. Read the upgrade guide before bumping.

Sources

Keep reading