Next.js vs Astro for content sites and blogs: an honest comparison
Jun 14, 2026 · 6 min read
Choosing between Next.js and Astro for a content site or blog in 2026 is a genuine decision, not a religious war. I've shipped both in production — one for a documentation portal, one for a marketing site with a Sanity backend — and the right answer depends on specifics that most comparison posts skip.
The core difference in rendering model
Astro's default output is zero-JavaScript HTML. It uses an island architecture: interactive components hydrate only where you explicitly opt in with a client: directive. For a blog, that means a 50-article site ships maybe 2–5 kB of JavaScript in total unless you add a search widget or a menu animation.
Next.js with the App Router defaults to React Server Components, which do strip client JavaScript for non-interactive subtrees. But the React runtime itself — even with RSC — lands around 45–70 kB gzipped on a cold page load because the client bundle is always present for navigation and hydration of any "use client" boundary. That's fine for apps. For a marketing blog with no interactive state, it's overhead you didn't ask for.
Practically: Astro wins on raw bundle size for static or near-static content. Next.js wins the moment your content site needs any meaningful application behaviour — authenticated dashboards, personalised feeds, A/B variants, cart drawers.
Sanity CMS integration
Both frameworks integrate with Sanity cleanly. The difference is in the ISR vs SSG story.
With Next.js, I use revalidation tags tied to Sanity webhooks. A content editor publishes a post; the webhook fires; Next.js revalidates only the affected routes. Pages stay cached at the edge until they're stale-on-demand. For a site updating multiple times a day, that's the right model.
// app/blog/[slug]/page.tsx
import { client } from '@/sanity/lib/client'
import { postBySlugQuery } from '@/sanity/lib/queries'
export const revalidate = false // rely on on-demand revalidation
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await client.fetch(postBySlugQuery, { slug: params.slug }, {
next: { tags: [`post:${params.slug}`] },
})
return <article>{/* render post */}</article>
}
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import { parseBody } from 'next-sanity/webhook'
export async function POST(req: Request) {
const { body, isValidSignature } = await parseBody<{ slug: string }>(req, process.env.SANITY_WEBHOOK_SECRET!)
if (!isValidSignature) return new Response('Unauthorised', { status: 401 })
revalidateTag(`post:${body.slug}`)
return new Response('Revalidated')
}Astro's integration story is different. Astro 5 ships with a Content Layer API that can pull from any data source — including Sanity — at build time. For a site with infrequent updates (weekly newsletter, documentation releases), a full rebuild on every publish is fine and arguably simpler. Astro also has SSR adapters (Vercel, Node, Cloudflare) if you need server rendering, but ISR with granular tag-based invalidation is not a first-class feature the way it is in Next.js.
// src/content/config.ts (Astro 5 Content Layer)
import { defineCollection } from 'astro:content'
import { sanityLoader } from 'astro-sanity' // community loader
export const collections = {
posts: defineCollection({
loader: sanityLoader({
query: `*[_type == "post"]{ _id, title, slug, body }`,
}),
}),
}This works well. The trade-off: every publish triggers a full rebuild. On Vercel with Astro, that's 30–90 seconds depending on content volume. On Next.js with ISR, a single post publish revalidates one or two cache entries in under two seconds.
Build times at scale
At 50 posts: both are fast. At 5,000 posts: build time matters.
Astro's static output per page is extremely fast to generate because there's no React tree to hydrate. But if every publish rebuilds everything, 5,000-page sites will start feeling the ceiling (5–15 minutes is not uncommon). Next.js's generateStaticParams combined with on-demand ISR sidesteps this: you can pre-build zero pages and let the cache warm on first request, or pre-build only the top 100 posts.
For content volume above ~500 pages, I'd lean Next.js solely for ISR unless the client's deploy pipeline supports incremental builds with a caching layer.
Developer experience
Astro's .astro file format is genuinely pleasant for content-focused templating. It's closer to HTML than JSX. Designers who write a little HTML find it less alienating. Component islands are explicit, which makes performance reasoning easy: you see exactly where JavaScript enters.
Next.js App Router has a steeper mental model — the RSC / Client Component boundary trips up junior developers regularly. But if your team is already in React, there's no context switch. Sanity Studio, Radix primitives, Framer Motion, Algolia InstantSearch — all assume React, so ecosystem friction is lower.
TypeScript support is strong in both. Sanity TypeGen works identically regardless of which framework consumes the generated types.
Decision matrix
| Scenario | Pick | |---|---| | Pure blog, no auth, < 500 posts | Astro | | Blog + ecommerce or auth | Next.js | | Docs site, infrequent deploys | Astro | | Marketing site, daily CMS updates, ISR needed | Next.js | | Team already in React | Next.js | | Designers own the templates | Astro | | Sanity with on-demand revalidation | Next.js | | Maximum page speed, static only | Astro | | Personalisation, draft preview, edge middleware | Next.js |
When I actually pick each
I reach for Astro when a client has a documentation site or a mostly-static marketing site where content changes happen at most a few times per week, the team is small, and nobody needs authenticated routes or interactive application state. The bundle size advantage is real and measurable in Lighthouse scores without any extra effort.
I reach for Next.js for everything else — any project with Sanity that needs ISR, any site that will grow a logged-in user section later, any team that's already in a React codebase. The ISR + webhook revalidation pattern is mature, well-documented, and the Sanity ecosystem (Portable Text renderers, image helpers, draft mode) is built with React as the default target.
The mistake I see most often is teams choosing Astro because they want performance, then gradually bolting on React islands for a search widget, a cookie banner, a newsletter form, and an interactive pricing table until they've reintroduced most of the JavaScript they were trying to avoid. If you can honestly scope the site and keep it content-only for its lifetime, Astro is the leaner choice. If there's any doubt, Next.js is the safer bet — not because it's faster, but because it doesn't paint you into a corner when requirements expand.
Related posts
All posts →How I sync Sanity CMS to Algolia search in a Next.js app
Jun 12, 2026 · 6 min read
Step-by-step: webhook-driven Sanity-to-Algolia sync, InstantSearch React UI, and handling stale index edge cases in Next.js. Practical and production-tested.
How I build a custom Sanity document actions publishing workflow
Jun 08, 2026 · 7 min read
Step-by-step guide to building custom Sanity document actions: approval gates, Slack notifications on publish, and scheduled releases via the Releases API.
Payload CMS vs Sanity for Next.js in 2026: an honest comparison
May 30, 2026 · 8 min read
Payload CMS vs Sanity for Next.js projects in 2026. Hosting, GROQ vs local API, TypeScript DX, pricing, and lock-in compared honestly.