Building and deploying
What next build produces, how to read the route summary, and the deployment targets for a Node server, a container or static hosting.
What the build tells you
npx create-next-app@latest my-app --ts --app --eslint
npm run dev # development server with fast refresh
npm run build # production build; reports route types and sizes
npm start # serve the production build on a Node server
# a self-contained server bundle for containers
# next.config.js -> output: 'standalone'The build output marks each route as static, dynamic or a server function, and shows the first-load JavaScript for each. Read that table after every change: an accidentally dynamic page and a growing bundle are both visible there before users notice them.
| Route type | Rendered | Typical cause |
|---|---|---|
| Static | At build time, cached | No request-specific data |
| Dynamic | Per request | Cookies, headers, search params, no-store |
| ISR / revalidated | Cached then refreshed | revalidate on a fetch or route segment |
| Route handler | On demand | route.js endpoint |
generateStaticParamsprerenders a known set of dynamic routes; anything not listed is rendered on demand.next/imageoptimises and lazy-loads images, and needs allowed remote hosts in the config.next/fontself-hosts fonts at build time, which removes a render-blocking request and a layout shift.
Configuration that matters
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // minimal server bundle for Docker
images: {
remotePatterns: [{ protocol: 'https', hostname: 'images.example.com' }]
},
env: { SITE_NAME: 'Guides' }, // inlined at build time - not for secrets
async redirects() {
return [{ source: '/old-guide', destination: '/guides/intro', permanent: true }]
}
}
module.exports = nextConfig- Environment variables are read at build time for anything inlined and at runtime for server code — a container built with one set of values will not pick up new ones without a rebuild if the value was inlined.
- Prefix with
NEXT_PUBLIC_only what the browser may see; keep the rest server-side. redirectsandrewritesin the config are evaluated before routing to your pages, which is the cheapest place to handle a URL migration.output: 'standalone'traces the files the server actually imports, so a container image does not need your wholenode_modules.
Where to run it
| Target | Build output | Notes |
|---|---|---|
| Managed platform (Vercel and similar) | next build | Zero config; static assets on a CDN, functions per route |
| Node server on a VPS | next build + next start | You manage processes, TLS and a reverse proxy |
| Container (Docker, Kubernetes) | output: 'standalone' | Copy .next/standalone plus .next/static and run server.js |
| Static export | output: 'export' | No server features: no route handlers, no server actions, no on-demand revalidation |
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]⚠️
Static export removes anything that needs a server: route handlers, server actions, and on-demand revalidation all stop working. Decide that trade-off before you build the site around them, not when the deploy fails.
FAQ
Why does my build fail with a window or document reference?
A client-only API ran during server rendering. Add
'use client' to that component and move the access into an effect, or guard the render with a mounted flag.Should I use static export for a content site?
If every page can be prerendered at build time and you have no per-request behaviour, yes — it is cheaper and simpler. The moment you need middleware, cookies or server actions, choose a server target instead.
Related
Data fetching and server components Effects and data fetching
Last refreshed 2026-09-18.