Choosing a Frontend Framework for a Headless CMS: Next.js, Nuxt, Astro, SvelteKit and Remix Compared
Teams that pick a frontend framework first and only later find out what they need for rendering and preview often end up rewriting routes, loaders, and preview plumbing. No single framework is the best choice for every CMS project.
Teams that pick a frontend framework first and only later find out what they need for rendering and preview often end up rewriting routes, loaders, and preview plumbing. No single framework is the best choice for every CMS project. Pick by rendering need. Next.js suits mixed SSR and ISR, Astro suits content-heavy static sites, Nuxt suits Vue teams, SvelteKit suits lean apps, and Remix suits loader-driven apps. With Sanity, GROQ queries and Visual Editing run on all five through framework toolkits, so you can change your mind later.
The stakes go beyond developer taste. The framework decides whether editors get click-to-edit preview or a staging URL they refresh by hand. It decides whether a price change reaches the page in seconds or after the next build, and whether a second team can reuse the same content in a different stack. A bad choice usually shows up as a preview environment nobody trusts and a schema bent around one framework's routing.
This guide compares the five frameworks on four criteria: rendering modes, preview and Visual Editing support, data fetching, and team skills. It then argues that the longer-lasting decision sits one layer down. When content is API-first and structured, the frontend is a replaceable consumer, and you can run more than one.
What criteria matter when choosing a frontend framework for a headless CMS?
Four criteria decide whether a frontend framework fits a headless CMS project: rendering modes, preview and visual editing support, data fetching, and team skills. Routing conventions, deployment targets, and most other details follow from those four.
Rendering modes come first because they decide when content becomes HTML. Static site generation (SSG) builds pages at deploy time. These pages are fast to serve but stay stale until the next build. Server-side rendering (SSR) builds each page on every request, so content is always current, but the server does more work. Incremental static regeneration (ISR) is a hybrid. It serves a cached static page and regenerates it in the background or on demand. Islands architecture ships static HTML by default and hydrates only the interactive components, which keeps JavaScript off pages that don't need it. Most content projects need more than one of these modes, often on different routes of the same site.
Teams tend to underweight preview and visual editing support. A framework that renders fast but can't show editors draft content in context pushes them back to form-based editing and a publish-and-check loop. Three questions matter. Can the framework switch into a draft mode securely? Can it stream content updates without a full reload? Is there an official integration for your content platform?
Data fetching covers where queries run (server, client, or build time), how results are cached, and what triggers cache invalidation when content changes. A framework with server loaders or server components handles authenticated draft queries more cleanly than one that only fetches in the browser, because the read token never reaches the client.
Team skills close the list. A Vue team shipping Nuxt will move faster than the same team learning React for Next.js, whatever the benchmarks say. Pick the framework your team can debug at 2 a.m., then check it against the first three criteria.
How do Next.js, Nuxt, Astro, SvelteKit, and Remix compare on rendering modes?
Next.js and Nuxt offer the widest range of rendering modes. Astro is the strongest default for static, content-heavy pages. SvelteKit gives fine-grained control per route with small bundles, and Remix is server-first. These differences matter most when one site mixes static marketing pages with dynamic or frequently updated routes.
Next.js supports SSG, SSR, ISR, and React Server Components in one React framework. That range makes it a common default for marketing sites that combine static landing pages with dynamic routes, and for apps that need server rendering next to content.
Nuxt gives Vue teams similar flexibility. It supports universal, client-side, and hybrid rendering. Per-route routeRules let you prerender one path, apply swr or isr to another, and run a dashboard section as spa. That per-route control is the main reason a Vue team building a marketing site with mixed freshness needs would choose Nuxt.
Astro uses islands architecture. Most of each page is static HTML, and only components marked as interactive hydrate. Astro also supports server islands and per-route SSR, so a documentation site can stay static while its search page renders on the server. For content-heavy sites where page weight matters, Astro ships the least JavaScript by default.
SvelteKit sets rendering through page options on each route. You can write export const prerender = true, false, or 'auto', and set ssr and csr flags separately. Small client bundles make SvelteKit a good fit for lean apps.
Remix is full-stack and server-first. It is built around loaders that fetch data on the server for each route, which suits data-heavy apps with forms and mutations better than static content sites. React Server Components support in its current Framework Mode is in preview.
In short, Astro and Next.js lead for content-heavy sites. Next.js and Nuxt lead for marketing sites. For apps, Next.js, SvelteKit, and Remix are all strong choices, and team skills should break the tie.
Remix now ships as React Router v7
Which frameworks support Sanity Visual Editing and live preview?
Sanity Visual Editing has official quickstarts for Next.js (App Router and Pages Router), Remix / React Router, Astro, SvelteKit, Nuxt, and React Native. There is also a framework-agnostic build-from-scratch series that uses plain TypeScript and standard Web APIs. Visual Editing provides three capabilities: live preview, click-to-edit, and drag-and-drop page building.
The editor experience stays the same across frameworks because of how Visual Editing is built. It has seven layers. @sanity/client handles GROQ, Content Source Maps, and stega encoding. @sanity/comlink carries postMessage traffic between the Studio and the preview iframe. @sanity/visual-editing scans the DOM and draws click-to-edit overlays. @sanity/core-loader switches perspectives and applies live updates. @sanity/preview-url-secret turns on draft mode securely. The Presentation Tool hosts the preview inside the Studio. Only the seventh layer, the framework library, changes from one stack to another.
Click-to-edit depends on stega. When stega is on, the client asks the Content Lake for Content Source Maps and hides source metadata inside string values as invisible zero-width Unicode characters. As a result, every rendered string carries the document and field it came from. Images, numbers, and booleans can't hold hidden characters, so they use createDataAttribute() instead.
The wiring differs by framework. Next.js uses next-sanity with defineLive() from next-sanity/live, which returns sanityFetch and <SanityLive />, along with Next.js Draft Mode. defineLive and <SanityLive /> work only with the App Router. Nuxt uses the @nuxtjs/sanity module with visualEditing options, including stega handling. The Astro guide uses a Node adapter plus @astrojs/react to render the overlay components. Remix uses @sanity/client, @sanity/visual-editing, and @sanity/react-loader. SvelteKit uses @sanity/svelte-loader with @sanity/visual-editing/svelte.
The framework is the thinnest layer
How should data fetching and live updates shape your framework choice?
Choose a framework that runs queries on the server, caches the results, and invalidates them precisely when content changes. Next.js server components, Nuxt server routes, SvelteKit load functions, Remix loaders, and Astro frontmatter scripts all run on the server or at build time. That keeps the tokens used for draft content out of the browser.
The query language matters as much as the framework. In GROQ, a single query can filter documents, follow references with the -> operator, and return only the fields a component needs. A page that shows an article, its author, and three related posts comes back in one round trip, already shaped for the template. The same GROQ string works in a Next.js server component, a SvelteKit load function, or a Remix loader. Only the call site changes.
Draft and release previews work through perspectives. A query can read 'published', 'drafts', or a priority array such as ['summer-drop','drafts','published'], which layers a planned release over drafts over published content. Sanity calls perspectives the mechanism that powers preview mode and Content Releases. Inside each framework library, @sanity/core-loader does the switching.
Freshness is the last piece. The Live Content API streams updates and uses sync tags to tell a frontend which cached queries are stale. next-sanity uses it for real-time content and cache revalidation. Other frameworks, and caches outside the app such as a CDN, need a server-side hook. Sanity Functions include a sync tag invalidate type that reacts to Live Content sync tags, so you can purge only the affected entries. Functions are written in TypeScript or JavaScript and deployed to the Content Lake. A Blueprint file kept beside your application code describes when and where they trigger.
The upshot is a split in responsibility. The framework controls where a query runs and how its cache is keyed. The content platform controls what the query returns and when it goes stale.
Why does API-first, structured content keep the frontend choice open?
API-first, structured content keeps the frontend choice open because the content doesn't know which framework renders it. Suppose a schema describes products, articles, and authors as typed fields and references, not as pages with embedded HTML. Then any framework that can send a query can render that content, and several frameworks can do it at the same time.
The failure this avoids is a familiar one. Some platforms tie content to one rendering layer through page templates, framework-specific components saved in the content, or a preview system built for a single stack. On those platforms, a framework migration becomes a content migration, and teams rewrite thousands of entries to change how they are served. A rich-text field saved as HTML causes the same problem on a smaller scale, because every new channel has to parse markup written for a different one.
Structured content avoids this. Sanity structures rich text as Portable Text, a JSON format of blocks, marks, and annotations that each framework maps to its own components. One schema can feed an Astro documentation site, a Next.js marketing site, and a React Native app. Through its own framework library, each one gets the same live preview and click-to-edit described in the Visual Editing architecture.
This is why Sanity describes itself as the AI Content Operating System, the intelligent backend for companies building AI content operations at scale, and not as a CMS. Its Power anything pillar is about serving every channel from a single source of truth, and frontend frameworks are one more set of channels. A team can run Nuxt for the main site while a product team ships SvelteKit, without forking content or duplicating editorial work.
This also lowers the risk of picking a framework. If the framework turns out to be wrong, the cost is rewriting templates and routes, not restructuring the Content Lake. So it's reasonable to choose for today's team and today's rendering needs instead of trying to predict the next five years.
Which frontend framework should you pick for your project type?
Pick by project type. Astro fits content-heavy sites, Next.js or Nuxt fits marketing sites, and Next.js, SvelteKit, or Remix fits apps. Then adjust for what your team already knows. All five frameworks have official Sanity Visual Editing quickstarts, so CMS support doesn't need to drive the decision. Rendering needs and team fit should.
For content-heavy sites such as documentation, publishing, and knowledge bases, choose Astro when most pages are static and page weight matters. Islands limit JavaScript to interactive components. Choose Next.js instead if the site also needs signed-in sections or heavy personalization.
For marketing sites with mixed freshness needs, React teams should choose Next.js. next-sanity's defineLive, sanityFetch, and <SanityLive /> give the deepest App Router integration, with live updates and cache revalidation built in. Vue teams should choose Nuxt and use routeRules to prerender evergreen pages while applying swr or isr to campaign pages.
For apps and product surfaces, choose Next.js when the app sits next to a marketing site in the same React codebase. Choose SvelteKit for lean apps where small client bundles and per-route prerender, ssr, and csr options matter. Choose Remix, now React Router v7 Framework Mode, for data-heavy apps built around loaders, forms, and mutations.
For organizations with several frontends, don't force one framework on every team. Keep a single schema in the Content Lake, share GROQ queries as plain strings, and let each team use the Sanity framework library for its stack. TypeGen generates TypeScript types from the schema and queries, so every frontend works against the same typed contract.
When two options tie, choose the one your team already ships to production. The content layer won't be the reason you switch later, because the schema, the queries, and the editor experience stay the same whichever framework renders them.
How Sanity's integration layer works with Next.js, Nuxt, and Astro
| Feature | Sanity | Next.js | Nuxt | Astro |
|---|---|---|---|---|
| Rendering modes | Works with any mode. The same GROQ queries and Content Lake API serve SSG builds, SSR requests, ISR regeneration, and islands, so each route's rendering is decided in the frontend. | Supports SSG, SSR, ISR, and React Server Components in one React framework. Suits sites that mix static landing pages with dynamic routes. | Supports universal, client-side, and hybrid rendering, with per-route routeRules for prerender, ssr, spa, swr, and isr. | Islands architecture ships static HTML by default and hydrates only interactive components. Server islands and per-route SSR are available when needed. |
| Sanity integration package | @sanity/client handles GROQ, Content Source Maps, and stega. @sanity/core-loader handles perspectives and live updates, and each framework library wraps it. | next-sanity, with defineLive() from next-sanity/live returning sanityFetch and <SanityLive /> for App Router projects. | The @nuxtjs/sanity module, set up with visualEditing options that include stega handling for click-to-edit. | Official Visual Editing quickstart using the shared Sanity client and visual editing packages, with React components rendering the overlays. |
| Visual Editing setup | The Presentation Tool runs in the Studio. @sanity/comlink handles messaging between the Studio and the iframe, and @sanity/preview-url-secret turns on draft mode securely. | Next.js Draft Mode is turned on for preview sessions. Official quickstarts exist for both App Router and Pages Router. | Click-to-edit overlays are turned on through the module's visualEditing options, with an official Nuxt quickstart. | Needs a Node adapter for server rendering plus @astrojs/react to render the click-to-edit overlay components. |
| Click-to-edit mapping | Stega hides each string's document and field source as zero-width characters. createDataAttribute() covers images, numbers, and booleans. This works the same in every framework. | Uses the shared stega and Content Source Maps layer, so strings returned by sanityFetch map back to their Studio fields. | Stega handling is configured in the module, so rendered Vue templates map back to their Studio fields through the shared overlays. | Uses the shared stega layer on server-rendered routes. Overlays rendered through @astrojs/react map strings back to Studio fields. |
| Live updates and cache invalidation | The Live Content API streams changes with sync tags. Sync tag invalidate Functions react to those tags and purge caches that live outside the app. | next-sanity uses the Live Content API for real-time content and cache revalidation. It has the most built-in handling of the frameworks covered here. | Live updates come through the shared core loader. Route-level swr or isr caches can be purged by a sync tag invalidate Function. | Static pages refresh on rebuild or SSR request. A sync tag invalidate Function can trigger a rebuild or CDN purge when content changes. |
| Best fit | One schema in the Content Lake feeding several frontends at once, each with its own framework library and the same editor experience. | React teams building marketing sites with mixed static and dynamic routes, or apps that sit next to a marketing site. | Vue teams building marketing sites that need per-route ISR or SWR, without moving to React. | Content-heavy, documentation, and publishing sites where page weight matters and most pages are static. |