Files
vercel__next.js/test/development/app-dir/dev-validation-worker-source-maps/dev-validation-worker-source-maps.test.ts
Jiwon Choi d7aa66c345 Remove generated error codes (#97687)
### Why?

Should come up with better solution that does not block PRs with git
conflict

x-ref:
https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH

### How?

- Delete `errors.json`, the error-code SWC plugin, generated WASM, merge
driver, and validation/build tooling.
- Stop attaching error codes to server-rendering digests, redboxes, and
telemetry; native `Error.code` and `Error.name` remain available where
applicable.
- Remove the development-overlay error feedback UI, middleware, and
telemetry event that depended on stable codes.
- Update fixtures, snapshots, and guidance for code-free errors and
numeric-only digests.

<!-- NEXT_JS_LLM -->
2026-08-21 22:45:12 +02:00

297 lines
13 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
import { getDevCliValidationOutput } from 'e2e-utils/instant-validation'
import { retry } from 'next-test-utils'
// Validation errors are logged with source-mapped frames, and the routes below
// cover the cases that differ in where that source map comes from. Node.js
// caches source maps per isolate, so a validation pass running on a worker
// thread resolves a frame only if that thread holds the script it names. A
// chunk loaded from disk has its map beside it, but a module the server updated
// in place runs as a script of its own with the map inline, which exists only
// in the thread that evaluated it. The worker therefore applies the same
// updates the dev server does; the routes that edit a file while the server
// runs are what covers that. Under Webpack there is no worker and validation
// runs in process, so the two bundlers reach the same frames by different
// means.
//
// Each route owns its copy of the module it edits, because a module shared
// between routes lands in whichever chunk the first compiled route put it in,
// which would make these snapshots depend on the order the routes run in.
describe('dev-validation-worker-source-maps', () => {
const { next } = nextTestSetup({
files: __dirname,
env: { NEXT_TEST_LOG_VALIDATION: '1' },
})
it('symbolicates a cookie read in a statically imported module', async () => {
const browser = await next.browser('/static')
// The module stays in the route's own chunk, so its map is in the cache of
// whichever thread validates the route.
expect(
await getDevCliValidationOutput(await browser.url(), () => next.cliOutput)
).toMatchInlineSnapshot(`
"Error: Route "/static": Next.js encountered runtime data during prerendering.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
at readCookie (app/static/read-cookie.ts:4:30)
at StaticPage (app/static/page.tsx:4:41)
2 |
3 | export async function readCookie(): Promise<string> {
> 4 | const store = await cookies()
| ^
5 | return store.get('probe')?.value ?? 'none'
6 | }
7 |"
`)
})
it('symbolicates a frame in a module updated while the server runs', async () => {
// Load the route first so its chunk is evaluated, then edit it. The update
// gives the module a source URL of its own, which is a different input to
// source map resolution than the chunk path a freshly loaded module has.
// The edit refreshes the route on its own, so the frames below come from
// the render that follows it.
const browser = await next.browser('/updated')
// That first load is validated as well, and passes. Wait for it to finish
// and mark the output, so what is read below is what the edit produced.
await getDevCliValidationOutput(await browser.url(), () => next.cliOutput)
const outputIndex = next.cliOutput.length
await next.patchFile('app/updated/page.tsx', (content) =>
content.replace('// await new Promise', 'await new Promise')
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Next.js encountered uncached data during prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/updated/page.tsx (5:9) @ UpdatedPage
> 5 | await new Promise((resolve) => setTimeout(resolve, 0))
| ^",
"stack": [
"UpdatedPage app/updated/page.tsx (5:9)",
],
}
`)
const output = await getDevCliValidationOutput(await browser.url(), () =>
next.cliOutput.slice(outputIndex)
)
expect(output).toMatchInlineSnapshot(`
"Error: Route "/updated": Next.js encountered uncached data during prerendering.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] Cache the data access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at UpdatedPage (app/updated/page.tsx:5:9)
3 | // module is updated in place after the route has already been loaded.
4 |
> 5 | await new Promise((resolve) => setTimeout(resolve, 0))
| ^
6 |
7 | return <p>updated</p>
8 | }"
`)
// Edit the same module again, moving the access two lines down. The line
// below is what the thread that validates would report from the update it
// last applied, so a thread left behind on the first edit reports 5 again.
const secondEditIndex = next.cliOutput.length
await next.patchFile('app/updated/page.tsx', (content) =>
content.replace(
'export default async function UpdatedPage() {',
'export default async function UpdatedPage() {\n // A second edit,\n // moving the access down.'
)
)
await expect(browser).toDisplayRedbox(`
{
"description": "Next.js encountered uncached data during prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/updated/page.tsx (7:9) @ UpdatedPage
> 7 | await new Promise((resolve) => setTimeout(resolve, 0))
| ^",
"stack": [
"UpdatedPage app/updated/page.tsx (7:9)",
],
}
`)
expect(
await getDevCliValidationOutput(await browser.url(), () =>
next.cliOutput.slice(secondEditIndex)
)
).toMatchInlineSnapshot(`
"Error: Route "/updated": Next.js encountered uncached data during prerendering.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] Cache the data access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at UpdatedPage (app/updated/page.tsx:7:9)
5 | // module is updated in place after the route has already been loaded.
6 |
> 7 | await new Promise((resolve) => setTimeout(resolve, 0))
| ^
8 |
9 | return <p>updated</p>
10 | }"
`)
})
it('symbolicates a frame in an imported module updated while the server runs', async () => {
const browser = await next.browser('/shared')
await getDevCliValidationOutput(await browser.url(), () => next.cliOutput)
const outputIndex = next.cliOutput.length
// The update lands in a module the page imports rather than in the page
// itself, so the reported frames span both: the access in the imported
// module, and the page that called it.
await next.patchFile('app/shared/read-value.ts', (content) =>
content
.replace('// import { connection }', 'import { connection }')
.replace('// await connection()', 'await connection()')
)
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "Next.js encountered uncached data during prerendering.",
"environmentLabel": "Server",
"label": "Blocking Route",
"source": "app/shared/read-value.ts (7:19) @ readValue
> 7 | await connection()
| ^",
"stack": [
"readValue app/shared/read-value.ts (7:19)",
"SharedPage app/shared/page.tsx (4:29)",
],
}
`)
expect(
await getDevCliValidationOutput(await browser.url(), () =>
next.cliOutput.slice(outputIndex)
)
).toMatchInlineSnapshot(`
"Error: Route "/shared": Next.js encountered uncached data during prerendering.
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [cache] Cache the data access with \`"use cache"\` (does not apply to \`connection()\`)
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
at readValue (app/shared/read-value.ts:7:19)
at SharedPage (app/shared/page.tsx:4:29)
5 | // server runs, so the update lands in a module the page imports.
6 |
> 7 | await connection()
| ^
8 |
9 | return 'shared'
10 | }"
`)
})
it("symbolicates a route that another route's update did not touch", async () => {
// Validate the neighbour first, so the thread that validates holds its
// module state, then update it. The route asserted below shares that
// thread, and reports the same frames as it did before the update.
const neighbor = await next.browser('/neighbor')
await getDevCliValidationOutput(await neighbor.url(), () => next.cliOutput)
await next.patchFile('app/neighbor/page.tsx', (content) =>
content.replace('<p>neighbor</p>', '<p>neighbor edited</p>')
)
await retry(async () => {
expect(await neighbor.elementByCss('p').text()).toBe('neighbor edited')
})
const outputIndex = next.cliOutput.length
const browser = await next.browser('/static')
expect(
await getDevCliValidationOutput(await browser.url(), () =>
next.cliOutput.slice(outputIndex)
)
).toMatchInlineSnapshot(`
"Error: Route "/static": Next.js encountered runtime data during prerendering.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
at readCookie (app/static/read-cookie.ts:4:30)
at StaticPage (app/static/page.tsx:4:41)
2 |
3 | export async function readCookie(): Promise<string> {
> 4 | const store = await cookies()
| ^
5 | return store.get('probe')?.value ?? 'none'
6 | }
7 |"
`)
})
it('symbolicates a cookie read in a dynamically imported module', async () => {
const browser = await next.browser('/dynamic')
const output = await getDevCliValidationOutput(
await browser.url(),
() => next.cliOutput
)
// A module in a chunk of its own is the case a worker thread cannot resolve
// from Node.js' cache: under Turbopack the worker falls back to the emitted
// `.map`, and under Webpack validation runs in process, where the module
// was evaluated and its map is cached.
expect(output).toMatchInlineSnapshot(`
"Error: Route "/dynamic": Next.js encountered runtime data during prerendering.
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
- [stream] Provide a placeholder with \`<Suspense fallback={...}>\` around the data access
- [block] Set \`export const instant = false\` to allow a blocking route
Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
at readCookie (app/dynamic/read-cookie.ts:4:30)
at DynamicPage (app/dynamic/page.tsx:4:31)
2 |
3 | export async function readCookie(): Promise<string> {
> 4 | const store = await cookies()
| ^
5 | return store.get('probe')?.value ?? 'none'
6 | }
7 |"
`)
})
})