cf.completefrontend
Open lab My progress
THE NEXT.JS FIELD GUIDE

Inside next build

Follow next build through its source: route collection, Turbopack, the type check, prerendering in workers, the manifests, .next and file traces.

By the end, you can
  • 01
    Name the steps of next buildRead the build's output and its .next/trace file, and find each step in build/index.ts at v16.4.3.
  • 02
    Explain the worker poolSay how many workers the build starts, how it splits the pages among them, and why a failed page stops the build.
  • 03
    Read the .next folderFind what each manifest says and what reads it at runtime, and how .nft.json traces become the standalone folder.

One build of the tea shop, from start to finish

next build turns your project into an app that next start can serve. Earlier lessons read what it prints. Its source shows how it works: the code in Next.js that runs when you type the command, and the files it leaves behind. The source quoted is Next.js 16.4.3's, from GitHub at the tag v16.4.3.

The running example is the tea shop: a home page, a page for each tea at /teas/[id] with an Add to cart button, and a cart page that reads a cookie. Here it is, built with next build. The Terminal tab opens first. Under what the build printed, it lists what the .next folder held afterwards: each file up to two levels down, with deeper files added up into their folder.

The tea shop, builtNext.js
app/cart/teas/[id]/
app/teas/[id]/page.tsx
import { notFound } from "next/navigation";import { AddToCart } from "../../add-to-cart";import { findTea, teas } from "../../teas"; export function generateStaticParams() {  return teas.map((tea) => ({ id: tea.id }));} export default async function TeaPage({ params }: PageProps<"/teas/[id]">) {  const { id } = await params;  const tea = findTea(id);  if (!tea) notFound();  return (    <main>      <h1>{tea.name}</h1>      <p>${tea.price}</p>      <AddToCart id={tea.id} />    </main>  );}
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (7/7)  Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found├ ◐ /cart└   /teas/[id]  ├ ◐ /teas/[id]  ├ ○ /teas/green  └ ○ /teas/mint ○  (Static)             prerendered as static content◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content # What .next holds: 228 files, 29.0 MB (deeper folders added up).next/BUILD_ID  21 bytes.next/app-path-routes-manifest.json  158 bytes.next/build-manifest.json  2.3 kB.next/cache/.previewinfo  262 bytes.next/cache/.rscinfo  102 bytes.next/cache/.tsbuildinfo  121.5 kB.next/cache/turbopack/  23.2 MB, 20 files.next/diagnostics/build-diagnostics.json  93 bytes.next/diagnostics/framework.json  37 bytes.next/diagnostics/route-bundle-stats.json  1.8 kB.next/export-marker.json  111 bytes.next/fallback-build-manifest.json  366 bytes.next/images-manifest.json  1.2 kB.next/next-minimal-server.js.nft.json  9.8 kB.next/next-server.js.nft.json  91.1 kB.next/package.json  20 bytes.next/prerender-manifest.json  9.5 kB.next/required-server-files.js  10.8 kB.next/required-server-files.json  10.8 kB.next/routes-manifest.json  2.3 kB.next/server/app-paths-manifest.json  212 bytes.next/server/app/  229.5 kB, 85 files.next/server/chunks/  4.7 MB, 70 files.next/server/functions-config-manifest.json  37 bytes.next/server/interception-route-rewrite-manifest.js  48 bytes.next/server/middleware-build-manifest.js  2.3 kB.next/server/middleware-manifest.json  83 bytes.next/server/next-font-manifest.js  135 bytes.next/server/next-font-manifest.json  94 bytes.next/server/pages-manifest.json  58 bytes.next/server/pages/  17.1 kB, 2 files.next/server/prefetch-hints.json  1.1 kB.next/server/preview-props.json  250 bytes.next/server/server-reference-manifest.js  422 bytes.next/server/server-reference-manifest.json  352 bytes.next/static/Ck2PLfmuLay5_FTZYdHZb/  417 bytes, 3 files.next/static/chunks/  593.0 kB, 10 files.next/trace  6.6 kB.next/trace-build  1.8 kB.next/turbopack  0 bytes.next/types/cache-life.d.ts  4.6 kB.next/types/root-params.d.ts  101 bytes.next/types/routes.d.ts  1.5 kB.next/types/validator.ts  5.3 kB

Each line the build printed is one step. Creating an optimized production build is the compile. Running TypeScript is the type check. Collecting page data asks each route what it is. Generating static pages (7/7) made 7 pages ahead of time. The route table comes last. The output above has no times, no worker counts and no page counter (the line a terminal draws over itself): they change from run to run, so they were removed.

The folder listing is the other half of the story. A manifest is a file that lists what the build made, so the server doesn't have to work it out again: routes-manifest.json, prerender-manifest.json and others. server/ holds the code that runs on the server, static/ the files the browser downloads, and cache/ what the next build can reuse. The steps, in order:

The eight steps of next build, in two rows. First row: 1 settings, 2 routes, 3 compile, marked Turbopack, 4 types, marked TypeScript. An arrow leads to the second row: 5 page data and 6 prerender, both shaded and marked the worker pool, then 7 manifests and 8 route table.1 settings2 routes3 compile4 typesTurbopackTypeScript5 page data6 prerender7 manifests8 route tablein the worker poolback in the main process
next build runs these steps one after another, in build/index.ts. Turbopack does step 3. Steps 5 and 6 send work to a pool of worker processes; the main process does the rest.
Real-life analogyA bakery, the night before

