Next.js

Verified against Claude Code · 2026-07-22

Package a Next.js app for self-hosted deployment without guessing at a Dockerfile

Sets up output: 'standalone' with a multi-stage Dockerfile sized to what the trace-based build actually includes, and names the ISR cache-persistence problem a naive container setup silently gets wrong.

Claude CodeCursorGitHub Copilot4 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are packaging a Next.js app for self-hosted deployment in a container, using output: 'standalone' in next.config so the resulting image ships only what the app actually traces as needed at runtime, rather than the entire node_modules tree or a Dockerfile improvised from a generic Node.js template.

APP DETAILS
A Next.js 16 App Router app using the pnpm package manager, with a mix of static and ISR-revalidated pages

DEPENDENCIES WITH NATIVE OR BUILD-STEP REQUIREMENTS
sharp for image processing, which needs its native binary matched to the container's target platform architecture

CACHING NEEDS
Will run as 2-3 horizontally scaled replicas behind a load balancer, so per-replica ISR cache regeneration is a real concern, not a hypothetical

CONTAINER PLATFORM
A self-managed Kubernetes cluster

BUILD RULES
Set output: 'standalone' in next.config so the build produces a minimal, traced .next/standalone directory containing only the files and dependencies the app actually needs at runtime, then structure the Dockerfile as a genuine multi-stage build: one stage installs dependencies and runs the build, a second, much smaller final stage copies only .next/standalone, .next/static, and the public directory into a lean base image, rather than copying the entire repository including devDependencies and source files that have no runtime purpose into the final image. Run the container as a non-root user in the final stage, and explicitly copy in and use whatever Node.js version matches what the app was built and tested against, rather than floating on whatever "latest" tag a base image happens to resolve to at build time, since an unpinned base image can silently change the app's runtime environment between builds with no code change at all. Address the ISR cache-persistence question directly rather than leaving it implicit: a standalone container's filesystem cache for revalidated pages lives inside that specific container instance, so a fresh container — from a restart, a redeploy, or a horizontally scaled second instance — starts with an empty cache and has to regenerate every ISR page from scratch on its next request, which is a real, working behavior but a different one than a long-running single server would have, and if this container needs to run as multiple replicas, either accept that each replica regenerates its own cache independently, or configure a custom cache handler backed by a shared store such as Redis so all replicas share one cache instead of each maintaining its own. Confirm which environment variables the app needs at build time versus runtime, since a value baked into the standalone build during the image build stage cannot be changed later just by setting a different environment variable when the container starts — if a variable genuinely needs to differ per deployment without rebuilding the image, it has to be read at runtime inside the app, not consumed only during the build.

OUTPUT FORMAT
The complete multi-stage Dockerfile, the exact next.config.ts change, and one paragraph specifically addressing what happens to ISR-cached pages across a container restart or a horizontally scaled second replica, given the caching needs stated above.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

output: 'standalone' works by statically tracing the app's actual runtime dependency graph and copying only what's genuinely reachable at runtime into a minimal output directory, which is meaningfully different from a Dockerfile that just copies the whole repository including node_modules — the traced output is dramatically smaller, and a multi-stage Dockerfile that copies only .next/standalone, .next/static, and public into its final stage is what actually captures that size benefit; a Dockerfile that copies everything into a single stage regardless of standalone output defeats the entire point of tracing, since the final image ends up carrying the same devDependencies and unused files the traced build was specifically designed to exclude. Pinning the Node.js version explicitly rather than floating on a "latest" base-image tag closes a specific and hard-to-diagnose class of bug: an image built today and an identical-looking image built next month from the same Dockerfile can end up running on genuinely different Node.js versions if the base image's latest tag moved in between, which means a container that behaved one way in staging can behave differently in production for a reason that has nothing to do with any actual code change, and is very difficult to trace back to "the base image resolved differently" without already suspecting that specifically. The ISR cache-persistence detail is the one that's easy to miss entirely because a single-container local test doesn't expose it at all — a container running as one long-lived instance behaves exactly like a traditional long-running Next.js server would, and the cache-loss-on-restart and cache-fragmentation-across-replicas problems only become visible once the app is actually deployed at the replica count and restart frequency it's designed to run at, by which point it's a production incident report about a page occasionally showing stale or inconsistent content rather than a design decision made deliberately during setup. Separating build-time from runtime environment variable resolution matters here for the same reason it matters in any deployment context, but it's specifically sharp-edged in a container workflow, where "just change the environment variable and restart" is the instinctive fix for a config problem, and that instinct is simply wrong for any variable the standalone build already consumed and baked in during the image build stage.

What you get back

# Stage 1: deps + build FROM node:20-slim AS builder WORKDIR /app COPY package.json pnpm-lock.yaml ./ RUN corepack enable && pnpm install --frozen-lockfile COPY . . RUN pnpm build # Stage 2: minimal runtime FROM node:20-slim AS runner WORKDIR /app RUN addgroup --system nodejs && adduser --system nextjs --ingroup nodejs COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public USER nextjs EXPOSE 3000 CMD ["node", "server.js"] ISR caching: with 2-3 replicas behind a load balancer, each container's filesystem cache is independent — a restart or a request routed to a different replica can regenerate the same ISR page redundantly rather than reusing a cache another replica already has. Given the stated multi-replica need, this setup should move to a custom cache handler backed by Redis so all replicas share one cache, rather than accepting per-replica regeneration as a permanent tradeoff.

Verified against

Claude Code Sonnet 4.6 · 2026-07-22

GitHub Copilot 2026.7 · 2026-08-01

Changelog

  • 2026-07-22 Initial publish, verified against Claude Code (Sonnet 4.6) and GitHub Copilot packaging a Next.js 16 app with output: 'standalone' for a multi-replica deployment.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Next.js prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY