skip to content

Deployment Targets

Next on Vercel and Next in your own Docker image are the same code with very different operational stories, especially around ISR across multiple instances. Interviewers ask what you lose or must rebuild when you self-host.

part ofNext.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Next.js app you set `output: 'standalone'` in next.config and build a Docker image from the result. What does that build produce, and what do you have to copy into the runtime image yourself?

level: middleimportance: must knowfreq 62%

answer

  1. packaging mode, not a runtime change
  2. traced node_modules, generated server entry
  3. two folders left out on purpose
  4. assets 404 when you forget them
  5. small image, unchanged operational behaviour

basics

~20 s

It emits a self-contained .next/standalone folder with a server.js and only the traced node_modules the server needs, so the image runs node server.js without the full project. It does not include public or .next/static — copy those in yourself.

solid answer

~50 s

With `output: 'standalone'`, `next build` writes a `.next/standalone` directory containing a minimal `server.js`, the compiled server output, and a pruned copy of just the `node_modules` files that output file tracing found the server actually requires. That folder runs on its own with `node server.js` — no `next` CLI, no full dependency install — which is what makes the runtime layer of a Docker image small. The catch is that `public/` and `.next/static` are deliberately left out, because Next assumes you may serve them from a CDN. If you serve them from the same container you must copy both into the standalone folder in your Dockerfile. Forget that and the container boots, serves HTML, and then every `/_next/static` request 404s — an unstyled, non-interactive page. Standalone is a packaging mode only: it changes nothing about how the app renders or caches.

code

javascript · 7 lines
javascript
// next.config.js
module.exports = {
  output: 'standalone',
  // For a monorepo, widen the file-tracing root so hoisted
  // dependencies are included in .next/standalone/node_modules.
  outputFileTracingRoot: require('path').join(__dirname, '../../'),
}

go deeper

for a junior

Know that output: 'standalone' produces a folder you can run with node server.js, and that public and .next/static have to be copied in alongside it.

for a middle

Explain output file tracing, why the pruned node_modules keeps the image small, and describe the unstyled-page symptom that follows from omitting the static folders.

for a senior

Demonstrate the operational split: standalone fixes image size and cold start, while asset delivery, a shared ISR cache and image-optimization cost remain separate decisions you still have to make.

for a principal

Own the packaging standard across services — a multi-stage build, non-root runtime, tracing root set correctly for the monorepo, and a documented contract for whether the origin or a CDN serves static assets.

