Files
Hendrik LiebauandGitHub d2e6b6c0e4 Preserve __NEXT_ERROR_CODE across the /_error page handoff (#93183)
When SSR fails in development — whether the failing page is in the App Router or the Pages Router — Next.js falls back to rendering the Pages Router `/_error` page and serializes the original error into `__NEXT_DATA__.err`. The client bootstrap in `packages/next/src/client/ index.tsx` later re-throws a fresh `new Error(initialErr.message)` so that the dev overlay picks it up. The error-code SWC plugin stamps that new `Error` with the generic code mapped to `%s` in `errors.json` because the message argument is an identifier, not a statically-known string, which is how the overlay ended up showing the wrong code for errors like `UseCacheTimeoutError`.

The pipeline has two problems on the way to the overlay. First, `errorToJSON` in `packages/next/src/server/render.tsx` only copies the error's standard fields (`name`, `source`, `message`, `stack`, `digest`) into `__NEXT_DATA__.err`, so the real `__NEXT_ERROR_CODE` attached to the thrown error never reaches the client. Second, the subsequent rewrap in `getServerError` at `packages/next/src/server/dev/node-stack- frames.ts` creates yet another `new Error(...)` that the plugin stamps with the same generic code, clobbering anything the caller might have set on the rewrapped instance.

This change plumbs the code through. `errorToJSON` now also emits `__NEXT_ERROR_CODE` using `extractNextErrorCode` so the value survives JSON serialization into `__NEXT_DATA__.err`; the type in `packages/next/src/shared/lib/utils.ts` is updated to match. Both rewrap sites — the client bootstrap and `getServerError` — copy the code from the source error onto the fresh `Error` via `Object.defineProperty` with `enumerable: false` and `configurable: true`, matching how the plugin itself stores the property, which overrides the generic code the plugin stamped on the rewrapped instance. The `use-cache-hanging` e2e test snapshot is updated from `E394` to `E236` now that the real code surfaces in the dev overlay.
2026-04-27 18:53:19 +02:00

90 lines
2.7 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
describe('server-navigation-error', () => {
const { next } = nextTestSetup({
files: __dirname,
})
describe('pages router', () => {
it('should error on navigation API redirect', async () => {
const browser = await next.browser('/pages/redirect')
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "Next.js navigation API is not allowed to be used in Pages Router.",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/pages/redirect.tsx (4:11) @ Page
> 4 | redirect('/')
| ^",
"stack": [
"Page pages/pages/redirect.tsx (4:11)",
],
}
`)
})
it('should error on navigation API notFound', async () => {
const browser = await next.browser('/pages/not-found')
await expect(browser).toDisplayRedbox(`
{
"code": "E1041",
"description": "Next.js navigation API is not allowed to be used in Pages Router.",
"environmentLabel": null,
"label": "Runtime Error",
"source": "pages/pages/not-found.tsx (4:11) @ Page
> 4 | notFound()
| ^",
"stack": [
"Page pages/pages/not-found.tsx (4:11)",
],
}
`)
})
})
describe('middleware', () => {
it('should error on navigation API redirect ', async () => {
const browser = await next.browser('/middleware/redirect')
// FIXME: the first request to middleware error load didn't show the redbox, need one more reload
await browser.refresh()
await expect(browser).toDisplayRedbox(`
{
"code": "E394",
"description": "Next.js navigation API is not allowed to be used in Middleware.",
"environmentLabel": null,
"label": "Runtime Error",
"source": "middleware.ts (8:13) @ middleware
> 8 | redirect('/')
| ^",
"stack": [
"middleware middleware.ts (8:13)",
],
}
`)
})
it('should error on navigation API not-found', async () => {
const browser = await next.browser('/middleware/not-found')
await expect(browser).toDisplayRedbox(`
{
"code": "E1041",
"description": "Next.js navigation API is not allowed to be used in Middleware.",
"environmentLabel": null,
"label": "Runtime Error",
"source": "middleware.ts (6:13) @ middleware
> 6 | notFound()
| ^",
"stack": [
"middleware middleware.ts (6:13)",
],
}
`)
})
})
})