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.

Deploy Guide

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 200 when 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 200

Remove 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.

FrameworkBuild commandPublish
Angular (standard)ng build --proddist/YOUR_PROJECT_NAME
Astroastro builddist
Create React Appreact-scripts buildbuild
Eleventyeleventy_site
Gatsbygatsby buildpublic
Hugohugopublic
Hydrogenremix vite:builddist/client
Next.js (SSR/hybrid)next build.next
Next.js (static export)next build && next exportout (NETLIFY_NEXT_PLUGIN_SKIP=true)
Nuxt 3nuxt builddist
Nuxt 2nuxt generatedist
React Routerreact-router buildbuild/client
Remix (Vite)remix vite:buildbuild/client
SolidStart 2 (Vite plugin)vite builddist/client
SolidStart 2 (Nitro)vite builddist
SolidStart 1.xvinxi builddist
SvelteKitvite buildbuild
TanStack Start (1.132.0+)vite builddist/client
Vitevite builddist
Vue CLIvue-cli-service builddist

Adapter setup by framework

Astro

npx astro add netlify

That 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-netlify
import 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: true gives one function per route, and is incompatible with Edge Functions (set edge: false or omit it).
  • edge: true runs SSR in a Deno edge function, and can’t be combined with split.
  • Redirects are not supported in netlify.toml for SvelteKit. Use _redirects.
  • Edge functions don’t work locally with netlify dev for SvelteKit.

React Router (7+)

New project:

npx create-react-router@latest --template netlify/react-router-template

Existing 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-adapter

Add 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/image uses Netlify Image CDN automatically. Set remote domains in nuxt.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-start

Add 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. Generates SSR and DSG functions. No Essential Gatsby plugin needed.
  • 5.11.0 or earlier (Essential Gatsby plugin): auto-installs @netlify/plugin-gatsby, and you also need to manually install gatsby-plugin-netlify for SSR, Gatsby redirects, and asset caching. Generates __api, __ssr, __dsg, __ipx. Skip them with NETLIFY_SKIP_GATSBY_FUNCTIONS for 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/express
netlify/functions/api.ts
import 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-template
cp .env.example .env && npm run dev

Local 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.

FrameworkPlugin or moduleRun
Astro (5.12+)built-in (Netlify Vite plugin auto-loaded)astro dev
Nuxt@netlify/nuxtnuxt dev
React Router@netlify/vite-pluginreact-router dev
SolidStart 2@netlify/vite-pluginvite dev
TanStack Start@netlify/vite-plugin-tanstack-start(vite)
Vite@netlify/vite-pluginnpx 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)

FrameworkPrefix
Create React AppREACT_APP_
GatsbyGATSBY_
Next.jsNEXT_PUBLIC_
NuxtNUXT_PUBLIC_
Vue CLIVUE_APP_
Vite / Astro / SvelteKitVITE_, PUBLIC_

Env var scope requirements

What needs the valueScopes required
Build command, pluginsBuilds
SSR / DSG at runtimeFunctions and Builds
Functions, Edge FunctionsFunctions

netlify.toml variables are build-time only and can’t be read by functions at runtime.

Adapter targets

FrameworkSSR targetMiddleware target
AstroNetlify FunctionsEdge Functions
Next.js 13.5+Serverless functionEdge Function
SvelteKitrender function (or Deno edge with edge: true)
React Router 7+Serverless Functions (Deno edge opt-in)Opt-in via future.v8_middleware
Nuxt 3Nitro (Functions)
SolidStart 2Netlify FunctionsNetlify Functions
TanStack StartServerless functionsServerless functions
Gatsby 5.12+SSR, DSG functions
AngularEdge Function
HydrogenEdge Functions only

Deploying via CLI

npm install netlify-cli -g
netlify init

Follow 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.

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.