## The problem it solves A default `next build` leaves you needing the project directory and its installed `node_modules` in order to run `next start`. In a container that means copying the source tree and running an install for production dependencies — a large image, a slow build, and a runtime that carries far more than the server touches. `output: 'standalone'` exists to cut that down. ## What the build emits Setting `output: 'standalone'` in `next.config` makes `next build` additionally produce `.next/standalone/`, containing: - a generated `server.js` that boots a Node HTTP server for your app directly, with no dependency on the `next` CLI; - the compiled server-side build; - a pruned `node_modules` assembled by output file tracing, which walks the require/import graph from the server entry and copies only the files reached. You start it with `node server.js`. The server reads `PORT` and `HOSTNAME` from the environment, and in a container you normally set `HOSTNAME` to `0.0.0.0` so it binds outside localhost. ## What is deliberately not in it Two things are omitted on purpose: your `public/` folder and `.next/static`. Both are pure static assets, and the assumption is that many deployments put them on a CDN rather than serving them from the Node process. If your container is the origin for them, copying them in is your job: ```dockerfile COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public ``` The failure mode when you miss this is distinctive and worth recognising in an interview: the container starts cleanly, health checks pass, the HTML document arrives — and every request under `/_next/static` returns 404, so the page renders unstyled and never becomes interactive, while anything referenced out of `public` (favicon, images) is missing too. It looks like a rendering bug and is actually a packaging bug. ## Monorepos File tracing starts from a root directory it infers. In a monorepo where dependencies are hoisted above the app package, the inferred root can be too narrow and the trace misses files, producing an image that fails at require time on first request. The `outputFileTracingRoot` option in `next.config` points tracing at the workspace root so hoisted packages are included. This is a top-level config option in current Next versions; in older ones it lived under `experimental`. ## What standalone does not change This is where candidates over-read the feature. Standalone is packaging, not a different runtime: - it does not make anything static — dynamic routes still render per request; - ISR still writes its cache into the container's own filesystem, so multiple replicas still each hold a private cache; - image optimization still runs inside your Node process, consuming its CPU and memory; - middleware still runs in your server rather than on any distributed network. In other words, `output: 'standalone'` gets you a small, dependency-free image. It does not get you any of the operational behaviour a managed platform layers on top of the same build; that is a separate set of decisions about a shared cache, a CDN in front of your assets, and how you roll deploys. ## A sane image shape The common pattern is a multi-stage Dockerfile: one stage installs dependencies and runs `next build`, and a slim runtime stage copies in the three paths above and nothing else, running as a non-root user with `CMD ["node", "server.js"]`. Because the runtime stage never installs packages, the image ends up a fraction of the naive size and starts fast — which matters directly for autoscaling and for how quickly a fresh replica can start serving. ## What to say when asked Name the folder and the entry point, name the two things you must copy, and name the symptom of forgetting. Then draw the boundary explicitly: standalone solves image size and startup, and leaves cache sharing, asset delivery and image optimization cost exactly where they were.

  • You start the standalone container and the HTML loads but the page is unstyled and nothing is interactive. What do you check first?
    Whether `.next/static` was copied into the image. The standalone output excludes it, so if the container is serving assets itself, every `/_next/static` request 404s: the document renders but the CSS and the client JavaScript never arrive, so the page looks broken and never hydrates. Check the network panel for 404s under that path before suspecting the application code.
  • Why would you serve `.next/static` from a CDN instead of from the container?
    Those files are content-hashed and immutable, so they can be cached aggressively and served close to users without ever touching your origin. Moving them off the Node process removes the bulk of the request volume from it, leaves it doing only rendering work, and is exactly the split the standalone output assumes by leaving them out.
  • Does `output: 'standalone'` change whether the app can use Server Actions, middleware or ISR?
    No. It only changes how the build is packaged for shipping. The app is still a full Next server, so Server Actions, middleware, route handlers and ISR all work as usual — the operational questions they raise, such as sharing the ISR cache between replicas, are unchanged and still yours to solve.

saying these in an interview costs you the question

  • Assumes the standalone folder already contains public and .next/static
  • Thinks standalone output makes the app static
  • Believes standalone removes the need for a shared ISR cache
  • Says you still need next start inside the container
  • Expects npm install in the runtime stage of the image

context

open as a page

In a Next.js project, what is the difference between running `next dev` and running `next build` followed by `next start`, and why can you not judge caching or bundle size from `next dev`?

level: juniorimportance: should knowfreq 60%

basics

~20 s

next dev compiles routes on demand with optimizations and caching turned off for fast feedback. next build produces the optimized production artifacts once, and next start serves them without compiling. Only the build-and-start pair reflects real caching, prerendering and bundle sizes.

open as a page

A Next.js App Router team sets `output: 'export'` in next.config so the app can be deployed to a plain static file host. Which capabilities stop working, and what has to change in the code?

level: middleimportance: should knowfreq 50%

basics

~20 s

Static export emits plain files into out/ with no server at runtime, so anything needing a request or a live server is gone: middleware, Server Actions, cookies/headers, draft mode, ISR and revalidation, and the default image optimizer. Every dynamic route must be enumerated at build time.

open as a page

A self-hosted Next.js App Router app runs three container replicas behind a load balancer. An ISR page shows fresh data on some refreshes and stale data on others, and a `revalidateTag` call after a mutation seems to work only sometimes. What is happening, and how do you fix it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Each replica keeps its own cache on its own filesystem, so the three containers hold independent copies of the page and revalidation only invalidates the replica that handled the request. Fix it by pointing all replicas at one shared cache through a custom cache handler.

open as a page

Your team is deciding whether to run a Next.js App Router app on a managed Next.js platform or self-host it as a container on your own infrastructure. What responsibilities move to you when you self-host, and how would you make the call?

level: principalimportance: nice to knowfreq 36%

basics

~20 s

Self-hosting runs identical application code but hands you the operational layer a managed platform supplied: a shared regeneration cache, static asset delivery, the image optimizer's cost, deploy atomicity and observability. Decide on constraints and team capacity, not on framework features.

open as a page