Production behaviour (caching, scaling, streaming) depends on how and where the app is deployed.
Deployment targets:
- Node.js server:
next buildthennext start. All features work. Put a reverse proxy (such as nginx) in front for TLS, compression and slow-client protection. - Docker: set
output: 'standalone'so the build produces a minimal server (.next/standalone/server.js) with only the needednode_modules; copypublic/and.next/staticnext to it. - Static export:
output: 'export'writes plain HTML/JS toout/for any static host, but drops server features (actions, runtime rendering, image optimisation by default, Proxy). - Platforms: Vercel supports everything natively; other platforms integrate through deployment adapters.
Runtime: pages, Route Handlers and Proxy run on Node.js. The older Edge runtime (export const runtime = 'edge') is deprecated, and with Cache Components the runtime export isn't supported at all, so remove it when you meet it in older code.
Caching across instances: prerendered HTML lives on disk and 'use cache' entries in memory per instance. With several instances, configure a shared cache handler (cacheHandlers config, 'use cache: remote') and a stable deployment ID and NEXT_SERVER_ACTIONS_ENCRYPTION_KEY.
`after(fn)` from next/server runs work after the response is sent (logging, analytics) without delaying users.
Version skew: clients on an old build may request assets or actions from a new one; a consistent deploymentId lets Next.js detect this and hard-reload.
Example
# Dockerfile (standalone output)
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # next.config.ts has output: 'standalone'
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=3000
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"]
# app/api/checkout/route.ts - log after responding
# import { after } from 'next/server'
# export async function POST(req: Request) {
# const order = await createOrder(await req.json())
# after(() => analytics.track('order', { id: order.id })) // runs after the response
# return Response.json(order, { status: 201 })
# }Remember
next starton Node.js supports everything;output: 'standalone'suits containersoutput: 'export'produces static files but drops server features- Multi-instance setups need a shared cache handler, deployment ID and stable action key
after()runs non-critical work after the response
Watch out for
- Running several self-hosted instances with the default in-memory cache and per-build keys, causing inconsistent caches and failed actions.