npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stayingupwind/adapter

v0.4.0

Published

Next.js deployment adapter: turns `next build` output into a content-addressed deployment bundle and the Functions that serve it.

Downloads

755

Readme

@stayingupwind/adapter

Bundles declare aiInference: 1 at the top level when their runtime supports publishing inference-only AI service bindings. The marker is independent of the bundle version and resource manifest version. A host must require it before publishing an AI-enabled deployment, including an uploaded bundle. The marker itself does not bind AI or include a host's AI service code in application Functions.

next build calls this adapter with a description of the application, and it writes a deployment bundle under <projectDir>/.arkor/ — bundle.json, the blobs it names, and the Functions (app, middleware, and workflow for a build that uses the Workflow SDK) that run the application's code under @stayingupwind/runtime. A build needs no credentials or service calls, and uploading the bundle is the host's job.

Name the adapter in next.config:

import { createRequire } from 'node:module';

export default {
  adapterPath: createRequire(import.meta.url).resolve('@stayingupwind/adapter'),
};

or by environment, which is how a host builds an application without touching its config:

NEXT_ADAPTER_PATH=$(node -e "console.log(require.resolve('@stayingupwind/adapter'))") next build

The adapter uses the stable Next.js Adapter API (modifyConfig, onBuildComplete), whose shape changes only in a major release. This file is the record of what it reads from that API, what it makes of it, and where it reaches past the API into Next.js's own output. The tests under test/ hold each row to its word.

What the adapter reads

From onBuildComplete's context, and where each thing ends up. "Recorded" means the field is carried into the bundle and nothing reads it yet — it is there so a later reader has it, and so a diff of two bundles shows a change in it. A field that is neither read nor recorded is not in the bundle at all, so no consumer can come to depend on it by accident.

