Files
vercel__next.js/test/development/server-side-dev-errors/server-side-dev-errors.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

456 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { nextTestSetup } from 'e2e-utils'
import { waitForNoRedbox, retry } from 'next-test-utils'
import stripAnsi from 'strip-ansi'
describe('server-side dev errors', () => {
const { next, isTurbopack } = nextTestSetup({
files: __dirname,
})
function stripInternalHandler(output) {
return output
.replace(/Creating turbopack project \{[\s\S]*?\}\s*/g, '')
.replace(/WARNING: The git repository is dirty[^\n]*\n?/g, '')
.replace(/.*at async handler .*next-route-loader.*/g, '')
.replace(/.*at async handleResponse.*/g, '')
.replace(/.*at async doRender \(.*/g, '')
.split(/\n/)
.filter((item) => {
const trimmed = item.trim()
if (!trimmed) return false
// Drop bootstrap/startup banner lines that may appear after
// `next.cliOutput` was sliced. The Experiments banner is logged
// asynchronously after the dev server reports ready (see
// `logExperimentalInfo` in `start-server.ts`), so it can race
// with the test capturing `cliOutputIdx`.
if (trimmed.startsWith('- ')) return false
if (/^[✓⚠△] /.test(trimmed)) return false
// Individual entries under the `- Experiments` header are indented and
// use status glyphs (✓ enabled, disabled, · value), e.g.
// ` exampleFlag (disabled by ...)`. Only these banner entries
// are indented; real dev errors start at column 0 (` ReferenceError:
// ...`), so key off the leading indentation to avoid dropping them.
if (/^\s+[✓⚠△⨯·] /.test(item)) return false
// Drop compiling indicator lines (e.g. "○ Compiling /gsp ...").
if (trimmed.startsWith('○ ')) return false
return true
})
.join('\n')
}
it('should show server-side error for gsp page correctly', async () => {
const content = await next.readFile('pages/gsp.js')
try {
const cliOutputIdx = next.cliOutput.length
await next.patchFile(
'pages/gsp.js',
content.replace('return {', 'missingVar;return {')
)
const browser = await next.browser('/gsp')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'ReferenceError: missingVar is not defined'
)
})
const stderrOutput = stripInternalHandler(
stripAnsi(next.cliOutput.slice(cliOutputIdx)).trim()
)
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at getStaticProps'
)
expect(stderrOutput).toContain('gsp.js:6:3')
expect(stderrOutput).toContain(
' 5 | export async function getStaticProps() {\n' +
'> 6 | missingVar;return {\n' +
' | ^'
)
await expect(browser).toDisplayRedbox(`
{
"description": "missingVar is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "pages/gsp.js (6:3) @ getStaticProps
> 6 | missingVar;return {
| ^",
"stack": [
"getStaticProps pages/gsp.js (6:3)",
],
}
`)
await next.patchFile('pages/gsp.js', content)
await waitForNoRedbox(browser)
} finally {
await next.patchFile('pages/gsp.js', content)
}
})
it('should show server-side error for gssp page correctly', async () => {
const content = await next.readFile('pages/gssp.js')
try {
const cliOutputIdx = next.cliOutput.length
await next.patchFile(
'pages/gssp.js',
content.replace('return {', 'missingVar;return {')
)
const browser = await next.browser('/gssp')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'ReferenceError: missingVar is not defined'
)
})
const stderrOutput = stripInternalHandler(
stripAnsi(next.cliOutput.slice(cliOutputIdx)).trim()
)
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at getServerSideProps'
)
expect(stderrOutput).toContain('gssp.js:6:3')
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
'> 6 | missingVar;return {\n' +
' | ^'
)
await expect(browser).toDisplayRedbox(`
{
"description": "missingVar is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "pages/gssp.js (6:3) @ getServerSideProps
> 6 | missingVar;return {
| ^",
"stack": [
"getServerSideProps pages/gssp.js (6:3)",
],
}
`)
await next.patchFile('pages/gssp.js', content)
await waitForNoRedbox(browser)
} finally {
await next.patchFile('pages/gssp.js', content)
}
})
it('should show server-side error for dynamic gssp page correctly', async () => {
const content = await next.readFile('pages/blog/[slug].js')
try {
const cliOutputIdx = next.cliOutput.length
await next.patchFile(
'pages/blog/[slug].js',
content.replace('return {', 'missingVar;return {')
)
const browser = await next.browser('/blog/first')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'ReferenceError: missingVar is not defined'
)
})
const stderrOutput = stripInternalHandler(
stripAnsi(next.cliOutput.slice(cliOutputIdx)).trim()
)
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at getServerSideProps'
)
expect(stderrOutput).toContain('[slug].js:6:3')
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
'> 6 | missingVar;return {\n' +
' | ^'
)
await expect(browser).toDisplayRedbox(`
{
"description": "missingVar is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "pages/blog/[slug].js (6:3) @ getServerSideProps
> 6 | missingVar;return {
| ^",
"stack": [
"getServerSideProps pages/blog/[slug].js (6:3)",
],
}
`)
await next.patchFile('pages/blog/[slug].js', content)
} finally {
await next.patchFile('pages/blog/[slug].js', content)
}
})
it('should show server-side error for api route correctly', async () => {
const content = await next.readFile('pages/api/hello.js')
try {
const cliOutputIdx = next.cliOutput.length
await next.patchFile(
'pages/api/hello.js',
content.replace('res.status', 'missingVar;res.status')
)
const browser = await next.browser('/api/hello')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'ReferenceError: missingVar is not defined'
)
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx)).trim()
if (isTurbopack) {
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at handler'
)
expect(stderrOutput).toContain('hello.js:2:3')
expect(stderrOutput).toContain(
' 1 | export default function handler(req, res) {\n' +
"> 2 | missingVar;res.status(200).json({ hello: 'world' })\n" +
' | ^'
)
} else {
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at handler'
)
expect(stderrOutput).toContain('hello.js:2:3')
// TODO(veil): Why not ignore-listed?
expect(stderrOutput).toContain('\n at ')
expect(stderrOutput).toContain(
' 1 | export default function handler(req, res) {\n' +
"> 2 | missingVar;res.status(200).json({ hello: 'world' })\n" +
' | ^'
)
}
await expect(browser).toDisplayRedbox(`
{
"description": "missingVar is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "pages/api/hello.js (2:3) @ handler
> 2 | missingVar;res.status(200).json({ hello: 'world' })
| ^",
"stack": [
"handler pages/api/hello.js (2:3)",
],
}
`)
await next.patchFile('pages/api/hello.js', content)
await retry(async () => {
await browser.refresh()
await waitForNoRedbox(browser)
})
} finally {
await next.patchFile('pages/api/hello.js', content)
}
})
it('should show server-side error for dynamic api route correctly', async () => {
const content = await next.readFile('pages/api/blog/[slug].js')
try {
const cliOutputIdx = next.cliOutput.length
await next.patchFile(
'pages/api/blog/[slug].js',
content.replace('res.status', 'missingVar;res.status')
)
const browser = await next.browser('/api/blog/first')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'ReferenceError: missingVar is not defined'
)
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx)).trim()
if (isTurbopack) {
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at handler'
)
expect(stderrOutput).toContain('[slug].js:2:3')
expect(stderrOutput).toContain(
' 1 | export default function handler(req, res) {\n' +
'> 2 | missingVar;res.status(200).json({ slug: req.query.slug })\n' +
' | ^'
)
} else {
expect(stderrOutput).toStartWith(
' ReferenceError: missingVar is not defined\n at handler'
)
expect(stderrOutput).toContain('[slug].js:2:3')
// TODO(veil): Why not ignore-listed?
expect(stderrOutput).toContain('\n at')
expect(stderrOutput).toContain(
' 1 | export default function handler(req, res) {\n' +
'> 2 | missingVar;res.status(200).json({ slug: req.query.slug })\n' +
' | ^'
)
}
await expect(browser).toDisplayRedbox(`
{
"description": "missingVar is not defined",
"environmentLabel": null,
"label": "Runtime ReferenceError",
"source": "pages/api/blog/[slug].js (2:3) @ handler
> 2 | missingVar;res.status(200).json({ slug: req.query.slug })
| ^",
"stack": [
"handler pages/api/blog/[slug].js (2:3)",
],
}
`)
await next.patchFile('pages/api/blog/[slug].js', content)
await retry(async () => {
await browser.refresh()
await waitForNoRedbox(browser)
})
} finally {
await next.patchFile('pages/api/blog/[slug].js', content)
}
})
it('should show server-side error for uncaught rejection correctly', async () => {
const cliOutputIdx = next.cliOutput.length
await next.browser('/uncaught-rejection')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain(
'Error: catch this rejection'
)
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx))
.replace(
'⚠ Fast Refresh had to perform a full reload due to a Runtime ReferenceError.',
''
)
.trim()
// FIXME(veil): error repeated
expect(stderrOutput).toContain('Error: catch this rejection')
expect(stderrOutput).toContain('uncaught-rejection.js:7:20')
if (isTurbopack) {
expect(stderrOutput).toContain('at Timeout._onTimeout')
} else {
expect(stderrOutput).toContain('at Timeout.eval [as _onTimeout]')
}
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
' 6 | setTimeout(() => {\n' +
"> 7 | Promise.reject(new Error('catch this rejection'))"
)
expect(stderrOutput).toContain(
' unhandledRejection: Error: catch this rejection'
)
})
it('should show server-side error for uncaught empty rejection correctly', async () => {
const cliOutputIdx = next.cliOutput.length
await next.browser('/uncaught-empty-rejection')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain('Error:')
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx))
.replace(
'⚠ Fast Refresh had to perform a full reload due to a Runtime ReferenceError.',
''
)
.trim()
// FIXME(veil): error repeated
expect(stderrOutput).toContain('uncaught-empty-rejection.js:7:20')
if (isTurbopack) {
expect(stderrOutput).toContain('at Timeout._onTimeout')
} else {
expect(stderrOutput).toContain('at Timeout.eval [as _onTimeout]')
}
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
' 6 | setTimeout(() => {\n' +
'> 7 | Promise.reject(new Error())'
)
expect(stderrOutput).toContain(' unhandledRejection: Error:')
})
it('should show server-side error for uncaught exception correctly', async () => {
const cliOutputIdx = next.cliOutput.length
await next.browser('/uncaught-exception')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain('Error:')
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx))
.replace(
'⚠ Fast Refresh had to perform a full reload due to a Runtime ReferenceError.',
''
)
.trim()
// FIXME(veil): error repeated
expect(stderrOutput).toContain('Error: catch this exception')
expect(stderrOutput).toContain('uncaught-exception.js:7:11')
if (isTurbopack) {
expect(stderrOutput).toContain('at Timeout._onTimeout')
} else {
expect(stderrOutput).toContain('at Timeout.eval [as _onTimeout]')
}
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
' 6 | setTimeout(() => {\n' +
"> 7 | throw new Error('catch this exception')"
)
expect(stderrOutput).toContain(
' uncaughtException: Error: catch this exception'
)
})
it('should show server-side error for uncaught empty exception correctly', async () => {
const cliOutputIdx = next.cliOutput.length
await next.browser('/uncaught-empty-exception')
await retry(() => {
expect(next.cliOutput.slice(cliOutputIdx)).toContain('Error:')
})
const stderrOutput = stripAnsi(next.cliOutput.slice(cliOutputIdx))
.replace(
'⚠ Fast Refresh had to perform a full reload due to a Runtime ReferenceError.',
''
)
.trim()
// FIXME(veil): error repeated
expect(stderrOutput).toContain('uncaught-empty-exception.js:7:11')
if (isTurbopack) {
expect(stderrOutput).toContain('at Timeout._onTimeout')
} else {
expect(stderrOutput).toContain('at Timeout.eval [as _onTimeout]')
}
expect(stderrOutput).toContain(
' 5 | export async function getServerSideProps() {\n' +
' 6 | setTimeout(() => {\n' +
'> 7 | throw new Error()'
)
expect(stderrOutput).toContain(' uncaughtException: Error:')
})
})