Files
Tim Neutkens d0798a0416 Fix missing Pages runtime in adapter Pages API outputs (#96819)
## Summary

### Original issue

Pages API functions produced through a build adapter can fail during
function initialization, before the customer handler executes:

```
Cannot find module 'next/dist/compiled/next-server/pages-turbo.runtime.prod.js'
```

The failure is triggered when an externalized dependency used by the API
route imports a Next.js module such as `next/head`. At runtime the load
path is:

```
external dependency
  -> next/head
  -> head-manager-context.shared-runtime
  -> Pages vendored head-manager context
  -> pages/module.compiled.js
  -> pages[-turbo].runtime.prod.js
```

The two important edges at the end of this chain are selected
dynamically. The Next.js require hook redirects the shared-runtime
import to the Pages vendored context, and `pages/module.compiled.js`
selects the bundler-specific production renderer. As a result, a Pages
API entry trace does not discover the regular Pages renderer. It
naturally contains `pages-api[-turbo].runtime.prod.js`, which is the
runtime for the API route module, but not
`pages[-turbo].runtime.prod.js`, which is reached through the external
`next/head` import.

This is why the failure is specific to Pages API routes. A regular Pages
SSR entry already references and traces the Pages renderer. App Router
entries use different route modules and traces.

### Fix

Trace the hidden Pages renderer dependency using the mechanism
appropriate to each bundler:

- **Turbopack:** add `pages-turbo.runtime.prod.js` as an explicit entry
in `Project::pages_traced_modules`. The existing native Turbopack module
graph then traces its full runtime closure. This does not run Node File
Trace and remains scoped to Pages endpoints.
- **Webpack:** run the existing Node File Trace path on
`pages.runtime.prod.js` and merge that closure into the Pages shared
assets. This code remains inside the non-Turbopack branch.

The existing require-hook modules remain separate, and App Router or
neutral endpoint trace sets are unchanged. No adapter change is
required.

### Regression test

The fixture externalizes a package that imports `next/head`, matching
the reported dependency boundary. The test builds with an adapter,
materializes only the generated Pages API function entry and its
provided assets into a clean directory, initializes the production
environment, and requires the handler in a child process.

This verifies the observable initialization behavior under both
Turbopack and Webpack rather than asserting a particular output file
list.

## Verification

- `cargo check -p next-api`
- `pnpm --filter=next build`
- `pnpm test-start-turbo
test/production/adapter-pages-api-runtime/adapter-pages-api-runtime.test.ts`
- `pnpm test-start-webpack
test/production/adapter-pages-api-runtime/adapter-pages-api-runtime.test.ts`

<!-- NEXT_JS_LLM -->

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-08-14 10:38:28 +02:00

15 lines
287 B
JavaScript

import fs from 'fs/promises'
/** @type {import('next').NextAdapter} */
const adapter = {
name: 'test-adapter',
async onBuildComplete(ctx) {
await fs.writeFile(
new URL('build-complete.json', import.meta.url),
JSON.stringify(ctx)
)
},
}
export default adapter