A bakery prepares at night so the morning shift can sell. Someone reads the order book, the dough is mixed, the recipes are checked, and several bakers bake trays at the same time. Last, someone writes a sheet that says what is on which shelf. In the morning, the shop staff don't bake or read the order book again: they read the sheet and hand out what's ready.

In real life: Reading tomorrow's order book
In Next.js: Loading .env, next.config.ts and the routes in app
In real life: Mixing every dough
In Next.js: Turbopack compiling every route
In real life: Checking each recipe against the rules
In Next.js: TypeScript checking the types
In real life: Bakers baking trays side by side
In Next.js: Workers prerendering pages
In real life: The sheet that says what's on which shelf
In Next.js: The manifests in .next
The main idea

next build collects your routes, compiles them with Turbopack, checks the types, prerenders what it can in a pool of worker processes, and writes manifests that describe the result. next start builds nothing: it reads those manifests and serves the files beside them. The file names and shapes in .next are this version's internals, not an API your code should use.

The steps of next build, in build/index.ts

next build starts in the CLI, which calls one function: build, at line 1116 of build/index.ts. The file is almost 5,000 lines long, but the function reads from top to bottom, one step after another. Each step is wrapped in a span: a named piece of work with a start time and a length, made by nextBuildSpan.traceChild("…"). Next.js writes the spans into a file you can open yourself: .next/trace. These are the tea shop's recorded top-level spans, the ones directly under next-build, in the order they started:

.next/trace: the recorded top-level spans, in the order they started (lengths left out)Text
generate-buildid
load-custom-routes
create-dist-dir
clean
discover-routes
create-root-mapping
generate-route-types
public-dir-conflict-check
generate-routes-manifest
run-turbopack
turbopack-write-entrypoints
turbopack-module-graph
turbopack-emit
turbopack-persistence
run-typescript
generate-required-server-files
static-check
write-routes-manifest
static-generation
next-export
print-tree-view
write-route-bundle-stats
telemetry-flush

Each name is a traceChild in build/index.ts, so you can search the file for it. Loading the settings is an earlier phase that the file doesn't show: its spans, load-dotenv and load-next-config, end before Next.js knows where .next is, and the trace writer drops a span when it doesn't know that yet (to-json.ts, lines 186 to 190), and build only tells it after the config is loaded, at lines 1245 and 1246. The settings phase first, then the spans in order, give the steps of the picture above:

  1. Settings, before the trace starts. load-dotenv reads the .env files, then loadConfig reads next.config.ts (lines 1170 to 1195). Then, in the trace, generate-buildid makes up this build's id, and load-custom-routes reads the redirects, rewrites and headers.
  2. A clean folder. clean deletes .next, but keeps cache, dev, diagnostics, lock and trace (lines 1379 to 1388).
  3. Routes. discover-routes walks the app folder for page and route files. generate-route-types writes the types behind PageProps<"/teas/[id]"> into .next/types, and generate-routes-manifest builds the routes manifest in memory, before anything is compiled (lines 1784 to 1800).
  4. Compile. run-turbopack and the four turbopack- spans under it: Turbopack, Next.js's bundler, compiles every route.
  5. Types. run-typescript.
  6. Page data. generate-required-server-files writes the server's settings, then static-check asks the worker pool about each route.
  7. Prerender. static-generation, and next-export inside it.
  8. Manifests and the table. write-routes-manifest appears twice, because the build writes that file, then updates it after prerendering. print-tree-view prints the route table.

The compile is one call. This is how build/index.ts makes it, shortened:

