How to deploy Next.js, Astro, Nuxt, or SvelteKit to Netlify
Build settings and SSR adapters for every major framework, the env-var rules that need a redeploy, and the leftover SPA redirect that silently breaks server rendering.
Key takeaways
Netlify detects most frameworks and suggests the right build command and publish directory. For server rendering you add that framework’s adapter, and for several frameworks that’s a single command: npx astro add netlify for Astro, @netlify/plugin-nextjs automatically for Next.js 13.5+. Two rules cause most of the trouble: environment values are injected at build time so any change needs a redeploy, and a leftover SPA catch-all redirect will silently break your SSR routes.
- Remove
/* /index.html 200when you adopt an SSR adapter. User redirects beat adapter-generated routes, so the catch-all serves static HTML for your server routes and API endpoints. - Any env var change requires a redeploy. Editing one in the UI doesn’t reach the live site or already-deployed functions.
- Never use a client prefix for secrets.
VITE_,NEXT_PUBLIC_,PUBLIC_,NUXT_PUBLIC_,REACT_APP_,GATSBY_,VUE_APP_all get inlined into the browser bundle. - SSR runtime access needs both Functions and Builds scopes, not just Builds.
- Vite-based frameworks emulate Netlify locally without the CLI: functions, blobs, database, Image CDN, redirects, headers, AI Gateway.
- Don’t pin
@netlify/plugin-nextjs. Netlify auto-updates it each build.
The framework part should be boring
Framework deploys go one of two ways. Either detection picks up your project, the suggested build settings are right, and it works on the first push. Or something subtle is off, and you spend an afternoon on a page that renders correctly locally and serves stale static HTML in production.
The second case usually has one of a small number of causes, and they’re the same causes across frameworks. This covers the build settings table you probably came for, the adapter setup per framework, and then the failure modes worth knowing before you hit them.
Environment variables: read this first
Values are injected at build time. Any change, client-side or server-side, requires a redeploy. Editing a variable in the UI or CLI does not reach the live site or already-deployed functions until a new build runs. This is the single most common “I already fixed that” moment.
Never use a client prefix for secrets. These are inlined into the browser bundle: VITE_, NEXT_PUBLIC_, PUBLIC_, NUXT_PUBLIC_, REACT_APP_, GATSBY_, VUE_APP_.
The client-embed prefix varies by framework: Create React App uses REACT_APP_, Gatsby GATSBY_, Next NEXT_PUBLIC_, Nuxt NUXT_PUBLIC_, Vue CLI VUE_APP_.
Scopes. Build-time access needs the Builds scope. SSR and DSG runtime access needs both Functions and Builds. And netlify.toml is read only during the build, so functions can’t read variables from it at runtime; set those through the UI, CLI, or API.
Netlify’s own build variables can’t be used as values in the UI or in netlify.toml env sections. Set them inline before the build command instead:
[build] command = "REACT_APP_CONTEXT=$CONTEXT npm run build"The SPA catch-all footgun
Single-page apps (React, Vue CLI, Vite, Nuxt in SPA mode) need a rewrite so pushState routing doesn’t 404:
/* /index.html 200Remove it when you adopt an SSR adapter. User redirects take precedence over adapter-generated routes, so a leftover catch-all quietly serves static index.html for SSR pages and API routes. Nothing errors. The page just isn’t server-rendered any more, and your API routes return HTML.
Build settings by framework
Detection suggests these. Override in netlify.toml or the UI under project configuration > Build & deploy > Continuous deployment > Build settings.
| Framework | Build command | Publish |
|---|---|---|
| Angular (standard) | ng build --prod | dist/YOUR_PROJECT_NAME |
| Astro | astro build | dist |
| Create React App | react-scripts build | build |
| Eleventy | eleventy | _site |
| Gatsby | gatsby build | public |
| Hugo | hugo | public |
| Hydrogen | remix vite:build | dist/client |
| Next.js (SSR/hybrid) | next build | .next |
| Next.js (static export) | next build && next export | out (NETLIFY_NEXT_PLUGIN_SKIP=true) |
| Nuxt 3 | nuxt build | dist |
| Nuxt 2 | nuxt generate | dist |
| React Router | react-router build | build/client |
| Remix (Vite) | remix vite:build | build/client |
| SolidStart 2 (Vite plugin) | vite build | dist/client |
| SolidStart 2 (Nitro) | vite build | dist |
| SolidStart 1.x | vinxi build | dist |
| SvelteKit | vite build | build |
| TanStack Start (1.132.0+) | vite build | dist/client |
| Vite | vite build | dist |
| Vue CLI | vue-cli-service build | dist |
Adapter setup by framework
Astro
npx astro add netlifyThat installs the adapter and edits astro.config.mjs. You need it for SSR and for out-of-the-box Image CDN support with <Image />. SSR routes become Netlify Functions; middleware becomes an Edge Function. You can deploy without the adapter only if you have no server features and no Image CDN need. Skew protection is available from Astro 5.15.0.
Next.js (13.5+ only)
Zero-config through the OpenNext adapter, @netlify/plugin-nextjs. Do not pin the version. Netlify auto-updates it each build.
The adapter provisions a serverless function for SSR, ISR, PPR, route handlers, and Server Actions; an Edge Function for Middleware; the Full Route and Data Cache; and Image CDN for next/image.
Skew protection is opt-in and version-conditional. Set NETLIFY_NEXT_SKEW_PROTECTION=true and redeploy. On Next.js earlier than 14.1.4 the env var is not sufficient on its own; add the deployment-id flags:
/** @type {import('next').NextConfig} */const nextConfig = { experimental: { useDeploymentId: true, // only needed when using Server Actions useDeploymentIdServerActions: true, },};There’s no automatic support for client-side fetch. Make those calls directly with x-deployment-id: process.env.NEXT_DEPLOYMENT_ID.
If you find guidance referencing the legacy Next adapter, treat it as history rather than a recommendation.
SvelteKit
npm install -D @sveltejs/adapter-netlifyimport adapter from '@sveltejs/adapter-netlify';export default { kit: { adapter: adapter() } };Replace @sveltejs/adapter-auto with the specific import. SSR routes become a render function. Four things to know:
split: truegives one function per route, and is incompatible with Edge Functions (setedge: falseor omit it).edge: trueruns SSR in a Deno edge function, and can’t be combined withsplit.- Redirects are not supported in
netlify.tomlfor SvelteKit. Use_redirects. - Edge functions don’t work locally with
netlify devfor SvelteKit.
React Router (7+)
New project:
npx create-react-router@latest --template netlify/react-router-templateExisting project: npm install @netlify/vite-plugin-react-router, then add netlifyReactRouter() to your Vite plugins. The default target is Serverless Functions.
For Edge (Deno) you need plugin v2.1.1+, edge: true, and you must create app/entry.server.tsx:
export { default } from 'virtual:netlify-server-entry'Exclude your own function paths with netlifyReactRouter({ edge: true, excludedPaths: ['/api/*'] }). Moving back to Serverless means removing edge: true and deleting app/entry.server.tsx.
Middleware needs React Router v7.9.0+ and plugin v2.0.0+. Opt in via future.v8_middleware, import netlifyRouterContext from @netlify/vite-plugin-react-router/serverless (or /edge when edge: true), and read it with context.get(netlifyRouterContext).
Remix
New: npx create-remix@latest --template netlify/remix-template, which prompts for functions vs Edge Functions. Manual setup requires Remix Vite:
npm install --save-dev @netlify/remix-adapterAdd netlifyPlugin() from @netlify/remix-adapter/plugin to your Vite plugins.
Nuxt
SSR runs through Nitro, automatic on Nuxt 3. For local parity install @netlify/nuxt with npx nuxi module add @netlify/nuxt.
- SSR on Edge Functions needs a different Nitro deployment preset, and it isn’t auto-detected.
- On pnpm with Nuxt 3, set
PNPM_FLAGS=--shamefully-hoist. nuxt/imageuses Netlify Image CDN automatically. Set remote domains innuxt.config.ts.
SolidStart
SolidStart 2 builds on Vite and has no SolidStart-specific adapter. Install @netlify/vite-plugin:
import netlify from "@netlify/vite-plugin";import { solidStart } from "@solidjs/start/config";import { defineConfig } from "vite";export default defineConfig({ plugins: [solidStart(), netlify({ build: { enabled: true } })],});Publish dist/client. SSR routes, server functions, and middleware all become Netlify Functions with no extra config. For the Nitro alternative, add nitro(), use plain netlify() without build.enabled, and publish dist. SolidStart 1 auto-configures Nitro; optionally set preset: "netlify" in app.config.ts and use vinxi build with dist.
TanStack Start
React and Solid.js full-stack. SSR, Server Routes, Server Functions, and middleware all become serverless functions.
npm install -D @netlify/vite-plugin-tanstack-startAdd netlify() to your Vite plugins alongside tanstackStart(), then vite build with dist/client on 1.132.0+. CLI deploys need netlify-cli 17.31 or newer.
Gatsby
Which path you’re on depends on your version.
- 5.12.0+ (adapter): auto-detects and installs
gatsby-adapter-netlify, zero-config. GeneratesSSRandDSGfunctions. No Essential Gatsby plugin needed. - 5.11.0 or earlier (Essential Gatsby plugin): auto-installs
@netlify/plugin-gatsby, and you also need to manually installgatsby-plugin-netlifyfor SSR, Gatsby redirects, and asset caching. Generates__api,__ssr,__dsg,__ipx. Skip them withNETLIFY_SKIP_GATSBY_FUNCTIONSfor all, or the per-function variants.
Gatsby 5 requires Node 18. On large sites, set GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE to load the datastore from the CDN, which avoids the max function deploy size at the cost of a slower first SSR/DSG load. For Image CDN set NETLIFY_IMAGE_CDN=true (Contentful, Drupal, WordPress source plugins), though it’s not supported on 5.12.x with the adapter, so upgrade to 5.13.0+. And note StaticImage and gatsby-transformer-sharp don’t work for SSR or DSG; host those images on a CDN.
Angular
SSR is auto-configured through an Edge Function. Suggested dev setup is ng serve on port 4200.
The important consequence: SSR pages are not subject to _redirects or netlify.toml redirects, because SSR runs in Edge Functions that execute before redirects. Use Angular’s built-in redirects instead.
Access Request and Context in SSR via the netlify.request and netlify.context providers from @netlify/edge-functions. They’re unavailable client-side and during prerendering. Test locally with netlify serve. NgOptimizedImage uses Image CDN automatically; set remote_images as an array of regex under [images] in netlify.toml.
Express
Node 18.14.0+. Deploy as a Netlify Function via serverless-http:
npm i express serverless-http @netlify/functions @types/expressimport express, { Router } from "express";import serverless from "serverless-http";const api = express();const router = Router();router.get("/hello", (req, res) => res.send("Hello World!"));api.use("/api/", router);export const handler = serverless(api); // v1 Functions API — serverless-http requires this pattern[functions] external_node_modules = ["express"] node_bundler = "esbuild"[[redirects]] force = true from = "/api/*" status = 200 to = "/.netlify/functions/api/:splat"With no frontend, set a placeholder build command like echo Building Functions. All Function limits apply, and this isn’t recommended for background or scheduled functions.
Hydrogen
Shopify’s stack on React Router 7. SSR runs only on Netlify Edge Functions. Netlify Functions are not officially supported. Node 24+. Use the starter:
npm create @shopify/hydrogen@latest -- --template https://github.com/netlify/hydrogen-templatecp .env.example .env && npm run devLocal development with platform emulation
Vite-based frameworks emulate Netlify primitives right in the dev server, with no Netlify CLI: functions, edge functions, blobs, Netlify Database, the Cache API, Image CDN, redirects and rewrites, headers, env vars, and AI Gateway.
| Framework | Plugin or module | Run |
|---|---|---|
| Astro (5.12+) | built-in (Netlify Vite plugin auto-loaded) | astro dev |
| Nuxt | @netlify/nuxt | nuxt dev |
| React Router | @netlify/vite-plugin | react-router dev |
| SolidStart 2 | @netlify/vite-plugin | vite dev |
| TanStack Start | @netlify/vite-plugin-tanstack-start | (vite) |
| Vite | @netlify/vite-plugin | npx vite |
You still need netlify dev for Gatsby generated functions (run netlify build first), Angular SSR local testing (netlify serve), and any framework without a Vite plugin.
Support level: Astro, Nuxt, TanStack Start, React Router, and SolidStart are full. SvelteKit is experimental.
Common failure modes
Your SSR pages serve stale static HTML. A leftover SPA catch-all. Remove /* /index.html 200 when you adopt an adapter.
You changed an env var and nothing happened. Values are baked in at build time. Redeploy.
Your SSR route can’t read an env var that works at build time. Runtime access needs both the Functions and Builds scopes, not Builds alone.
Your custom netlify dev command is being ignored. If [dev] has both a command and a targetPort, you must set framework = "#custom". Otherwise the detector runs and your command is silently dropped.
Your Angular redirects don’t fire. SSR pages bypass _redirects and netlify.toml because Edge Functions run first. Use Angular’s own redirects.
Your SvelteKit redirects don’t fire. SvelteKit doesn’t support redirects in netlify.toml. Use _redirects.
Your SvelteKit build fails with split and edge together. They’re mutually exclusive. Pick one.
You switched React Router back from edge and it broke. Removing edge: true isn’t enough; delete app/entry.server.tsx too.
Hugo fails with exit code: 255. A missing or mismatched HUGO_VERSION. Set it in [build.environment] to any release after 0.19. Also install themes as git submodules (git submodule add ...), not git clone.
Eleventy build plugins collide. Change node_modules in your .gitignore to **/node_modules/**. Otherwise Netlify plugins and Eleventy collide on .netlify/plugins/node_modules/ and the build errors.
Your Gatsby function exceeds the max deploy size. Set GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE to load the datastore from the CDN.
Your pnpm Nuxt 3 build fails. Set PNPM_FLAGS=--shamefully-hoist.
Reference
Client-embed prefixes (never put secrets in these)
| Framework | Prefix |
|---|---|
| Create React App | REACT_APP_ |
| Gatsby | GATSBY_ |
| Next.js | NEXT_PUBLIC_ |
| Nuxt | NUXT_PUBLIC_ |
| Vue CLI | VUE_APP_ |
| Vite / Astro / SvelteKit | VITE_, PUBLIC_ |
Env var scope requirements
| What needs the value | Scopes required |
|---|---|
| Build command, plugins | Builds |
| SSR / DSG at runtime | Functions and Builds |
| Functions, Edge Functions | Functions |
netlify.toml variables are build-time only and can’t be read by functions at runtime.
Adapter targets
| Framework | SSR target | Middleware target |
|---|---|---|
| Astro | Netlify Functions | Edge Functions |
| Next.js 13.5+ | Serverless function | Edge Function |
| SvelteKit | render function (or Deno edge with edge: true) | |
| React Router 7+ | Serverless Functions (Deno edge opt-in) | Opt-in via future.v8_middleware |
| Nuxt 3 | Nitro (Functions) | |
| SolidStart 2 | Netlify Functions | Netlify Functions |
| TanStack Start | Serverless functions | Serverless functions |
| Gatsby 5.12+ | SSR, DSG functions | |
| Angular | Edge Function | |
| Hydrogen | Edge Functions only |
Deploying via CLI
npm install netlify-cli -gnetlify initFollow the prompts to create or link the site and set build settings.
FAQs
How do I deploy Next.js to Netlify?
Push your repo and Netlify detects it, installing @netlify/plugin-nextjs automatically for Next 13.5+. Build command next build, publish .next. Don’t pin the plugin version; Netlify updates it each build.
Which frameworks does Netlify support? Detection and documented build settings cover Angular, Astro, Create React App, Eleventy, Gatsby, Hugo, Hydrogen, Next.js, Nuxt, React Router, Remix, SolidStart, SvelteKit, TanStack Start, Vite, Vue CLI, and Express.
Why are my SSR pages serving static HTML instead of rendering?
You almost certainly have a leftover SPA catch-all (/* /index.html 200). User redirects beat adapter-generated routes. Remove it.
Why isn’t my environment variable updating on the live site? Values are injected at build time. Editing a variable doesn’t reach the live site or already-deployed functions until you redeploy.
Can I test Netlify features locally without the CLI?
Yes, for Vite-based frameworks. Astro 5.12+ has it built in; Nuxt uses @netlify/nuxt; React Router, SolidStart, and Vite use @netlify/vite-plugin. That emulates functions, blobs, database, Image CDN, redirects, headers, and AI Gateway.
Do I need the Astro adapter?
For SSR or Image CDN with <Image />, yes: npx astro add netlify. For a fully static site with no server features, you can deploy without it.
How do I enable skew protection on Next.js?
Set NETLIFY_NEXT_SKEW_PROTECTION=true and redeploy. On Next.js earlier than 14.1.4 that isn’t sufficient on its own, so also set experimental.useDeploymentId (plus useDeploymentIdServerActions if you use Server Actions) in next.config.js.
Is SvelteKit fully supported for local emulation? It’s experimental. Astro, Nuxt, TanStack Start, React Router, and SolidStart are full.
Why do my Angular redirects get ignored?
Angular SSR runs in an Edge Function that executes before redirect rules, so _redirects and netlify.toml don’t apply to SSR pages. Use Angular’s built-in redirects.
Related
This article is generated from Netlify’s open-source agent guidance at netlify/context-and-tools, the same reference our AI coding agents use.
Ready to ship your framework? Start at netlify.new.