Files
next.js/test/e2e/app-dir/cache-components-request-apis/cache-components-request-apis.test.ts
Josh StoryandGitHub ed5d085fb1 [Cache Components] Error for Sync IO in Server Components during Static Prerender (#82500)
Currently we error if you cannot produce a shell unless you have a
Suspense boundary above the root. This is fine for normal IO but sync IO
like Math.random() and new Date() have much more significant bad
consequences for prerendering. Instead of treating these errors as
another flavor of "must have a shell" validation we should instead treat
them like they must be guarded behind something else dynamic like `await
connection()`.

In addition to unconditionally erroring for Sync IO in Server Components
when prerendering this change also removes the Sync IO warning for
runtime prefetches. This is because at the moment there is no way to
debug these errors in dev. In the future we will add validation for
prefetches and need to add back in some ability to warn for these cases
but until then we will leave this case as a silent deopt.
2025-08-20 15:23:31 +00:00

120 lines
3.4 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
const stackStart = /\s+at /
function createExpectError(cliOutput: string) {
let cliIndex = 0
return function expectError(
containing: string,
withStackContaining?: string
) {
const initialCliIndex = cliIndex
let lines = cliOutput.slice(cliIndex).split('\n')
let i = 0
while (i < lines.length) {
let line = lines[i++] + '\n'
cliIndex += line.length
if (line.includes(containing)) {
if (typeof withStackContaining !== 'string') {
return
} else {
while (i < lines.length) {
let stackLine = lines[i++] + '\n'
if (!stackStart.test(stackLine)) {
expect(stackLine).toContain(withStackContaining)
}
if (stackLine.includes(withStackContaining)) {
return
}
}
}
}
}
expect(cliOutput.slice(initialCliIndex)).toContain(containing)
}
}
describe(`Request Promises`, () => {
describe('On Prerender Completion', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: __dirname + '/fixtures/reject-hanging-promises-static',
skipStart: true,
})
if (skipped) {
return
}
if (isNextDev) {
it('does not run in dev', () => {})
return
}
it('should reject request APIs after the prerender is complete when it finishes naturally', async () => {
try {
await next.start()
} catch {
throw new Error('expected build not to fail for fully static project')
}
const expectError = createExpectError(next.cliOutput)
expectError(
'Error: During prerendering, `params` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `searchParams` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `cookies()` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `headers()` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `connection()` rejects when the prerender is complete'
)
})
})
describe('On Prerender Interruption', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: __dirname + '/fixtures/reject-hanging-promises-dynamic',
skipStart: true,
skipDeployment: true,
})
if (skipped) {
return
}
if (isNextDev) {
it('does not run in dev', () => {})
return
}
it('should reject request APIs after the prerender is interrupted with synchronously dynamic APIs', async () => {
try {
await next.start()
} catch {}
const expectError = createExpectError(next.cliOutput)
expectError(
'Error: During prerendering, `params` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `searchParams` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `cookies()` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `headers()` rejects when the prerender is complete'
)
expectError(
'Error: During prerendering, `connection()` rejects when the prerender is complete'
)
})
})
})