build/index.ts, lines 1879 to 1905, shortened (… marks lines left out)TypeScript
Log.info('Creating an optimized production build ...')
…
if (!isGenerateMode) {
  if (bundler === Bundler.Turbopack) {
    const {
      duration: compilerDuration,
      shutdownPromise: p,
      warnings,
      ...rest
    } = await turbopackBuild(telemetry)
    …
    const durationString = durationToString(compilerDuration)
    Log.event(`Compiled successfully in ${durationString}`)

Line 1 prints Creating an optimized production build .... Line 4 checks the bundler: Turbopack, unless you ran next build --webpack. Line 10 hands the whole compile to turbopackBuild, in turbopack-build/impl.ts, which starts Turbopack's Rust code and asks it to write every route to disk: the server's code, the browser's chunks, and a small manifest for each route. Line 13 prints ✓ Compiled successfully, with the time the recording leaves out. What Turbopack does inside that call is in SWC and Turbopack.

And this is the type check, right after it:

build/index.ts, lines 2049 to 2056TypeScript
// For app directory, we run type checking after build.
if (appDir && !isCompileMode && !isGenerateMode) {
  await updateBuildDiagnostics({
    buildStage: 'type-checking',
  })
  await startTypeChecking(typeCheckingOptions)
  traceMemoryUsage('Finished type checking', nextBuildSpan)
}

Line 1 says it plainly: with an app folder, types are checked after the compile. Line 6 runs TypeScript. Turbopack only removes types from your code; it never checks them. So a type error doesn't stop the compile, and the build stops one step later. Each row of this table is one step, what it does, and what it leaves in .next:

The steps of next build
StepWhat it doesWhat it leaves in .next
Load the settingsReads the .env files and next.config.ts, then the redirects, rewrites and headers they define.Nothing yet: the settings say where .next is.
Collect the routesWalks app and lists every page.tsx and route.ts, then writes the route types and builds the routes manifest in memory.types/routes.d.ts and types/validator.ts.
CompileTurbopack compiles every route for the server and the browser and writes the code and the per-route manifests.server/app/…/page.js, server/chunks/, static/chunks/, the per-route manifests.
Check the typesRuns TypeScript over the project, after compiling, for an app with an app folder.cache/.tsbuildinfo, which the next check reuses.
Collect page dataStarts the worker pool and asks it about each route: is it static, and which paths does generateStaticParams list.required-server-files.json, written right before it.
PrerenderThe workers render each path they can make ahead of time.The .html, .rsc, .meta and segment files in server/app.
Write the manifestsWrites what the server needs to answer requests: the routes, the prerendered pages, the build's id.routes-manifest.json, prerender-manifest.json, BUILD_ID, and more.
Print the route tablePrints each route with its symbol, and the legend.diagnostics/route-bundle-stats.json.

The order shows best when a build fails. Each version below breaks one thing in the tea shop, and each was built with real Next.js. Before you click, guess which line each build stops at.

Playground: where does the build stop?
The tea shop, in this versionNext.js
app/cart/teas/[id]/
app/teas/[id]/page.tsx
import { notFound } from "next/navigation";import { AddToCart } from "../../add-to-cart";import { findTea, teas } from "../../teas"; export function generateStaticParams() {  return teas.map((tea) => ({ id: tea.id }));} export default async function TeaPage({ params }: PageProps<"/teas/[id]">) {  const { id } = await params;  const tea = findTea(id);  if (!tea) notFound();  return (    <main>      <h1>{tea.name}</h1>      <p>${tea.price}</p>      <AddToCart id={tea.id} />    </main>  );}
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (7/7)  Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found├ ◐ /cart└   /teas/[id]  ├ ◐ /teas/[id]  ├ ○ /teas/green  └ ○ /teas/mint ○  (Static)             prerendered as static content◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content # What .next holds: 228 files, 29.0 MB (deeper folders added up).next/BUILD_ID  21 bytes.next/app-path-routes-manifest.json  158 bytes.next/build-manifest.json  2.3 kB.next/cache/.previewinfo  262 bytes.next/cache/.rscinfo  102 bytes.next/cache/.tsbuildinfo  121.5 kB.next/cache/turbopack/  23.2 MB, 20 files.next/diagnostics/build-diagnostics.json  93 bytes.next/diagnostics/framework.json  37 bytes.next/diagnostics/route-bundle-stats.json  1.8 kB.next/export-marker.json  111 bytes.next/fallback-build-manifest.json  366 bytes.next/images-manifest.json  1.2 kB.next/next-minimal-server.js.nft.json  9.8 kB.next/next-server.js.nft.json  91.1 kB.next/package.json  20 bytes.next/prerender-manifest.json  9.5 kB.next/required-server-files.js  10.8 kB.next/required-server-files.json  10.8 kB.next/routes-manifest.json  2.3 kB.next/server/app-paths-manifest.json  212 bytes.next/server/app/  229.5 kB, 85 files.next/server/chunks/  4.7 MB, 70 files.next/server/functions-config-manifest.json  37 bytes.next/server/interception-route-rewrite-manifest.js  48 bytes.next/server/middleware-build-manifest.js  2.3 kB.next/server/middleware-manifest.json  83 bytes.next/server/next-font-manifest.js  135 bytes.next/server/next-font-manifest.json  94 bytes.next/server/pages-manifest.json  58 bytes.next/server/pages/  17.1 kB, 2 files.next/server/prefetch-hints.json  1.1 kB.next/server/preview-props.json  250 bytes.next/server/server-reference-manifest.js  422 bytes.next/server/server-reference-manifest.json  352 bytes.next/static/Ck2PLfmuLay5_FTZYdHZb/  417 bytes, 3 files.next/static/chunks/  593.0 kB, 10 files.next/trace  6.6 kB.next/trace-build  1.8 kB.next/turbopack  0 bytes.next/types/cache-life.d.ts  4.6 kB.next/types/root-params.d.ts  101 bytes.next/types/routes.d.ts  1.5 kB.next/types/validator.ts  5.3 kB
Try it yourself
The shop as it is

Every step runs: Turbopack compiles, TypeScript finds no errors, the pages are collected and prerendered, and the route table comes last.

Every version was built with next build from Next.js 16.4.3, and the Terminal shows what it printed, with the times, the worker counts and the page counter left out. Reset goes back to the shop as it is.

A missing file stops the compile: Turbopack can't follow the import, and nothing after it runs. The type error compiles, then stops at Running TypeScript, with (17,18): line 17, column 18. The page that throws gets through both, and fails while a worker prerenders /teas/oolong. Each failure stops at the first step that can see it.

Prerendering in a worker pool

A process is a program that's running, with its own memory. next build is one Node.js process, the main process. Rendering many pages in it, one after another, would leave the computer's other cores idle. So after the compile, the build starts more Node.js processes, called workers, and sends them pages to render. The group of workers is a pool.

createStaticWorker, at lines 1010 to 1057 of build/index.ts, makes the pool. Each worker loads build/worker.ts, which offers four functions to the main process: hasCustomGetInitialProps, isPageStatic, getDefinedNamedExports and exportPages. The first three answer Collecting page data. exportPages, from export/worker.ts, prerenders. The pool is built on jest-worker, a library that runs a file in several processes and spreads calls across them. Next.js uses child processes unless you turn on experimental.workerThreads (lib/worker.ts, lines 163 to 172).

How many workers

The build sizes the pool once, after the type check: getNumberOfWorkers(config, totalPageCount), at lines 2256 to 2261. The setting that counts is experimental.cpus, whose default is the computer's cores minus one (config-shared.ts, lines 2422 to 2426). Then the pool gets at most one worker more than there are routes: an app with 5 routes never starts more than 6 workers, however many cores the computer has.

How the pages are split

For prerendering, the paths are split into batches, and each batch goes to one worker: exportPagesInBatches, at lines 692 to 745 of export/index.ts. The number of batches is the paths divided by 25, rounded up (staticGenerationMinPagesPerWorker), capped at the number of workers (lines 706 to 709). Then the paths are cut, in order, into slices of the same size, one per batch, the last one sometimes shorter (lines 712 to 722). So 25 is the target that picks how many batches there are, not the size of a batch: 7 paths make 1 batch, 26 paths make 2 batches of 13, and 120 paths with 5 workers make 5 batches of 24. Inside a worker, exportPages renders up to 8 paths at a time (staticGenerationMaxConcurrency, export/worker.ts, lines 432 and 433). The tea shop on a computer with 8 cores:

Left, a terminal window for next build, the main process, holding seven paths: slash, not-found, global-error, cart, teas green, teas mint and teas id. An arrow carries them as one batch to worker 1 on the right, which is shaded. Workers 2 to 6 sit beside it with no batch.next build//_not-found/_global-error/cart/teas/green/teas/mint/teas/[id]one batchworker 1worker 2worker 3worker 4worker 5worker 6workers 2 to 6: no batchthe pool: 6 child processes
The tea shop on a computer with 8 cores: a pool of 6 workers (5 routes plus one), and 7 paths to prerender. The number of batches is 7 ÷ 25, rounded up and capped at the 6 workers: 1. So all 7 go to worker 1, which renders up to 8 at a time. In the step before, Collecting page data, each route is a separate call, so the pool can share that work.

Move the sliders. Start with the tea shop, then give it 120 paths: when does a second worker get a batch? Then give it 2 routes: what happens to the pool?

Playground: how many workers prerender the pages?
build/index.ts: getNumberOfWorkers, shortenedTypeScript
function getNumberOfWorkers(config: NextConfigComplete, maxTasks?: number) {
  let workers: number
  …
  } else if (config.experimental.cpus) {
    workers = config.experimental.cpus
  }
  …
  // Cap workers to one more than the number of tasks.
  // Tasks can be passed to avoid over-allocating workers.
  if (maxTasks !== undefined && maxTasks > 0) {
    workers = Math.min(workers, maxTasks + 1)
  }

  return Math.max(workers, 1)
}
export/index.ts: exportPagesInBatches, lines 701 to 712TypeScript
const minPageCountPerBatch =
  nextConfig.experimental.staticGenerationMinPagesPerWorker ?? 25

// Calculate the number of workers needed to ensure each batch has at least
// minPageCountPerBatch pages.
const numWorkers = Math.min(
  options.numWorkers,
  Math.ceil(exportPaths.length / minPageCountPerBatch)
)

// Calculate the page count per batch based on the number of workers.
const pageCountPerBatch = Math.ceil(exportPaths.length / numWorkers)

What the build would do

  • experimental.cpus by default: 8 cores minus one, at least 1: 7
  • The pool: the smaller of 7 and 5 routes plus one: 6 workers
  • Batches: the smaller of 6 and 7 paths ÷ 25, rounded up: 1
  • Paths in each batch, in equal slices: 7 (25 picks the count, not a batch's size)
  • Rendered at once in one worker: up to 7
Try it yourself
6 workers in the pool, 1 batch of paths

7 paths ÷ 25, rounded up, is 1, fewer than the 6 workers, so only 1 get a batch. The paths are cut into equal slices, one per batch, and each worker renders up to 8 of its paths at a time.

This runs a small model of the two functions in this page, with Next.js 16.4.3's default settings; it isn't next build itself. Reset goes back to the tea shop on an 8-core computer.

With 7 paths, the pool has 6 workers, but only one batch. At 26 paths, a second batch appears. With 2 routes, the pool shrinks to 3, however many cores there are. Your terminal prints the pool's size, like Generating static pages using 6 workers, even when one batch is all there is. Both limits, 25 and 8, can be changed in next.config.ts; they're experimental settings, so they can change in any release.

When a page fails

Open the playground above again and pick A page that throws. The last line of the Terminal says ⨯ Next.js build worker exited with code: 1 and signal: null. That's the pool at work. When /teas/oolong threw, exportPages printed the error and ended its process, because experimental.prerenderEarlyExit is on by default (export/worker.ts, lines 526 to 531). The main process watches each worker; when one exits with an error, it prints that line and exits with the same code (lib/worker.ts, lines 172 to 185). One failed page fails the whole build, so a broken page never reaches next start.

The manifests, and what reads them

After the build, next start knows nothing about your app folder. Everything it needs, the build wrote down. Turbopack writes a small manifest for each route while it compiles, and TurbopackManifestLoader merges them into the files at the top of .next (turbopack-build/impl.ts, lines 227 to 295). build/index.ts writes the rest: routes-manifest.json (line 3076), BUILD_ID (line 3098) and prerender-manifest.json (line 4619).

Two different pieces of code read them, at two different times:

  • At startup, for route matching. The router server decides which route a request is for. When it starts, it calls setupFsCheck (router-server.ts, line 177), which reads the routes manifest, the two app manifests, the middleware manifest and the pages manifest once (filesystem.ts, lines 348 to 396).
  • Per route, when its module handles a request. A route's compiled module calls loadManifests each time it prepares a request (route-module.ts, lines 873 to 877). That reads the routes, prerender, build and server reference manifests, the page's client reference manifest and required-server-files.json (lines 341 to 466). In production each file is read once and kept (shouldCache: !this.isDev).

Pick a manifest below and read the tea shop's:

Playground: open a manifest from the tea shop's build
.next/routes-manifest.json, in part (recorded)JSON
{
  "dynamicRoutes": [
    {
      "page": "/teas/[id]",
      "regex": "^/teas/([^/]+?)(?:/)?$",
      "routeKeys": {
        "nxtPid": "nxtPid"
      },
      "namedRegex": "^/teas/(?<nxtPid>[^/]+?)(?:/)?$"
    }
  ],
  "staticRoutes": [
    "/",
    "/_global-error",
    "/_not-found",
    "/cart"
  ]
}
What reads it at runtime
At startup, setupFsCheck reads it for route matching (filesystem.ts, lines 372 to 374). Per route, loadManifests loads it too: route-module.ts, lines 357 to 362
Try it yourself
.next/routes-manifest.json

Every route as a pattern. /teas/[id] is dynamic, so it has a regular expression, with a named group for id; the other four are static. The whole file also has the redirects, rewrites and headers, and the names of the router's headers.

Every file is the text next build 16.4.3 wrote for the tea shop, recorded. Chunk names, ids and keys are this build's own; a secret key shows as a placeholder. Reset goes back to the routes manifest.

Read app-path-routes-manifest.json and app-paths-manifest.json side by side: one entry, /teas/[id]/page, leads to an address in one file and to a file of code in the other. The server reference manifest has the shop's one Server Action, addToCart, under an id the build made up. Its workers name app/teas/[id]/page, the route whose server code can run it. (Here "worker" means a route's server code, not a build worker.) How those ids are made, and the client reference manifests beside each page, are taught in 'use client' and 'use server' under the hood.

Left, a window for the .next folder with six manifests: routes-manifest, app-path-routes, app-paths-manifest, prerender-manifest, build-manifest and server-reference. Right, two cards: the router server, which reads the first three when it starts, and a route's code, which reads routes-manifest, prerender-manifest, build-manifest and server-reference when it handles a request..nextroutes-manifestapp-path-routesapp-paths-manifestprerender-manifestbuild-manifestserver-referencerouter serverwhen it startsa route's codewhen a request comes inroute listswhat to render
next build writes the manifests; next start only reads them. The router server reads the route lists once, when it starts, for route matching (filesystem.ts). A route's module reads what it needs to render when it handles a request, and in production keeps each file after the first read (route-module.ts).

Each row is one manifest, what it says, and what reads it:

The tea shop's manifests
FileWhat it saysWhat reads it at runtime
routes-manifest.jsonEvery route as a pattern (a regular expression), plus the redirects, rewrites and headers.At startup, for route matching: setupFsCheck (filesystem.ts, lines 372 to 374). Per route: loadManifests (route-module.ts, lines 357 to 362).
app-path-routes-manifest.jsonWhich address each app entry serves: /teas/[id]/page serves /teas/[id].At startup, for route matching: setupFsCheck (filesystem.ts, lines 394 to 396).
server/app-paths-manifest.jsonWhich compiled file holds each app entry's code: app/teas/[id]/page.js.At startup, to know which app entries exist: setupFsCheck (filesystem.ts, lines 391 to 393).
prerender-manifest.jsonEvery path made during the build, and for each dynamic route what to do with paths it didn't list.Per route, to find a prerendered path: loadManifests (route-module.ts, lines 363 to 368).
build-manifest.jsonThe browser's JavaScript files every page loads, and the old browsers' polyfills.Per route, for the <script> tags: loadManifests (route-module.ts, lines 375 to 380).
server/server-reference-manifest.jsonEvery Server Action's id, its file and name, and which routes can run it.Per app route, to find an action's function: loadManifests (route-module.ts, lines 416 to 424).
server/middleware-manifest.jsonThe code a proxy.ts or an edge route runs, and when. Empty here: the shop has neither.At startup: setupFsCheck (filesystem.ts, lines 380 to 382).
required-server-files.jsonThe settings the server runs with, and the files it can't run without.Per route in production: loadManifests (route-module.ts, lines 432 to 439). At build time, the standalone copy step.
Read them, don't depend on them

Manifests are Next.js's notes to itself. Their names, fields and ids are this version's, and they change between releases without warning. Read them to understand a build or debug one. In your app's code, use the documented APIs: generateStaticParams, the route table, and the adapter API for platforms.

The .next folder you can read yourself

Go back to the tea shop's Terminal tab at the top and read the listing under the route table. The build's id is a random string, so static/ has a folder named after it; the paths below call it <buildId>. The sizes are this machine's, from one build. In the listing:

  • The manifests at the top: routes-manifest.json, prerender-manifest.json, build-manifest.json, app-path-routes-manifest.json, required-server-files.json, and BUILD_ID, a file holding only the id.
  • server/: the server's manifests, then server/app/, with each route's compiled code (page.js) and the pages made during the build (.html, .rsc, .meta and segment files; Partial Prerendering reads them), and server/chunks/, the code the routes share.
  • static/: the browser's files. /_next/static/… in the HTML points here. static/chunks/ holds the JavaScript (Shrinking the JavaScript bundle measures it), and static/<buildId>/ three small files for the browser's router.
  • cache/: what the next build may reuse. cache/turbopack/ is Turbopack's filesystem cache, and cache/.tsbuildinfo is TypeScript's notes from the type check. The clean step keeps this folder.
  • types/: the route types from generate-route-types.
  • trace and trace-build: the spans you read above. trace-build keeps only the main ones, from a fixed list (to-json-build.ts).
  • turbopack, an empty file (0 bytes): a sign that Turbopack made this build (impl.ts, lines 172 and 173).
  • package.json, 20 bytes: {"type": "commonjs"}, so Node.js loads the server's .js files the same way even when your own package.json says "type": "module" (impl.ts, lines 179 to 182).
  • next-server.js.nft.json and next-minimal-server.js.nft.json: traces of the server's own files. output: "standalone" uses them.

None of these files needs Next.js to read. They're JSON or text, and your editor opens them. When a build surprises you, the answer is often written in .next.

Output file tracing and standalone

Your project's node_modules holds hundreds of packages: the build tools, TypeScript, the types. The server needs only some of them. Output file tracing finds which: it reads a route's compiled code without running it, follows every import, require and file read it can identify that way, and writes the files that may be needed into a .nft.json file beside it (NFT stands for Node File Trace). In a Turbopack build, Turbopack writes these files itself while it writes the routes (crates/next-api/src/nft_json.rs); build/index.ts runs its older tracer, collect-build-traces, only for webpack builds (lines 3017 to 3021). This is the tea page's trace, from the build below:

.next/server/app/teas/[id]/page.js.nft.json: the files listed (recorded, shortened)Text
../../../../../node_modules/@swc/helpers/cjs/_interop_require_default.cjs
../../../../../node_modules/@swc/helpers/esm/_interop_require_default.js
../../../../../node_modules/@swc/helpers/package.json
../../../../../node_modules/next/dist/build/adapter/setup-node-env.external.js
… 117 more …
../../../chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892nagajq.js
../../../chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0xky89qu.js
../../../chunks/ssr/node_modules_next_dist_compiled_@edge-runtime_cookies_index_1kiz_an963mbg.js
../../../chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1_fz5f9kvpn5j.js
page/react-loadable-manifest.json
page_client-reference-manifest.js

It lists 127 files, each relative to the trace file itself. The first lines are in node_modules, and the packages they name are only @swc/helpers and next: React comes inside next, in the compiled files Next.js ships. The last lines are the build's own files: the route's chunks in server/chunks/ssr/ and the manifests it loads.

With output: "standalone" in next.config.ts, the build uses the traces. writeStandaloneDirectory (build/index.ts, lines 852 to 905) calls copyTracedFiles (build/utils.ts), which copies every file each route's trace lists (lines 1566 to 1578), then every file next-server.js.nft.json lists (line 1585), into .next/standalone, and writes a small server.js there. Then it copies the files required-server-files.json lists, and your .env and .env.production. Here is the tea shop built that way; the Terminal lists the folder:

The tea shop, with output: standaloneNext.js
app/cart/teas/[id]/
next.config.ts
import type { NextConfig } from "next"; const nextConfig: NextConfig = {  /* config options here */  cacheComponents: true,  partialPrefetching: true,  output: "standalone",}; export default nextConfig;
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (7/7)  Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found├ ◐ /cart└   /teas/[id]  ├ ◐ /teas/[id]  ├ ○ /teas/green  └ ○ /teas/mint ○  (Static)             prerendered as static content◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content $ cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/ # What .next/standalone holds: 1284 files, 46.2 MB (deeper folders added up).next/standalone/.next/BUILD_ID  21 bytes.next/standalone/.next/app-path-routes-manifest.json  158 bytes.next/standalone/.next/build-manifest.json  2.3 kB.next/standalone/.next/package.json  20 bytes.next/standalone/.next/prerender-manifest.json  9.5 kB.next/standalone/.next/required-server-files.json  10.8 kB.next/standalone/.next/routes-manifest.json  2.3 kB.next/standalone/.next/server/  831.7 kB, 133 files.next/standalone/.next/static/  593.4 kB, 13 files.next/standalone/node_modules/@emnapi/runtime/  66.5 kB, 4 files.next/standalone/node_modules/@img/colour/  46.7 kB, 3 files.next/standalone/node_modules/@img/sharp-darwin-arm64/  281.3 kB, 3 files.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/  18.2 MB, 6 files.next/standalone/node_modules/@img/sharp-wasm32/  9.3 MB, 5 files.next/standalone/node_modules/@next/env/  11.0 kB, 2 files.next/standalone/node_modules/@swc/helpers/  30.7 kB, 5 files.next/standalone/node_modules/client-only/  467 bytes, 2 files.next/standalone/node_modules/detect-libc/  11.4 kB, 5 files.next/standalone/node_modules/next/  14.6 MB, 993 files.next/standalone/node_modules/react-dom/  1.4 MB, 12 files.next/standalone/node_modules/react/  22.1 kB, 9 files.next/standalone/node_modules/semver/  68.7 kB, 47 files.next/standalone/node_modules/sharp/  631.5 kB, 29 files.next/standalone/node_modules/styled-jsx/  22.3 kB, 4 files.next/standalone/package.json  391 bytes.next/standalone/server.js  8.0 kB

Line 7 of next.config.ts turns it on. The folder's node_modules has next and @swc/helpers, from the page's trace, and also react, react-dom, styled-jsx and sharp (the image library, with its native files for this machine). The page's trace doesn't list those, so they came from the other trace copyTracedFiles reads: next-server.js.nft.json, the server's own, which Turbopack writes too (next_server_nft.rs). The recorder served the page from this folder with node .next/standalone/server.js, after copying in public and .next/static, which the server doesn't trace. The Network tab shows it answered.

Left, two cards: page.js.nft.json, which lists next and swc helpers, and next-server.js.nft.json, which lists react, react-dom, sharp and more. Arrows from both lead to a window on the right for .next/standalone, holding server.js, .next and node_modules with only the packages the traces named.page.js.nft.jsonnext, @swc/helpersnext-server.js.nft.jsonreact, react-dom, sharp…standaloneserver.js.next/node_modules/only the traced files
Each route's .nft.json lists the files that reading its code (without running it) shows it may load, and next-server.js.nft.json lists the server's own. With output: "standalone", the build copies the files they list, and only those, into .next/standalone, beside a small server.js. A file the code loads in a way reading can't see is missing until outputFileTracingIncludes adds it.

The trace is static: it lists what reading the code shows, not what a run loads. A file your code reads with a path made at runtime, like readFile(`./data/${name}.json`), or a native module a package loads in its own way, can be missing from it, and then from .next/standalone. outputFileTracingIncludes is the fix: it adds files to a route's trace. outputFileTracingExcludes removes some, and outputFileTracingRoot says where tracing starts in a monorepo. Self-hosting Next.js runs the standalone folder in Docker.

Answering questions from the .next folder

When a build does something you didn't expect, the files in .next often say why. Some questions the tea shop's team asks, and where each answer is:

  • Was this page made during the build? The route table says ○ or ◐, and prerender-manifest.json lists every path that was, with its files in server/app.
  • Which file holds this route's server code? server/app-paths-manifest.json.
  • Why is this package in my container? Search the .nft.json files for its name: the trace that lists it is the route that needs it, or the server itself.
  • Which step was slow? .next/trace has every span's length in microseconds, beside its name.
  • Will the next build reuse anything? If .next/cache survives between builds, yes. A CI job that starts from an empty folder each time needs to keep it.

Sort these questions by the manifest that answers them:

Which manifest answers this?
  • Which pattern does /teas/oolong match?
  • Which redirects does the app have?
  • Which file holds the code of the cart page?
  • Which app entries did the compiler write?
  • Was /teas/mint made during the build?
  • What happens to a tea generateStaticParams didn't list?
  • Which function does an action's id stand for?
  • Which routes' code can run addToCart?
Try it yourself
0 of 8 correct

Sort each question by the file in the tea shop's .next folder that answers it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "next start builds whatever is missing." No. next start builds nothing. It reads the manifests and serves the files next build wrote; without a build, it stops with an error.
  • "Turbopack checks my types." No. Turbopack removes types and compiles. TypeScript checks them in the next step, which is why a type error comes after ✓ Compiled successfully.
  • "Generating static pages using 6 workers means six workers rendered pages." No. That's the pool's size. The number of batches is the paths divided by 25, rounded up, and capped at the workers, so a small app's pages make one batch, rendered by one worker, up to 8 at a time.
  • "More cores always means more workers." No. The pool never gets more than one worker more than there are routes.
  • "The manifests are an API I can build on." No. They're internal files whose names and shapes change between releases. Use the documented APIs.
  • "The clean step deletes all of .next." No. It keeps cache, dev, diagnostics, lock and trace.
  • "Standalone copies my whole node_modules." No. It copies only the files the .nft.json traces list: the routes' and the server's own.

Practice exercises

Answer each one first, then use the hints.

Exercise 1 · Warm-upFind the step that stopped

A build of the tea shop printed this. At which step did it stop?

Starter codeText
  Creating an optimized production build ...
✓ Compiled successfully
  Running TypeScript ...
app/teas/[id]/page.tsx(17,18): error TS2322: Type 'number' is not assignable to type 'string'.
Failed to type check.

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind a route's server code

    This is the tea shop's .next/server/app-paths-manifest.json, recorded. Which file holds the cart page's server code?

    Starter codeJSON
    {
      "/_global-error/page": "app/_global-error/page.js",
      "/_not-found/page": "app/_not-found/page.js",
      "/cart/page": "app/cart/page.js",
      "/page": "app/page.js",
      "/teas/[id]/page": "app/teas/[id]/page.js"
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeSize the pool

      A small app has 3 routes. It's built on a computer with 12 cores, with the default settings. How many workers does the pool get?

      Starter codeTypeScript
      function getNumberOfWorkers(config: NextConfigComplete, maxTasks?: number) {
        let workers: number
        …
        } else if (config.experimental.cpus) {
          workers = config.experimental.cpus
        }
        …
        // Cap workers to one more than the number of tasks.
        // Tasks can be passed to avoid over-allocating workers.
        if (maxTasks !== undefined && maxTasks > 0) {
          workers = Math.min(workers, maxTasks + 1)
        }
      
        return Math.max(workers, 1)
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeCount the batches

        The shop grows to 120 tea pages over 4 routes, built on a computer with 8 cores. How many batches does exportPagesInBatches make, and how many paths are in each?

        Starter codeTypeScript
        const minPageCountPerBatch =
          nextConfig.experimental.staticGenerationMinPagesPerWorker ?? 25
        
        // Calculate the number of workers needed to ensure each batch has at least
        // minPageCountPerBatch pages.
        const numWorkers = Math.min(
          options.numWorkers,
          Math.ceil(exportPaths.length / minPageCountPerBatch)
        )
        
        // Calculate the page count per batch based on the number of workers.
        const pageCountPerBatch = Math.ceil(exportPaths.length / numWorkers)

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFind it in the source

          Which function in build/index.ts decides how many workers the pool gets, and what does the line that caps it compute?

          Starter codeTypeScript
          function getNumberOfWorkers(config: NextConfigComplete, maxTasks?: number) {
            let workers: number
            …
            } else if (config.experimental.cpus) {
              workers = config.experimental.cpus
            }
            …
            // Cap workers to one more than the number of tasks.
            // Tasks can be passed to avoid over-allocating workers.
            if (maxTasks !== undefined && maxTasks > 0) {
              workers = Math.min(workers, maxTasks + 1)
            }
          
            return Math.max(workers, 1)
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeWhere did react-dom come from?

            The tea shop's .next/standalone/node_modules holds react-dom, but the tea page's trace below doesn't list it. Which file in .next listed it?

            Starter codeText
            ../../../../../node_modules/@swc/helpers/cjs/_interop_require_default.cjs
            ../../../../../node_modules/@swc/helpers/esm/_interop_require_default.js
            ../../../../../node_modules/@swc/helpers/package.json
            ../../../../../node_modules/next/dist/build/adapter/setup-node-env.external.js
            … 117 more …
            ../../../chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892nagajq.js
            ../../../chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0xky89qu.js
            ../../../chunks/ssr/node_modules_next_dist_compiled_@edge-runtime_cookies_index_1kiz_an963mbg.js
            ../../../chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1_fz5f9kvpn5j.js
            page/react-loadable-manifest.json
            page_client-reference-manifest.js

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, think about which step runs where, and which file it writes.

              The Inside next build quiz · 8 questionsScore: first tries count
              1. Question 1 of 8In a Next.js 16.4.3 app with an app folder, which comes first in next build?

                Choose an answer to see the explanation.

              2. Question 2 of 8The tea shop prerenders 7 paths, and the pool has 6 workers. How many workers get pages to prerender?

                Choose an answer to see the explanation.

              3. Question 3 of 8Which file says that the code for /teas/[id]/page is in app/teas/[id]/page.js?

                Choose an answer to see the explanation.

              4. Question 4 of 8This build stopped. Which step was it in?

                Read the code, then predictText
                  Creating an optimized production build ...
                ✓ Compiled successfully
                  Running TypeScript ...
                app/teas/[id]/page.tsx(17,18): error TS2322: Type 'number' is not assignable to type 'string'.
                Failed to type check.

                Choose an answer to see the explanation.

              5. Question 5 of 8In a Turbopack build, what writes .next/server/app/teas/[id]/page.js.nft.json?

                Choose an answer to see the explanation.

              6. Question 6 of 8next build cleans .next before it builds again. Which folder does it keep?

                Choose an answer to see the explanation.

              7. Question 7 of 8A prerender error ends with ⨯ Next.js build worker exited with code: 1 and signal: null. What exited?

                Choose an answer to see the explanation.

              8. Question 8 of 8Can your app's code read .next/prerender-manifest.json to find out which pages were prerendered?

                Choose an answer to see the explanation.

              Key takeaways

              • build in build/index.ts runs the steps in order: settings, routes, the Turbopack compile, the type check, page data, prerendering, the manifests, and the route table. .next/trace lists them by name.
              • A failed build stops at the first step that can see the problem: a missing file in the compile, a type error after it, a throwing page while prerendering.
              • Prerendering runs in a pool of worker processes: the cores minus one, at most one more than the routes. The number of batches is the paths divided by 25, rounded up and capped at the workers; the paths are cut into equal slices, one per batch, and each worker renders up to 8 at a time.
              • The manifests describe the build for the server. The router server reads the route lists when it starts; each route's code reads the rest when its first request comes.
              • Each route's .nft.json lists the files its code loads. output: "standalone" copies those, and the server's own, into .next/standalone.
              • Everything in .next is readable, and all of it is this version's internals: read it, don't build on it.

              Remember the one-liner.
              next build does the work once and writes down the result; next start only reads what it wrote.

              Coming next: SWC and Turbopack. It opens step 3: the Rust compiler that transforms each file, and the bundler that builds one module graph for the server and the browser.

              CompleteFrontend Clear concepts. Working examples.