Skip to main content
When self-hosting Next.js, you have several deployment options depending on your infrastructure. This guide covers configuration for each approach.

Reverse proxy

Place a reverse proxy (such as Nginx) in front of your Next.js server rather than exposing it directly to the internet. A proxy handles malformed requests, rate limiting, payload size limits, and security concerns, freeing the Next.js server to focus on rendering.

Image optimization

next/image works with zero configuration when running next start. To use a separate image optimization service, configure a custom image loader.
On glibc-based Linux systems, image optimization may require additional configuration to prevent excessive memory usage.

Environment variables

Server environment variables are available at runtime during dynamic rendering:
import { connection } from 'next/server'

export default async function Component() {
  await connection()
  // Evaluated at runtime, not build time
  const value = process.env.MY_VALUE
  // ...
}
This allows a single Docker image to be promoted through multiple environments with different values.

Caching and ISR

By default, the Next.js cache is stored on the filesystem. This works automatically when self-hosting with both the Pages and App Router.

Configuring caching

For distributed deployments (e.g., Kubernetes with multiple pods), configure a custom cache handler to share cache across instances:
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // disable default in-memory caching
}
const cache = new Map()

module.exports = class CacheHandler {
  constructor(options) {
    this.options = options
  }

  async get(key) {
    return cache.get(key)
  }

  async set(key, data, ctx) {
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }

  async revalidateTag(tags) {
    tags = [tags].flat()
    for (let [key, value] of cache) {
      if (value.tags.some((tag) => tags.includes(tag))) {
        cache.delete(key)
      }
    }
  }

  resetRequestCache() {}
}
You can store cached values in external storage like Redis or AWS S3 for consistency across pods.

Build cache

Use a consistent build ID across containers to avoid stale assets:
module.exports = {
  generateBuildId: async () => {
    return process.env.GIT_HASH
  },
}

Multi-server deployments

Server Actions encryption key

When running multiple server instances, all must use the same encryption key for Server Actions. Set a consistent key via environment variable:
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=your-generated-key next build
The key must be a base64-encoded value with a valid AES key length (16, 24, or 32 bytes).

Deployment identifier

Configure a deploymentId to enable version skew protection during rolling deployments:
module.exports = {
  deploymentId: process.env.DEPLOYMENT_VERSION,
}
When a deployment ID is configured, Next.js includes it in asset URLs and navigation requests. If a mismatch is detected between client and server, Next.js triggers a full page reload to ensure clients receive consistent assets.

Shared cache

By default, the in-memory cache is not shared across instances. Use 'use cache: remote' with a custom cache handler to store data in external storage.

Streaming and Suspense

The App Router supports streaming responses when self-hosting. If using Nginx, disable buffering to enable streaming:
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*{/}?',
        headers: [{ key: 'X-Accel-Buffering', value: 'no' }],
      },
    ]
  },
}

Docker deployment

A typical Next.js Dockerfile uses standalone output to minimize image size:
1

Enable standalone output

module.exports = {
  output: 'standalone',
}
2

Build the Docker image

FROM node:18-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
3

Run the container

docker build -t my-next-app .
docker run -p 3000:3000 my-next-app

after()

The after function is fully supported when self-hosting with next start. When stopping the server, send SIGINT or SIGTERM signals and wait for pending callbacks to complete.

Build docs developers (and LLMs) love