| Context field | Bundle field | Read by | | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | nextVersion, buildId | nextVersion, buildId | control (deployment summary); runtime (buildId on a resume) | | projectDir, repoRoot | projectDir (relative) | recorded | | distDir | — | the adapter itself: manifests, chunks, the instrumentation hook, images-manifest.json | | config.output | — | the adapter itself: 'export' is a build with no server, bundled as described below | | config.basePath, trailingSlash, skipTrailingSlashRedirect, skipProxyUrlNormalize, poweredByHeader, i18n | config.* | runtime (basePath, i18n for routing); control (basePath and i18n decide whether a dynamic shell is served from the edge) | | config.images, as <distDir>/images-manifest.json has it (source patterns compiled to regular expressions), when the default loader is in use | config.images | edge (what /_next/image enforces); control (carried to the manifest); runtime (the source, when the edge has nothing to serve) | | routing.* (every phase, shouldNormalizeNextData, rsc) | routing.*, verbatim | runtime (@next/routing, RSC headers); control (dynamic route matchers, reserved routes, header rules); the adapter itself (rsc's prefetch segment suffixes, to read a segment output's own pathname back) | | outputs.appPages, appRoutes, pages, pagesApi: pathname, filePath, assets, runtime | entrypoints[] (id, kind, pathname, runtime when it is 'edge'); the module and its traced chunks go into the app Function | runtime (which module answers a route); control (an edge route's shell is not served from the edge, and the cache holds no generation of it) | | outputs.appPages, appRoutes, pages, pagesApi: sourcePage | sourcePages[] (id, sourcePage), verbatim; left out of the Function's runtime manifest | the inspector (the application's own folders: a pathname keeps no route group or parallel slot) | | outputs.*[].edgeRuntime (modulePath, entryKey, handlerExport), assets, config.env, for an output on the edge runtime | — | the adapter itself: the Function's edge bundle is built from them (see below) | | outputs.*[].wasmAssets, for an output on the edge runtime | — | the adapter itself: each module travels as a CompiledWasm module of the Function, published under the global its key names | | a .wasm among outputs.*[].assets, for an output on the Node.js runtime | — | the adapter itself: Next.js says nothing about it, so it is looked for; the wasm-loader patch reads it from the Function | | outputs.middleware: filePath, assets, runtime, config.matchers | middleware.matchers[]; the module goes into both Functions | edge (when to run the middleware); runtime. On the edge runtime (middleware.ts) it goes into both Functions' edge bundles instead | | outputs.prerenders[]: id, pathname, route, routeType, response, compute | same names | runtime (which shell answers a route); control (which prerenders the edge may serve) | | outputs.prerenders[].fallback.filePath, postponedState | body, postponed (blobs) | runtime and control (shell and resume) | | outputs.prerenders[].pathname, read against routing.rsc.prefetchSegmentDirSuffix and prefetchSegmentSuffix | segmentPath | the host (which router prefetches it can answer from its own storage, prefetchSegments) | | outputs.prerenders[].fallback.initialStatus, initialHeaders | same names | runtime and control (the shell's status and headers) | | outputs.prerenders[].parentOutputId, groupId, htmlSize, pprChain, config.renderingMode, allowQuery, bypassFor, partialFallback | same names | recorded | | outputs.prerenders[].parentFallbackMode | same name | runtime (a member of a fallback: false route is resolved by its own name, since its dynamic matcher fires only for a draft) | | outputs.prerenders[].fallback.initialRevalidate, initialExpiration; config.allowHeader | same names | the host (the lifetime it seeds a build's generation with); runtime (the headers a regeneration may see) | | outputs.prerenders[].config.bypassToken | bypassToken, once for the build | runtime (a request whose __prerender_bypass carries it is in draft mode, and is rendered rather than answered from the build or the cache) | | config.cacheComponents, partialPrefetching, expireTime, cacheLife (as resolved) | config.* | recorded (what a runtime lifetime by name means); the inspector shows them | | config.cacheHandler, cacheHandlers (as required-server-files.json records them) | — | each module bundled into the Function, not a static export's, and loaded by the cache-handlers patch; the build warns (cache-handlers.ts, and below) | | outputs.staticFiles[]: pathname, filePath, immutableHash | staticFiles[] (pathname, blob, immutable); the one named /index is offered under / too | edge and control (served from storage); runtime (the small ones the Function carries) | | public/ (from projectDir) | staticFiles[], URL-encoded pathnames under the basePath | as above; not walked for a static export, whose out/ already holds it |

Both routers are built the same way. What the Pages Router adds is _next/data: next build emits a data output per page whose props come from getServerSideProps (an entrypoint of its own, named /_next/data/<buildId>/…json) or from getStaticProps (a prerender under the same name), and the runtime serves those from the name the client asks for. Its home page is the one output whose name is not its URL — normalizePagePath spells it /index — so the file is offered under the application's root as well.

modifyConfig sets supportsImmutableAssets (content-addressed /_next/static/immutable/*). Before 16.4 Next.js turns it off again for a static export, in finalizeConfig, after the hook has run, and such an export's hashed names are read off the rule its own server answers them by. next/image is left as the application configured it: the edge optimizes /_next/image.

The route table goes into the bundle as the build wrote it, collapsed or not. 16.4 collapses it by default (experimental.collapseAdapterRoutes): a dynamic page and its .rsc and per-segment forms become one entry whose destination ends in the suffix that matched (/blog/[slug]$2), and a run of fallback shells one entry whose destination begins with the prefix that matched (/$1/[slug]). The Functions route with it as it is. Where a page has to be named — the edge's shell for a dynamic route, the Function a split route is in — @stayingupwind/core reads each such entry back into the ones it replaced (pageRoutes), which name their pages as every release before 16.4 did.

The development server

modifyConfig is called for every phase, and in phase-development-server it does one thing: it reserves /__upwind inside the dev server's own routing (src/dev-prefix.ts). upwind dev is the front door and has already answered that prefix before Next.js is asked, so this is for the paths back in — a middleware that rewrites to /__upwind/…, a request Next.js makes of itself — where a catch-all route of the project's would otherwise answer for it. Two rules go at the head of beforeFiles, ahead of the filesystem, pointing at the address UPWIND_DEV_ADDRESS names; a project's own rewrites keep the list they were declared in.

Without that variable — a plain next dev, with no upwind in front — nothing is changed at all. There would be no server to send the prefix to, and a rewrite to a port nothing listens on is worse than no reservation.

What the project declares beside next.config

One thing the Adapter API has no notion of is a schedule, so the adapter reads it from a file of the project's own (src/project-config.ts). Four names are looked for directly under projectDir — upwind.config.ts, upwind.jsonc, upwind.json, vercel.json — and the first that exists is the whole of the configuration; they are not merged, because merging means deciding which file wins a key neither meant to share.

| Read | Bundle field | Held to | | ----------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | crons[] | crons | Vercel's dialect, at build time: a path, and a five-field UTC expression (@stayingupwind/core/cron) | | functions.split | (how the bundle is made) | false, true, or { maxMiB?, maxCodeMiB? }, which can only tighten the host's budgets (below). Never read from vercel.json, where functions is Vercel's per-file settings | | anything else | — | ignored, so a vercel.json full of Vercel's own deployment configuration is not read twice, differently |

An upwind.config.ts is evaluated by Node itself — type stripping, no build step, erasable syntax only — and may export a function, so what a project declares can be computed. A file that will not parse, a key whose shape is wrong, or a schedule that cannot be run fails the build with the file named: the alternative is a deployment whose jobs quietly never fire.

A large application, split across Functions

A Function has a size limit, and every byte of its code is compiled when it starts. An application whose routes between them outgrow one Function can be built as several instead, each holding some of the routes — only where the host asks for it (createAdapter({ functions: { split } })), since a host then has to send each request to the Function its route is in.

When. The application is built as one Function first, exactly as it would be otherwise, and weighed. Within the budgets — maxMiB, what a Function may weigh, and maxCodeMiB, how much of that may be code — that Function is the build, and the bundle is the version-1 bundle it always was. Past either, the routes are split — where they can be: an application whose routes are one unit (see below), or whose plan comes to one Function, stays one Function over the budgets, and the build says so. A host can split every application that is past the budgets (projects: 'all', the default) or only those whose own configuration asks (projects: 'opted-in'); a project can turn the split off with functions.split: false, or tighten the budgets, never loosen them.

How the routes are placed (plan.ts). The unit is a module — entrypoints that share a built file go together — except the Pages Router, which goes whole, since res.revalidate() renders any of its pages in the Function that calls it. (An App Router page it asks for may be in another Function, which the one asking can neither render nor reach; that page is invalidated instead, as revalidatePath invalidates one, and its own Function renders it for the next request.) Every app Function carries the same base: the middleware, the instrumentation hook, the not-found page and the Pages Router's error pages, the manifests every route reads, and the same runtime.json, so that each routes a request as Next.js would and knows where every route is. A route's own manifests — its client reference manifest and its React Loadable manifest, which Next.js reads in that route's module alone — go with the route, as Next.js's trace of the route names them; one that no route's trace names (_global-error's, in a build with no output for that page) is in the base. The units are then merged two at a time, always the pair whose union adds the least code to the larger of them — routes that share a layout and its libraries first, routes that share nothing last — while the result stays within both budgets. What remains is the plan: few Functions within the budgets, each holding routes that share their code. Few, not the fewest: the merge is greedy, and a pair taken early can leave two units that would each have fitted beside another without a partner — finding the fewest is bin packing, which no build should wait on. Each piece is weighed with what the one Function measured: a chunk at the bytes it put into that Function's code (the bundler's per-module figures, scaled to the module they went into), a package linked from .next/node_modules with the routes whose chunks import it, a prerendered body, a file a route reads, a route's own manifest, a WebAssembly module at its size. Code and WebAssembly count against both budgets; the rest against the size alone. What the middleware and the instrumentation hook reach — their code, their WebAssembly, the files they read — is in every Function, and is weighed there, whichever routes reach it too.

Three rules keep a plan from being worse than not splitting. A budget the base and the smallest unit already pass is one no Function could meet, and is dropped rather than leave every route in a Function of its own. A unit past a budget the others meet stands alone, as small as it can be — but takes in a unit whose code it mostly has, one that adds no more code than the two share, since leaving them apart would carry that code twice. And a plan is never more than MAX_APP_FUNCTIONS Functions: past that, the cheapest merges go on whatever the budgets say.

What is built. Each Function of the plan with its own routes' chunks, WebAssembly, files, manifests and prerendered bodies, and the base. The first — the one holding the most documents — is app, as always; the others are app-2, app-3…, carried in functions.split, each route placed in its Function on its entrypoint (function), and the bundle is version 2 (SPLIT_BUNDLE_VERSION), which a reader of version 1 refuses rather than taking app for the whole application. A Function's code modules are named after it — app-2.cjs, edge-2.cjs — and so are its source maps (app-2/app-2.cjs), so a stack frame says which Function it came from. dependencies.json records each Function and the plan: its routes and what each Function was expected to weigh.

What a host does with it. It sends each request to the Function its route is in. The manifest says which, as far as a table can: functionFor reads the request the way Next.js routes — a rewrite ahead of the filesystem, the exact pathnames, the dynamic routes — and answers undefined where it cannot, for the first Function to take. A Function handed a request for a route it does not hold answers 421 (MISDIRECTED_STATUS) with the name of the Function that does (x-arkor-function), the request's own body, unread, and — where it routed the request before it found that out — the routing it came to (x-arkor-routed). The host sends the request on, once, with that body and that header; a resume or a regeneration, answered before any routing, goes on as it was asked. The Function that receives it answers from where the routing left off, and the response is the one a single Function would have given. A second 421 is the host's own mistake and is not followed.

A static export

With output: 'export', next build writes out/ and then hands the adapter that directory and nothing else: outputs.staticFiles alone is populated, and pages, appPages, pagesApi, appRoutes, prerenders and middleware are all empty (Next.js, "Output Types"). The bundle is the same one, with the server parts empty, and three things are decided here.

Where each file is served. Next.js hands an exported file's path from out/ with .html taken off, so the root arrives as /index — a name no visitor types. What a visitor types is decided by which file the build chose to write, and trailingSlash is what settles that. The root document is out/index.html either way and is the site's root (/, or the spelling a basePath redirects the other to). With trailingSlash, a page is its directory's index (out/about/index.html → /about/); without it, a page sits beside its siblings (out/about.html → /about) and a nested index.html is then not a directory index at all but a route whose own last segment is index (app/blog/index/page.tsx → out/blog/index.html → /blog/index), which is kept as it is. Everything that is not HTML — _next/static, the RSC payloads the client router fetches, public/ files — keeps the name it has.

Which files may be cached forever. A static export gets no immutableHash, so instead the rule the build emits for its own _next/static (in routing.onMatch, naming the directories whose file names carry a content hash or the build id) decides. Read as the edge reads a cache-control (isImmutableCacheControl): the whole policy has to be long-lived, public and unwithheld, and only a rule that holds for every request — the last unconditional one to set the header — can make a file immutable at all. Without this every hashed chunk would go out must-revalidate.

What the Function is. It is still built, and it runs none of the application's code: no entry, no Turbopack runtime to patch, no build manifest for one to read. It carries the error documents and the routing tables, and the edge sends it only what is not a file — a miss, a redirect from next.config, the trailing-slash normalization — which @next/routing answers as Next.js's own router would. Every document is the edge's, from storage; which is also why a rewrite is not followed, its destination being a file the Function does not carry (output: 'export' does not support rewrites under Next.js either).

What the adapter reaches into

Beyond the API, the Function build depends on these files of Next.js's output and package. Each dependency is one module under src/patches/, held to every Next.js in the supported range by the two checks described under "Which Next.js" below, and recorded per build in .arkor/dependencies.json.

| Where | What | Why, and what guards it | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | .next/server/chunks/[turbopack]_runtime.js, .next/server/chunks/ssr/[turbopack]_runtime.js | the two require(path.resolve(RUNTIME_ROOT, chunkPath)) sites become a static table (turbopack-runtime patch); a chunk whose code another chunk has is loaded from that one's file (same-chunks.ts) | The deployment runtime resolves only the names in the bundle; a table is what esbuild can follow. Exactly two sites per file, or the build fails. Only Turbopack builds are accepted. Turbopack writes the same chunk under several names, and the bundle carried each; two chunks are the same when their code is, once the map comment, the debugId and chunkId comments and the first-line statement recording that id are set aside | | .next/server/chunks/[turbopack]_runtime.js, .next/server/chunks/ssr/[turbopack]_runtime.js | RUNTIME_ROOT and ABSOLUTE_ROOT, both path.resolve(__filename, …), are resolved from the runtime's own path under /bundle instead (turbopack-root patch) | bundled into the Function's module, __filename is at the top of the module tree, so both roots were /: a file a server module refers to by URL (new URL('./data.json', import.meta.url), which Turbopack copies to server/assets and the Function now carries) was read from /server/assets/…, where nothing is. One of each per file, and none left over, or the build fails | | next/dist/server/lib/router-utils/instrumentation-globals.external.js | the computed require of the hook becomes the hook's file, or an empty module when the app has none (instrumentation patch) | one site, or the build fails | | next/dist/server/load-manifest.external.js | readFileSync(path) + vm.runInNewContext become a JSON read (load-manifest patch); the JSON is what evaluateManifestScript produced at build time with the same vm evaluation Next.js would run, in a context holding process.env.NEXT_DEPLOYMENT_ID = the bundle's deployment id | four sites, no vm left, or the build fails. test/manifests.test.ts compares every manifest of every built app with Next.js's own evalManifest. A missing manifest still surfaces as Next.js's own invariant ("The manifests singleton was not initialized"), since Next.js swallows the read's failure | | the Turbopack WebAssembly loader for the Node.js runtime, wherever the version keeps it: the server chunk it was moved into (16.3), or the Turbopack runtime itself (16.2) | its two entry points — compileModule/instantiate in the module, loadWebAssemblyModule/loadWebAssembly in the runtime — become reads of the module the Function carries, keyed by the .wasm path relative to distDir (wasm-loader and runtime-wasm-loader, both in patches/wasm-loader.ts) | Turbopack's loader reads the file off disk with createReadStream and compiles it with WebAssembly.compileStreaming: a Function has no file, and would compile it in every application instance. Only the registration is rewritten — the read itself is a named declaration or an inlined expression depending on how many ways the build imports a .wasm, and what is left of it is unreferenced. Neither file has a fixed name, so each is found by what only it carries — the module by its two marks together, the runtime by assigning a function to contextPrototype.w where 16.3 assigns the root — and the build asserts one of them fired when an entrypoint named WebAssembly | | next/dist/server/app-render/cache-signal.js, and the copy inside each next/dist/compiled/next-server/*.runtime.prod.js | the signal arms its pending task through a generation guard, so cancelling it does not depend on the timer being cleared (cache-signal-timers patch) | under cacheComponents every dynamic import() is tracked on one signal per application instance, and a timer it scheduled in one request cannot be cleared from another ("Cannot perform I/O on behalf of a different request") — measured, and refused the same way whether or not that request has ended, so a timer that fires anyway would wake the listeners a round of the event loop early. With the guard the clear is only an optimization | | next/dist/compiled/@vercel/og | Turbopack's externalImport of index.node.js becomes an import() of index.edge.js — dynamic, so the library is evaluated by the first request that renders an image and not at the Function's start (vercel-og patch), and that build's fallback-font fetch a read of the font the Function carries (vercel-og-font patch); its ./resvg.wasm?module imports resolve to the Function's own modules | the Node.js build is external — a module no bundler can follow and the Function does not have — and reads its font and resvg.wasm off disk before compiling the WebAssembly in every application instance. The edge build is the same library written for a runtime with neither, so next/og answers with an image instead of a 500 | | next/dist/compiled/next-server/app-page*.runtime.prod.js | the one setTimeout a prerender's task group is scheduled with becomes the runtime's task scheduler, setTimeout staying as the fallback (task-timers patch); the warning that Cache Components "cannot be guaranteed" is silenced | a prerender with cacheComponents is a sequence of setTimeout(…, 0) tasks between which React's setImmediate work is drained by Next.js's patch of setImmediate and process.nextTick; the deployment runtime fires the timers first and has no nextTick queue, so the render was aborted empty and hung when decoded. packages/runtime/src/tasks.ts runs each task once the immediates before it have run. One timer and one warning, or the build fails | | next/dist/server/node-environment-extensions/fast-set-immediate.external.js | a task of a prerender captures no immediates where the runtime's task scheduler is installed (fast-immediates patch); under next build, where none is, Next.js captures them as before | Next.js runs what a task captured once the task's microtasks are done, which it finds with a process.nextTick queued from a microtask; the deployment runtime's nextTick is a microtask of its own, so React's work ran two microtasks into the task. A prerender's server rendering started before the payload it renders had been read, reached a read of the time with the viewport unresolved, and failed. The scheduler already holds each task until the immediates scheduled before it have run. One site, or the build fails | | cloudflare:node (handleAsNodeRequest) | not a patch: the runtime hands each entrypoint the IncomingMessage and ServerResponse of a real node:http server the deployment runtime runs, as every official adapter does from Node's own | requires workerd 1.20260917.1 or later: before it, the response emitted close when the request body ended, which Next.js's response pipe takes for a client that went away (see EXPERIMENTS.md, V-01) | | require-in-the-middle, import-in-the-middle | resolved to an empty module | Node module-loader hooks (OpenTelemetry) with no loader to hook | | the Turbopack runtime's externalImport(id), the server runtime's import(e) (next/dist/compiled/next-server/*.runtime.prod.js) | not a patch: left as they are, an import() of a module named at run time, and recorded | the loads Next.js makes with something other than one module's name, known to be reached only where they work (a Node built-in); a require or import() like it anywhere else fails the build | | critters | resolved to a module whose constructor throws | required by the Pages Router runtime for experimental.optimizeCss, which reads built stylesheets off disk; an app that turns it on fails its first render in so many words rather than the bundle | | next/dist/compiled/raw-body | resolved to the adapter's own copy (patches/raw-body.ts) | the compiled module bundles depd, which builds a function from a string; the deployment runtime refuses that at load, and the Pages Router's API body parser answered every request 400 Invalid body | | next/dist/compiled/next-server/app-page-turbo*.runtime.prod.js, in the middleware Function | resolved to a module holding the React the runtime vendors for server components, vendored["react-rsc"].React — next/dist/compiled/react*/react.react-server.js, as the runtime hands it out — and nothing else (page-runtime-react.ts) | a Node.js middleware reaches the runtime only for that React, through NextResponse, the cookies, connection() and the tracking of dynamic rendering, and carried and evaluated all 664 KB of it as it loaded. What the middleware's chunks read of the runtime is read off them once the bundle is built: a read of anything more, a require that is not a chunk's external, or a chunk not written as this reads it, and the Function is bundled again with the runtime itself | | next/dist/compiled/ws | resolved to a module that throws | Next.js requires it only from the WebSocket getter it installs where the runtime has none, and the deployment runtime has one | | next/dist/compiled/jsonwebtoken, in a build with no Pages Router route | resolved to a module that throws | it verifies the preview data a Pages Router route signs into a cookie, and is required only inside the try that reads the cookie back; with no route to sign any, a failure to load it comes to what a token that does not verify already does. Asked of the build, not of a Function: a cookie one Function sets is sent to all | | @opentelemetry/api, next/dist/compiled/@opentelemetry/api | each resolves to the app's own copy where it installed one, and to Next.js's where it did not (vendoredOtelPlugin); for Next.js's, the app's own is looked for from Next.js's package, whoever asks | Next.js prefers the app's and falls back to its own inside a try, which a bundler cannot see: bundling both carried the API twice, and bundling only the app's left an external the Function cannot load. The page runtime's own require of Next.js's copy (16.4) keeps nothing of what it returns. The chunks below ask for Next.js's copy from the build's output, where a strict install that has the API only as Next.js's peer has none to find | | the OpenTelemetry API in .next/server/chunks/**: the copies Next.js's tracer, compiled into the chunks, requires in its try and its catch | each becomes an external of next/dist/compiled/@opentelemetry/api, as Turbopack writes one, where a chunk's table installs it under its id alone and it carries the key the API keeps its state under (otel-chunks.ts) | Turbopack compiles the tracer into the chunks of every layer that reaches it and bundles both requires there: each layer held a copy of each beside the one Next.js's server is bundled with, seven of the API in the minimal fixture's app Function. The API keeps its state on a global, so one copy answers as the copies did | | next/dist/server/patch-error-inspect.js | its top-level require of next/dist/compiled/source-map becomes a read on first use (lazy-source-map patch) | the library maps an error's frames through a source map, and the Function carries none of its maps, yet it was evaluated at every start. Every read of the library has to be of SourceMapConsumer, or the build fails | | the AsyncLocalStorage banner | globalThis.AsyncLocalStorage ??= require('node:async_hooks').AsyncLocalStorage is the first line of app.cjs, and of edge.cjs | Next.js's storages read the global once, at module evaluation, and settle for a fake without it; a bundle without the banner fails every render (test/node/broken-bundles.test.ts in the runtime) | | .next/server/edge/chunks/**, the chunks of an entrypoint on the edge runtime | evaluated, in the order assets lists them, by the thunk in edge.cjs that the route's first request calls; the handler is then read from globalThis._ENTRIES[entryKey] | the documented way to invoke one (Adapters, "Invoking Entrypoints"). Their chunk loader reads no file at run time (loadChunkCached throws), so evaluating the chunks is the whole of loading an entry; their Turbopack runtime's import(), require.resolve and kept loader are recorded and allowed | | Node built-ins | node:* and the bare names are left to the deployment runtime; a built-in outside the list in dependencies.ts fails the build | under nodejs_compat every built-in import resolves (the unimplemented ones as stubs that throw when used), so the list is what is known to be reached only where it works | | *.json under .next/ and .next/server/, BUILD_ID, each route's react-loadable-manifest.json and *_client-reference-manifest.js (as .json) | shipped as text modules of the app Function under their .next/... names; the runtime mounts them at /bundle/.... The middleware Function carries none | excluded rather than listed (*.nft.json are the only files known to be of no use): a manifest Next.js reads without a fallback that a list left out fails every route. Only a route module reads one: the middleware module and its hook open none |

The app module (app.cjs) is bundled by Rolldown, as one CommonJS module with the patches applied as each file is loaded — 5% smaller than esbuild made it from the same graph, and built sooner (see EXPERIMENTS.md, V-03). The runtime module (index.mjs) is Rolldown's as well, for the workerd and worker conditions, and so is the edge bundle (edge.cjs), where a build produced one. An import that Rolldown cannot resolve, in the runtime or in the cache host it bundles, fails the build: the Function would not have that module either. One inside a try — a require(), or an awaited import() — is left to fail when it runs, for the fallback around it to catch. Rolldown shakes the members of a namespace object a dependency builds, which esbuild keeps whole: import { z } from 'zod' in a cache host kept all of zod in the runtime. All three are minified in whitespace and syntax. The runtime's variables and parameters are renamed short as well, and so are those of the CommonJS modules app.cjs bundles from Next.js's and React's own files — Next.js's server, React, and React's server renderer (react-dom) — one module at a time (mangle-packages.ts). No other package's code is renamed. Next.js and React are what the adapter's tests and Next.js's deploy tests run, and nearly all of what renaming takes off. Any other package would be renamed untested, where a mangler bug nobody has reported yet, or a package that reads its own functions' parameter names, would break it unseen. No function or class is renamed anywhere, so a stack trace names every frame as before, and nothing Turbopack or Next.js's own build minified is renamed again: React reads a page's postponed state back by the names of the components that rendered it, and with the whole of app.cjs renamed every resume fell back to client rendering. A module's /*#__PURE__*/ annotations, which the minifier drops as it prints, are put back before the calls they mark. A module with code the pinned mangler renames wrongly — a catch parameter taken apart, a direct eval, a function declared in a block of sloppy code (oxc-project/oxc#24331, #26485) — or one that marks a function #__NO_SIDE_EFFECTS__ is left as it was written, and so is a runtime with any of that code. The runtime is renamed whole, the host's cache module among it (below). In a fixture's app Function that took the runtime from 348,545 to 263,742 bytes and app.cjs from 2,544,044 to 2,480,829; in its middleware Function, 141,809 to 109,476 and 550,583 to 489,274. A Pages Router fixture's pages render with the application's own react-dom, and its app Function's app.cjs went from 2,447,874 to 2,163,473, 221,203 bytes of that react-dom's.

Two things the build wrote are rewritten on their way into the app module, as the patches rewrite Next.js's files. With experimental.turbopackModuleFragments (Next.js 16.4) Turbopack registers every export of a module with a setter, a const export's among them — e.s(["k",()=>b,e=>b=e]), which nothing calls. Node.js runs such a chunk, since the assignment is only an error if it is made, but Rolldown refuses to bundle it (ILLEGAL_REASSIGNMENT). Such a setter's body becomes the TypeError running it would throw, and nothing else changes (const-export-setters.ts): which names are const is read scope by scope, and one that cannot be read is left for Rolldown to say what it says of it.

And the copies of the OpenTelemetry API that Turbopack bundled into the chunks, each layer's own beside Next.js's, which Next.js's tracer requires in its try and its catch, become externals of next/dist/compiled/@opentelemetry/api, and so the one copy Next.js's server is bundled with (otel-chunks.ts). Which modules those are is read off the tracer before anything is bundled: its try and its catch, each the require of one module by its id, then the six members it takes of what it got. A module is rewritten where a chunk's table installs it under that id alone and it carries the key the API keeps its state under; a build whose tracer reads otherwise keeps its copies.

.arkor/dependencies.json records, per Function — and under edge, for its edge bundle — the files bundled from the build output and from each package (with the bytes each puts in the bundle), the built-ins left external, the stubs, the patches applied with their edit counts, every use of the loader the bundler could not follow — a require or import() made with something other than one module's name, a method of the loader called or kept (require.resolve, `requi