CodeVetter · Repo Unpack · Behavior traces
vercel/next.js
Three flows dominate the codebase. (1) Dev startup: CLI parse → fork → HTTP listen → router-server init → dev bundler → render server lazy init. (2) A production request: socket → header filtering → route resolution → `BaseServer.handleRequest` → route module render → response cache → pipe. (3) A production build: config load → route discovery → bundler compile → static generation → manifests → trace collection → adapter callback. Each is traceable through named functions, and each is instrumented with a `trace()`/OTEL span so the sequence is visible in `.next/trace`.
- Dev startup order: `bin/next.ts` checks the Node version against `__NEXT_REQUIRED_NODE_VERSION_RANGE` and exits 1 on mismatch, warns if `react`/`react-dom` fail `require.resolve`, stamps `NEXT_PRIVATE_START_TIME`, then runs the `dev` action.
- `nextDev` then opens a `trace('next-dev')` session span, records a 'rage restart' if the previous session for this project dir stopped within `RAGE_RESTART_THRESHOLD_MS` (90s) using a shared `dev-state.json` keyed by project directory under the OS cache dir, and forks the child.
- In the child, `startServer()` sets `process.title = 'next-server (v...)'`, creates the http(s) server with a `requestListener` that awaits `handlersPromise`, registers an `'upgrade'` handler that destroys the socket on error, and only afterwards calls `initialize()` to populate `requestHandler`/`upgradeHandler`.
- `initialize()` runs `setupDevBundler({ ... renderServer ... })` inside a `setupDevBundlerSpan`, wraps the result in `new DevBundlerService(...)`, assigns `renderServer.instance = require('./render-server')`, builds `renderServerOpts`, sets `renderServerOpts.serverFields.routerServerHandler = requestHandlerImpl` (a deliberate back-reference), and awaits `renderServer.instance.initialize(renderServerOpts)`.
- Only once handlers exist does the child `process.send({ nextWorkerReady: true })`; the parent replies with `nextWorkerOptions`, the child later sends `{ nextServerReady: true, port, distDir }`, and the parent stores `port` (for restart reuse) and `distDir` (for telemetry and trace upload).
- Dev restart is signalled by exit code: `requestListener`'s `finally` block calls `getMemoryRestartStats()` and, when near the heap limit, emits a `server-restart-close-to-memory-threshold` span, flushes traces and `process.exit(RESTART_EXIT_CODE)`; the parent sees that code, uploads the existing `.next/trace` before it is overwritten, and restarts.
- Request trace (dev): `requestHandlerImpl` → `filterInternalHeaders` → request-insights endpoint check → i18n `getLocaleRedirect` → optional `compress()` with a `res.once('close')` hook calling `releaseCompressionStream` to avoid a zlib leak on client disconnect → `handleRequest(0)`, which first calls `blockCrossSiteDEV`.
- `handleRequest(handleIndex)` is recursive with a hard stop — it throws `Attempted to handle request too many times` once `handleIndex > 5` — and `invokeRender` re-enters it at `handleIndex + 1` when the render server throws `NoFallbackError`.
- Render trace: `BaseServer.handleRequest` → `await this.prepare()` → `tracer.withPropagatedContext(req.headers, ...)` → `tracer.trace(BaseServerSpan.handleRequest, ...)` → `handleRequestImpl`, with the span renamed in `.finally()` to `RSC GET /route` or `GET /route` from `next.route`, and `http.route` propagated up to a platform-created parent span for APM tools.
- When request insights are enabled the whole handler is additionally wrapped in `runWithRequestInsightsIdentity({ requestId, htmlRequestId, url }, handleRequest)`, taking the id from the `NEXT_REQUEST_ID_HEADER`/`NEXT_HTML_REQUEST_ID_HEADER` headers or minting one with `nanoid()`.
- `handleRequestImpl` normalises before routing: it calls `patchSetHeaderWithCookieSupport` so middleware-set cookies survive API routes/`getServerSideProps`, then 308-redirects any URL matching `/(\\|\/\/)/` through `normalizeRepeatedSlashes`.
- Page resolution: `NextNodeServer.findPageComponents` → `loadComponents({...})` reads the pages manifest and returns a `ComponentMod`; the corresponding `RouteModule` subclass then runs, e.g. `AppPageRouteModule.render()` → `renderToHTMLOrFlight()`.
- Build trace: `build()` → `trace('next-build')` → load env files → resolve `NextConfigComplete` → `setAttribute('bundler', ...)` → `loadCustomRoutes` → create distDir → `discoverRoutes` → `createPagesMapping` → write `ROUTES_MANIFEST` → bundle → static generation → `writePrerenderManifest` → `printTreeView` → `telemetry.flush()`.
- Webpack builds can be offloaded: when `experimental.webpackBuildWorker` is enabled the build sets a `use-build-worker` span attribute and runs `webpackBuild(useBuildWorker, [...])` in a worker exposing `collectBuildTraces`; Turbopack instead goes through a single `turbopackBuild(telemetry)` call.
- Static export is a nested build stage: `build()` `require`s `../export` and calls `exportApp(...)` twice — once for `output: 'export'` full static output (`writeFullyStaticExport`) and once for per-page static generation — before manifests are finalised.