mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
prefetching is now controlled with the prefetch export and this option is inert on the instant export. this removes the prefetch option as a valid property on the instant config type. also updates the ts plugin for this export and adds the definition for the previously landed prefetch export
3424 lines
173 KiB
TypeScript
3424 lines
173 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import {
|
|
expectNoBuildValidationErrors,
|
|
expectBuildValidationSkipped,
|
|
extractBuildValidationError,
|
|
waitForValidation,
|
|
} from 'e2e-utils/instant-validation'
|
|
import {
|
|
openRedbox,
|
|
retry,
|
|
waitForNoErrorToast,
|
|
waitForRedbox,
|
|
} from '../../../lib/next-test-utils'
|
|
import {
|
|
createRedboxSnapshot,
|
|
ErrorSnapshot,
|
|
RedboxSnapshot,
|
|
} from '../../../lib/add-redbox-matchers'
|
|
import { Playwright } from '../../../lib/next-webdriver'
|
|
|
|
describe('instant validation', () => {
|
|
const { next, skipped, isNextDev, isNextStart, isTurbopack } = nextTestSetup({
|
|
files: __dirname,
|
|
skipStart: true,
|
|
skipDeployment: true,
|
|
env: {
|
|
NEXT_TEST_LOG_VALIDATION: '1',
|
|
},
|
|
})
|
|
if (skipped) return
|
|
|
|
if (isNextStart && !isTurbopack) {
|
|
// TODO(instant-validation-build): snapshot tests for webpack
|
|
it.skip('TODO: snapshot tests for webpack', () => {})
|
|
return
|
|
}
|
|
|
|
if (isNextStart) {
|
|
beforeAll(async () => {
|
|
await next.build({ args: ['--experimental-build-mode', 'compile'] })
|
|
})
|
|
afterEach(async () => {
|
|
await next.stop()
|
|
})
|
|
} else {
|
|
beforeAll(async () => {
|
|
await next.start()
|
|
})
|
|
}
|
|
|
|
let currentCliOutputIndex = 0
|
|
beforeEach(() => {
|
|
currentCliOutputIndex = next.cliOutput.length
|
|
})
|
|
|
|
function getCliOutputSinceMark(): string {
|
|
if (next.cliOutput.length < currentCliOutputIndex) {
|
|
// cliOutput shrank since we started the test, so something (like a `sandbox`) reset the logs
|
|
currentCliOutputIndex = 0
|
|
}
|
|
return next.cliOutput.slice(currentCliOutputIndex)
|
|
}
|
|
|
|
const prerender = async (pathname: string) => {
|
|
const args = [
|
|
'--experimental-build-mode',
|
|
'generate',
|
|
'--debug-build-paths',
|
|
`app${pathname}/page.tsx`,
|
|
]
|
|
return await next.build({ args })
|
|
}
|
|
|
|
const NO_VALIDATION_ERRORS_WAIT: Parameters<typeof waitForNoErrorToast>[1] = {
|
|
waitInMs: 500,
|
|
}
|
|
|
|
async function expectNoDevValidationErrors(
|
|
browser: Playwright,
|
|
url: string
|
|
): Promise<void> {
|
|
await waitForValidation(url, getCliOutputSinceMark)
|
|
await waitForNoErrorToast(browser, NO_VALIDATION_ERRORS_WAIT)
|
|
}
|
|
|
|
const cases = isNextDev
|
|
? [
|
|
{ isClientNav: false, description: 'dev - initial load' },
|
|
{ isClientNav: true, description: 'dev - client navigation' },
|
|
]
|
|
: [{ isClientNav: false, description: 'build' }]
|
|
|
|
describe.each(cases)('$description', ({ isClientNav }) => {
|
|
/**
|
|
* Navigate to a page either via initial load or soft navigation.
|
|
* For soft nav, navigates to the index page first, then clicks the link.
|
|
*/
|
|
async function navigateTo(href: string) {
|
|
if (!isClientNav) {
|
|
// Initial load - navigate directly
|
|
const browser = await next.browser(href)
|
|
await browser.elementByCss('main')
|
|
return browser
|
|
}
|
|
|
|
// Soft nav - go to index page first, then click link
|
|
const indexPage = href.startsWith('/default/')
|
|
? '/default'
|
|
: '/suspense-in-root'
|
|
const browser = await next.browser(indexPage)
|
|
const initialRootLayoutTimestamp = await browser
|
|
.elementById('root-layout-timestamp')
|
|
.text()
|
|
|
|
await browser
|
|
.elementByCss(`[data-link-type="soft"][href="${href}"]`)
|
|
.click()
|
|
|
|
await retry(
|
|
async () => {
|
|
expect(await browser.url()).toContain(href)
|
|
},
|
|
undefined,
|
|
100,
|
|
'wait for url to change'
|
|
)
|
|
|
|
// Sanity check: we shouldn't have switched or otherwise refetched the root layout
|
|
const finalRootLayoutTimestamp = await browser
|
|
.elementById('root-layout-timestamp')
|
|
.text()
|
|
expect(initialRootLayoutTimestamp).toBe(finalRootLayoutTimestamp)
|
|
return browser
|
|
}
|
|
|
|
it('valid - static prefetch - suspense around runtime and dynamic', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/suspense-around-dynamic'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/suspense-around-dynamic'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - suspense only around dynamic', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/suspense-around-dynamic'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/suspense-around-dynamic'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - missing suspense around runtime', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-around-runtime'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (6:16) @ Page
|
|
> 6 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/missing-suspense-around-runtime/page.tsx (6:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-around-runtime'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-around-runtime": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-runtime".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-around-runtime" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - missing suspense around dynamic', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-around-dynamic'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (6:19) @ Page
|
|
> 6 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/missing-suspense-around-dynamic/page.tsx (6:19)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-around-dynamic'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-around-dynamic": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-dynamic".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-around-dynamic" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - missing suspense around dynamic', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/missing-suspense-around-dynamic'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (23:19) @ Dynamic
|
|
> 23 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Dynamic app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (23:19)",
|
|
"Page app/suspense-in-root/runtime/missing-suspense-around-dynamic/page.tsx (16:9)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/missing-suspense-around-dynamic'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/missing-suspense-around-dynamic": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at main (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/missing-suspense-around-dynamic".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/missing-suspense-around-dynamic" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - missing suspense around dynamic in a layout', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-around-dynamic-layout'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (7:16) @ Layout
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/static/missing-suspense-around-dynamic-layout/layout.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-around-dynamic-layout'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-around-dynamic-layout": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-dynamic-layout".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-around-dynamic-layout" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - missing suspense around dynamic in a layout', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/missing-suspense-around-dynamic-layout'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (8:19) @ Layout
|
|
> 8 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/runtime/missing-suspense-around-dynamic-layout/layout.tsx (8:19)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/missing-suspense-around-dynamic-layout'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/missing-suspense-around-dynamic-layout" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - missing suspense around params', async () => {
|
|
// In build mode, providing params in the sample makes them resolve
|
|
// immediately, so the blocking behavior isn't detected. This case
|
|
// is only testable in dev mode.
|
|
if (!isNextDev) return
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-around-params/123'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = { samples: [{ params: { param: '123' } }] }
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (17:21) @ Runtime
|
|
> 17 | const { param } = await params
|
|
| ^",
|
|
"stack": [
|
|
"Runtime app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (17:21)",
|
|
"Page app/suspense-in-root/static/missing-suspense-around-params/[param]/page.tsx (11:7)",
|
|
],
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('valid - runtime prefetch - does not require Suspense around params', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-no-suspense-around-params/123'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-no-suspense-around-params/[param]'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - missing suspense around search params', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-around-search-params?foo=bar'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = { samples: [{ searchParams: { foo: 'bar' } }] }
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (4:18) @ Page
|
|
> 4 | const search = await searchParams
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/missing-suspense-around-search-params/page.tsx (4:18)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-around-search-params'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-around-search-params": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-around-search-params".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-around-search-params" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - does not require Suspense around search params', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-no-suspense-around-search-params?foo=bar'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-no-suspense-around-search-params'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - target segment not visible in all navigations', async () => {
|
|
if (isNextDev) {
|
|
// Notable special case -- we accept that the segment with the assertion might not
|
|
// *itself* be visible in all navigations as long as they're instant.
|
|
// A parent layout might be blocked from rendering the children slot,
|
|
// but that's fine as long as it provides a fallback.
|
|
//
|
|
// This is in opposition to an alternate model we considered at some point,
|
|
// where putting an assertion on a segment would mean that it must be visible
|
|
// in all navigations (which would require that its parent layouts must never
|
|
// block the children slots)
|
|
const browser = await navigateTo(
|
|
'/default/static/valid-blocked-children'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender('/default/static/valid-blocked-children')
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - suspense too high', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/suspense-too-high'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/suspense-too-high/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/suspense-too-high/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/suspense-too-high/page.tsx (6:16) @ Page
|
|
> 6 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/suspense-too-high/page.tsx (6:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/suspense-too-high'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/suspense-too-high": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at a (<anonymous>)
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/suspense-too-high".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/suspense-too-high" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - suspense too high', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/suspense-too-high'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/runtime/suspense-too-high/page.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/runtime/suspense-too-high/page.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/runtime/suspense-too-high/page.tsx (24:19) @ Dynamic
|
|
> 24 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Dynamic app/suspense-in-root/runtime/suspense-too-high/page.tsx (24:19)",
|
|
"Page app/suspense-in-root/runtime/suspense-too-high/page.tsx (17:9)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/suspense-too-high'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/suspense-too-high": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at main (<anonymous>)
|
|
at a (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/suspense-too-high".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/suspense-too-high" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - sync IO after runtime API', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-sync-io'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"description": "Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/runtime/invalid-sync-io/page.tsx (8:20) @ Page
|
|
> 8 | const now = Date.now()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/runtime/invalid-sync-io/page.tsx (8:20)",
|
|
"Page <anonymous>",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-sync-io'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at a (app/suspense-in-root/runtime/invalid-sync-io/page.tsx:8:20)
|
|
6 | export default async function Page() {
|
|
7 | await cookies()
|
|
> 8 | const now = Date.now()
|
|
| ^
|
|
9 | return (
|
|
10 | <main>
|
|
11 | <p>This page uses sync IO after awaiting cookies(): {now}</p>
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at b (app/suspense-in-root/runtime/invalid-sync-io/page.tsx:8:20)
|
|
6 | export default async function Page() {
|
|
7 | await cookies()
|
|
> 8 | const now = Date.now()
|
|
| ^
|
|
9 | return (
|
|
10 | <main>
|
|
11 | <p>This page uses sync IO after awaiting cookies(): {now}</p>
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-sync-io" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - sync IO in runtime segment with valid static parent', async () => {
|
|
// The static parent layout has sync IO after cookies() which is fine
|
|
// because it's not runtime-prefetchable. But the page itself has
|
|
// runtime prefetch enabled and also has sync IO after cookies(),
|
|
// which should error.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx (12:20) @ Page
|
|
> 12 | const now = Date.now()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx (12:20)",
|
|
"Page <anonymous>",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at a (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
|
|
10 | export default async function Page() {
|
|
11 | await cookies()
|
|
> 12 | const now = Date.now()
|
|
| ^
|
|
13 | return (
|
|
14 | <main>
|
|
15 | <p>Runtime page with sync IO after cookies: {now}</p>
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at b (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
|
|
10 | export default async function Page() {
|
|
11 | await cookies()
|
|
> 12 | const now = Date.now()
|
|
| ^
|
|
13 | return (
|
|
14 | <main>
|
|
15 | <p>Runtime page with sync IO after cookies: {now}</p>
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at c (app/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent/page.tsx:12:20)
|
|
10 | export default async function Page() {
|
|
11 | await cookies()
|
|
> 12 | const now = Date.now()
|
|
| ^
|
|
13 | return (
|
|
14 | <main>
|
|
15 | <p>Runtime page with sync IO after cookies: {now}</p>
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-sync-io-in-runtime-with-valid-static-parent" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - sync IO after public cache with cookie input', async () => {
|
|
// A public "use cache" function receives cookies() as a promise
|
|
// input (for cache keying). The cache body doesn't read the cookies.
|
|
// After the cache resolves, Date.now() is sync IO that should error
|
|
// because the cookies input causes the cache to resolve during the
|
|
// EarlyRuntime stage where canSyncInterrupt returns true.
|
|
//
|
|
// If the stage discrimination for cache inputs were broken (always
|
|
// using Runtime instead of getRuntimeStage), the cookies would
|
|
// resolve at Runtime where canSyncInterrupt returns false, and the
|
|
// sync IO would be silently allowed.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx (28:20) @ Page
|
|
> 28 | const now = Date.now()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx (28:20)",
|
|
"Page <anonymous>",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" accessed cookie "testCookie" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`cookies\` array, or \`{ name: "testCookie", value: null }\` if it should be absent.
|
|
at <unknown> (app/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input/page.tsx:26:49)
|
|
24 |
|
|
25 | export default async function Page() {
|
|
> 26 | const cookiePromise = cookies().then((c) => c.get('testCookie')?.value ?? '')
|
|
| ^
|
|
27 | await cachedFn(cookiePromise)
|
|
28 | const now = Date.now()
|
|
29 | return ( {
|
|
digest: 'INSTANT_VALIDATION_ERROR'
|
|
}
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-sync-io-after-cache-with-cookie-input" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - sync IO in a static parent layout is allowed', async () => {
|
|
// Sync IO (Date.now()) in a layout that is NOT runtime-prefetchable
|
|
// should not error, even though the child page has runtime prefetch
|
|
// enabled. Only segments that are runtime-prefetchable should be
|
|
// validated for sync IO after runtime APIs.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-static-parent'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-static-parent'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - sync IO in generateMetadata', async () => {
|
|
// The page has runtime prefetch enabled. generateMetadata uses
|
|
// cookies() then Date.now(). Since metadata belongs to the Page
|
|
// and the Page is runtime-prefetchable, this should error.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx (9:20) @ Module.generateMetadata
|
|
> 9 | const now = Date.now()
|
|
| ^",
|
|
"stack": [
|
|
"Module.generateMetadata app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx (9:20)",
|
|
"Next.MetadataOutlet <anonymous>",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at Module.e [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx:9:20)
|
|
7 | export async function generateMetadata() {
|
|
8 | await cookies()
|
|
> 9 | const now = Date.now()
|
|
| ^
|
|
10 | return {
|
|
11 | title: \`Sync IO in metadata: \${now}\`,
|
|
12 | }
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at Module.e [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata/page.tsx:9:20)
|
|
7 | export async function generateMetadata() {
|
|
8 | await cookies()
|
|
> 9 | const now = Date.now()
|
|
| ^
|
|
10 | return {
|
|
11 | title: \`Sync IO in metadata: \${now}\`,
|
|
12 | }
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-sync-io-in-generate-metadata" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - sync IO in generateMetadata on a static page is allowed', async () => {
|
|
// The page does NOT have runtime prefetch. generateMetadata uses
|
|
// cookies() then Date.now(). Since no segment is runtime-prefetchable,
|
|
// sync IO in generateMetadata should be allowed.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-generate-metadata-static-page'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-generate-metadata-static-page'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - sync IO in layout generateMetadata when page is prefetchable', async () => {
|
|
// The layout has generateMetadata with sync IO after cookies().
|
|
// The layout itself does NOT have runtime prefetch, but the child
|
|
// page does. Since metadata belongs to the Page, and the Page is
|
|
// runtime-prefetchable, sync IO in the layout's generateMetadata
|
|
// should error.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"code": "E394",
|
|
"description": "Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx (11:20) @ Module.generateMetadata
|
|
> 11 | const now = Date.now()
|
|
| ^",
|
|
"stack": [
|
|
"Module.generateMetadata app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx (11:20)",
|
|
"Next.MetadataOutlet <anonymous>",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
|
|
9 | export async function generateMetadata() {
|
|
10 | await cookies()
|
|
> 11 | const now = Date.now()
|
|
| ^
|
|
12 | return {
|
|
13 | title: \`Layout metadata with sync IO: \${now}\`,
|
|
14 | }
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
|
|
9 | export async function generateMetadata() {
|
|
10 | await cookies()
|
|
> 11 | const now = Date.now()
|
|
| ^
|
|
12 | return {
|
|
13 | title: \`Layout metadata with sync IO: \${now}\`,
|
|
14 | }
|
|
Error: Route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" used \`Date.now()\` before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-runtime-current-time
|
|
at Module.d [as generateMetadata] (app/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata/layout.tsx:11:20)
|
|
9 | export async function generateMetadata() {
|
|
10 | await cookies()
|
|
> 11 | const now = Date.now()
|
|
| ^
|
|
12 | return {
|
|
13 | title: \`Layout metadata with sync IO: \${now}\`,
|
|
14 | }
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-sync-io-in-layout-generate-metadata" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - sync IO in layout generateMetadata when page is NOT prefetchable', async () => {
|
|
// The layout has generateMetadata with sync IO after cookies().
|
|
// Neither the layout nor the page has runtime prefetch. Since no
|
|
// segment is runtime-prefetchable, sync IO in generateMetadata
|
|
// should be allowed.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-layout-generate-metadata-static-page'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-sync-io-in-layout-generate-metadata-static-page'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - no suspense needed around dynamic in page if loading.js is present', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-only-loading-around-dynamic'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-only-loading-around-dynamic'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
// The page is inside a route group with loading.tsx on the parent
|
|
// URL segment. Validation conservatively treats the route group as
|
|
// a potential shared boundary where loading.tsx's Suspense would
|
|
// already be revealed. A more advanced system could analyze siblings
|
|
// to determine if such a navigation is actually possible.
|
|
it('invalid - loading.js above route group does not cover dynamic in page', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-loading-above-route-group'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (34:19) @ Dynamic
|
|
> 34 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Dynamic app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (34:19)",
|
|
"Page app/suspense-in-root/static/invalid-loading-above-route-group/(group)/page.tsx (22:9)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-loading-above-route-group/(group)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-loading-above-route-group": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at main (<anonymous>)
|
|
at a (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-loading-above-route-group".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-loading-above-route-group" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - loading.js covers page, but not layout at the same level', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-dynamic-layout-with-loading'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (24:19) @ Dynamic
|
|
> 24 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Dynamic app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (24:19)",
|
|
"Layout app/suspense-in-root/static/invalid-dynamic-layout-with-loading/layout.tsx (15:9)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-dynamic-layout-with-loading'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-dynamic-layout-with-loading": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-dynamic-layout-with-loading".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-dynamic-layout-with-loading" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
describe('blocking', () => {
|
|
it('valid - blocking layout with unstable_instant = false is allowed to block', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/blocking-layout'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/blocking-layout'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - missing suspense inside blocking layout', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (6:16) @ Page
|
|
> 6 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic/page.tsx (6:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/blocking-layout/missing-suspense-around-dynamic" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - blocking page inside a static layout is allowed if the layout has suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/default/static/valid-blocking-inside-static'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/default/static/valid-blocking-inside-static'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - blocking page inside a runtime layout is allowed if the layout has suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/valid-blocking-inside-runtime'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/valid-blocking-inside-runtime'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - blocking page inside a static layout is not allowed if the layout has no suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-blocking-inside-static'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-blocking-inside-static/layout.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/invalid-blocking-inside-static/page.tsx (6:16) @ BlockingPage
|
|
> 6 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"BlockingPage app/suspense-in-root/static/invalid-blocking-inside-static/page.tsx (6:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-blocking-inside-static'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-blocking-inside-static": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-blocking-inside-static".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-blocking-inside-static" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - blocking page inside a runtime layout is not allowed if the layout has no suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/invalid-blocking-inside-runtime'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/runtime/invalid-blocking-inside-runtime/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/runtime/invalid-blocking-inside-runtime/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/runtime/invalid-blocking-inside-runtime/page.tsx (6:19) @ BlockingPage
|
|
> 6 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"BlockingPage app/suspense-in-root/runtime/invalid-blocking-inside-runtime/page.tsx (6:19)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/invalid-blocking-inside-runtime'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/invalid-blocking-inside-runtime": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/invalid-blocking-inside-runtime".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/invalid-blocking-inside-runtime" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('invalid - missing suspense in parallel slot', () => {
|
|
// The "caused by" source differs between bundlers due to parallel
|
|
it('index', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/layout.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/layout.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/page.tsx (4:16) @ IndexSlot
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"IndexSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-in-parallel-route" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('subpage', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route/foo'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/foo/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/foo/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/foo/page.tsx (4:16) @ FooSlot
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"FooSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/foo/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route/foo'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route/foo": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route/foo".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-in-parallel-route/foo" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('default slot', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route/bar'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/bar/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/missing-suspense-in-parallel-route/bar/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/default.tsx (4:16) @ DefaultSlot
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"DefaultSlot app/suspense-in-root/static/missing-suspense-in-parallel-route/@slot/default.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/missing-suspense-in-parallel-route/bar'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/missing-suspense-in-parallel-route/bar": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/missing-suspense-in-parallel-route/bar".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/missing-suspense-in-parallel-route/bar" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('client components', () => {
|
|
it('unable to validate - parent suspends on client data and blocks children', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-client-data-blocks-validation'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/invalid-client-data-blocks-validation/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-client-data-blocks-validation/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1082",
|
|
"description": "Route "/suspense-in-root/static/invalid-client-data-blocks-validation": Could not validate \`unstable_instant\` because a Client Component in a parent segment prevented the page from rendering.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx (12:19) @ FetchesClientData
|
|
> 12 | const data = use(promise)
|
|
| ^",
|
|
"stack": [
|
|
"FetchesClientData app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx (12:19)",
|
|
"Layout app/suspense-in-root/static/invalid-client-data-blocks-validation/layout.tsx (17:9)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-client-data-blocks-validation'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"client-data-fetching-lib :: MISS my-key
|
|
client-data-fetching-lib :: MISS my-key
|
|
client-data-fetching-lib :: MISS my-key
|
|
Error: Route "/suspense-in-root/static/invalid-client-data-blocks-validation": Could not validate \`unstable_instant\` because a Client Component in a parent segment prevented the page from rendering.
|
|
at <unknown> (app/suspense-in-root/static/invalid-client-data-blocks-validation/client.tsx:6:37)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
4 | import { useDataCache } from '../../../../client-data-fetching-lib/client'
|
|
5 |
|
|
> 6 | export function FetchesClientData({ children }) {
|
|
| ^
|
|
7 | const dataCache = useDataCache()
|
|
8 | const promise = dataCache.getOrLoad('my-key', async () => {
|
|
9 | await new Promise<void>((resolve) => setTimeout(resolve, 10))
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-data-blocks-validation".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-client-data-blocks-validation" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - parent suspends on client data but does not block children', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-client-data-does-not-block-validation'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-client-data-does-not-block-validation'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - parent uses sync IO in a client component', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/sync-io'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/sync-io'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - parent uses dynamic usePathname() in a client component', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/dynamic-params/123'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/dynamic-params/[id]'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - parent uses useSearchParams() in a client component', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/search-params'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-client-api-in-parent/search-params'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('client errors', () => {
|
|
function removeExpectedError(
|
|
errors: RedboxSnapshot,
|
|
shouldRemove: (error: ErrorSnapshot) => boolean
|
|
): ErrorSnapshot[] {
|
|
if (!Array.isArray(errors)) {
|
|
throw new Error('Expected to receive multiple errors to filter')
|
|
}
|
|
let found = false
|
|
const result = errors.filter((err) => {
|
|
if (shouldRemove(err)) {
|
|
found = true
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
})
|
|
if (!found) {
|
|
throw new Error(
|
|
`Did not find expected error in errors array: ${JSON.stringify(errors, null, 2)}`
|
|
)
|
|
}
|
|
return result
|
|
}
|
|
|
|
it('unable to validate - client error in parent blocks children', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-client-error-in-parent-blocks-children'
|
|
)
|
|
// We expect a collapsed redbox. We need to open it to assert on the messages.
|
|
await openRedbox(browser)
|
|
|
|
let errors = await createRedboxSnapshot(browser, next)
|
|
|
|
if (!isClientNav) {
|
|
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
|
|
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
|
|
// and sometimes it's after.
|
|
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
|
|
errors = removeExpectedError(errors, (err) => {
|
|
return (
|
|
err.label === 'Recoverable Error' &&
|
|
err.description.startsWith(
|
|
'Switched to client rendering because the server rendering errored:\n\nNo SSR please'
|
|
)
|
|
)
|
|
})
|
|
}
|
|
|
|
expect(errors).toMatchInlineSnapshot(`
|
|
[
|
|
{
|
|
"description": "Route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/page.tsx (1:33)",
|
|
],
|
|
},
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Error",
|
|
"message": "No SSR please",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx (5:11) @ ErrorInSSR
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^",
|
|
"stack": [
|
|
"ErrorInSSR app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx (5:11)",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1118",
|
|
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/layout.tsx (19:11) @ Layout
|
|
> 19 | <ErrorInSSR>{children}</ErrorInSSR>
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/layout.tsx (19:11)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-client-error-in-parent-blocks-children'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
|
|
at ignore-listed frames
|
|
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
|
|
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx:3:30)
|
|
at a (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>)
|
|
1 | 'use client'
|
|
2 |
|
|
> 3 | export function ErrorInSSR({ children }) {
|
|
| ^
|
|
4 | if (typeof window === 'undefined') {
|
|
5 | throw new Error('No SSR please')
|
|
6 | } {
|
|
[cause]: Error: No SSR please
|
|
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-blocks-children/client.tsx:5:11)
|
|
3 | export function ErrorInSSR({ children }) {
|
|
4 | if (typeof window === 'undefined') {
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^
|
|
6 | }
|
|
7 | return <>{children}</>
|
|
8 | }
|
|
}
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-client-error-in-parent-blocks-children" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('unable to validate - client error in component from node_modules blocks children', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-error-in-node-modules-blocks-children'
|
|
)
|
|
// We expect a collapsed redbox. We need to open it to assert on the messages.
|
|
await openRedbox(browser)
|
|
|
|
let errors = await createRedboxSnapshot(browser, next)
|
|
|
|
if (!isClientNav) {
|
|
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
|
|
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
|
|
// and sometimes it's after.
|
|
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
|
|
errors = removeExpectedError(errors, (err) => {
|
|
return (
|
|
err.label === 'Recoverable Error' &&
|
|
err.description.startsWith(
|
|
'Switched to client rendering because the server rendering errored:\n\nError from node_modules'
|
|
)
|
|
)
|
|
})
|
|
}
|
|
|
|
expect(errors).toMatchInlineSnapshot(`
|
|
[
|
|
{
|
|
"description": "Route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/page.tsx (1:33)",
|
|
],
|
|
},
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Error",
|
|
"message": "Error from node_modules",
|
|
"source": null,
|
|
"stack": [],
|
|
},
|
|
],
|
|
"code": "E1118",
|
|
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/layout.tsx (21:11) @ Layout
|
|
> 21 | <ErrorInSSRFromPackage>{children}</ErrorInSSRFromPackage>
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/static/invalid-error-in-node-modules-blocks-children/layout.tsx (21:11)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-error-in-node-modules-blocks-children'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
|
|
at ignore-listed frames
|
|
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
|
|
at a (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>) {
|
|
[cause]: Error: Error from node_modules
|
|
at ignore-listed frames
|
|
}
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-error-in-node-modules-blocks-children" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('unable to validate - CSR bailout from next/dynamic blocks children', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-csr-bailout-blocks-children'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
[
|
|
{
|
|
"description": "Route "/suspense-in-root/static/invalid-csr-bailout-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-csr-bailout-blocks-children/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-csr-bailout-blocks-children/page.tsx (1:33)",
|
|
],
|
|
},
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Error",
|
|
"message": "Bail out to client-side rendering: next/dynamic",
|
|
"source": null,
|
|
"stack": [],
|
|
},
|
|
],
|
|
"code": "E1118",
|
|
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-csr-bailout-blocks-children/layout.tsx (19:9) @ Layout
|
|
> 19 | <LazyClientWrapperWithNoSSR>{children}</LazyClientWrapperWithNoSSR>
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/static/invalid-csr-bailout-blocks-children/layout.tsx (19:9)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-csr-bailout-blocks-children'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-csr-bailout-blocks-children": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
|
|
at ignore-listed frames
|
|
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
|
|
at a (<anonymous>)
|
|
at b (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at c (<anonymous>) {
|
|
[cause]: Error: Bail out to client-side rendering: next/dynamic
|
|
at ignore-listed frames {
|
|
reason: 'next/dynamic',
|
|
digest: 'BAILOUT_TO_CLIENT_SIDE_RENDERING'
|
|
}
|
|
}
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-csr-bailout-blocks-children".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-csr-bailout-blocks-children" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('unable to validate - client error from sibling of children slot without suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/invalid-client-error-in-parent-sibling'
|
|
)
|
|
|
|
if (isClientNav) {
|
|
// In a client navigation, the redbox will be collapsed.
|
|
await openRedbox(browser)
|
|
} else {
|
|
// In SSR, the redbox will be open due to the missing tags error.
|
|
await waitForRedbox(browser)
|
|
}
|
|
|
|
let errors = await createRedboxSnapshot(browser, next)
|
|
if (!isClientNav) {
|
|
// In SSR, we expect a "Switched to client rendering ..." error because we deliberately throw in a client component.
|
|
// However, the timing of when it appears is inconsistent -- sometimes it's before validation errors,
|
|
// and sometimes it's after.
|
|
// To avoid flakiness, we filter it out (but assert that it appears in the redbox)
|
|
errors = removeExpectedError(errors, (err) => {
|
|
return (
|
|
err.label === 'Runtime Error' &&
|
|
err.description.startsWith(
|
|
'Missing <html> and <body> tags in the root layout.'
|
|
)
|
|
)
|
|
})
|
|
}
|
|
|
|
expect(errors).toMatchInlineSnapshot(`
|
|
[
|
|
{
|
|
"description": "Route "/suspense-in-root/static/invalid-client-error-in-parent-sibling": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/invalid-client-error-in-parent-sibling/page.tsx (1:33)",
|
|
],
|
|
},
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Error",
|
|
"message": "No SSR please",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx (5:11) @ ErrorInSSR
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^",
|
|
"stack": [
|
|
"ErrorInSSR app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx (5:11)",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1118",
|
|
"description": "An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.",
|
|
"environmentLabel": "Server",
|
|
"label": "Console Error",
|
|
"source": "app/suspense-in-root/static/invalid-client-error-in-parent-sibling/layout.tsx (20:7) @ Layout
|
|
> 20 | <ErrorInSSR />
|
|
| ^",
|
|
"stack": [
|
|
"Layout app/suspense-in-root/static/invalid-client-error-in-parent-sibling/layout.tsx (20:7)",
|
|
],
|
|
},
|
|
]
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/invalid-client-error-in-parent-sibling'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/invalid-client-error-in-parent-sibling": Could not validate \`unstable_instant\` because the target segment was prevented from rendering, likely due to the following error.
|
|
at ignore-listed frames
|
|
Error: An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.
|
|
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx:5:11)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
3 | export function ErrorInSSR() {
|
|
4 | if (typeof window === 'undefined') {
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^
|
|
6 | }
|
|
7 | return <div>Hello, browser!</div>
|
|
8 | } {
|
|
[cause]: Error: No SSR please
|
|
at <unknown> (app/suspense-in-root/static/invalid-client-error-in-parent-sibling/client.tsx:5:11)
|
|
3 | export function ErrorInSSR() {
|
|
4 | if (typeof window === 'undefined') {
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^
|
|
6 | }
|
|
7 | return <div>Hello, browser!</div>
|
|
8 | }
|
|
}
|
|
Build-time instant validation failed for route "/suspense-in-root/static/invalid-client-error-in-parent-sibling".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/invalid-client-error-in-parent-sibling" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - client error from sibling of children slot with suspense', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation'
|
|
)
|
|
await waitForValidation(await browser.url(), getCliOutputSinceMark)
|
|
if (isClientNav) {
|
|
// In a client nav, no errors should be reported.
|
|
await waitForNoErrorToast(browser, NO_VALIDATION_ERRORS_WAIT)
|
|
} else {
|
|
// In SSR, we expect to only see the error coming from react.
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"description": "Switched to client rendering because the server rendering errored:
|
|
|
|
No SSR please",
|
|
"environmentLabel": null,
|
|
"label": "Recoverable Error",
|
|
"source": "app/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation/client.tsx (5:11) @ ErrorInSSR
|
|
> 5 | throw new Error('No SSR please')
|
|
| ^",
|
|
"stack": [
|
|
"ErrorInSSR app/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation/client.tsx (5:11)",
|
|
],
|
|
}
|
|
`)
|
|
}
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/valid-client-error-in-parent-does-not-block-validation'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('head', () => {
|
|
it('valid - runtime prefetch - dynamic generateMetadata does not block navigation', async () => {
|
|
if (isNextDev) {
|
|
// Metadata streams and does not block navigation, so it can access
|
|
// dynamic data without failing validation.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/valid-dynamic-metadata-in-runtime'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/valid-dynamic-metadata-in-runtime'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - static prefetch - runtime generateMetadata does not block navigation', async () => {
|
|
if (isNextDev) {
|
|
// Metadata streams and does not block navigation, so it can access
|
|
// runtime data without failing validation.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/valid-runtime-metadata-in-static'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/valid-runtime-metadata-in-static'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - static prefetch - runtime generateViewport blocks navigation', async () => {
|
|
if (isNextDev) {
|
|
// if generateViewport uses runtime data and we use a static prefetch,
|
|
// we won't have it available when navigating, so we'll block and should fail validation.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/invalid-runtime-viewport-in-static'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (8:33) @ unstable_instant
|
|
> 8 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (8:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1165",
|
|
"description": "Runtime data was accessed inside generateViewport()
|
|
|
|
Viewport metadata needs to be available on page load so accessing data that comes from a user Request while producing it prevents Next.js from prerendering an initial UI.cookies(), headers(), params, and searchParams are examples of Runtime data that can only come from a user request.
|
|
|
|
To fix this:
|
|
|
|
Remove the Runtime data requirement from generateViewport. This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
|
|
|
|
or
|
|
|
|
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
|
|
|
|
params are usually considered Runtime data but if all params are provided a value using generateStaticParams they can be statically prerendered.
|
|
|
|
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
|
|
"environmentLabel": "Server",
|
|
"label": "Blocking Route",
|
|
"source": "app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (11:16) @ Module.generateViewport
|
|
> 11 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Module.generateViewport app/suspense-in-root/head/invalid-runtime-viewport-in-static/page.tsx (11:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/invalid-runtime-viewport-in-static'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/head/invalid-runtime-viewport-in-static": Next.js encountered runtime data such as \`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
|
|
at ignore-listed frames
|
|
Build-time instant validation failed for route "/suspense-in-root/head/invalid-runtime-viewport-in-static".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/head/invalid-runtime-viewport-in-static" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - runtime prefetch - dynamic viewport blocks navigation', async () => {
|
|
if (isNextDev) {
|
|
// if generateViewport uses dynamic data and we use a runtime prefetch,
|
|
// we won't have it available when navigating, so we'll block and should fail validation.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/invalid-dynamic-viewport-in-runtime'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (6:33) @ unstable_instant
|
|
> 6 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (6:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1167",
|
|
"description": "Data that blocks navigation was accessed inside generateViewport()
|
|
|
|
Viewport metadata needs to be available on page load so accessing data that waits for a user navigation while producing it prevents Next.js from prerendering an initial UI. Uncached data such as fetch(...), cached data with a low expire time, or connection() are all examples of data that only resolve on navigation.
|
|
|
|
To fix this:
|
|
|
|
Move the asynchronous await into a Cache Component ("use cache"). This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
|
|
|
|
or
|
|
|
|
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
|
|
|
|
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
|
|
"environmentLabel": "Server",
|
|
"label": "Blocking Route",
|
|
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (11:19) @ Module.generateViewport
|
|
> 11 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Module.generateViewport app/suspense-in-root/head/invalid-dynamic-viewport-in-runtime/page.tsx (11:19)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/invalid-dynamic-viewport-in-runtime'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime": Next.js encountered uncached data such as \`fetch(...)\` or \`connection()\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
|
|
at ignore-listed frames
|
|
Build-time instant validation failed for route "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/head/invalid-dynamic-viewport-in-runtime" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('valid - runtime prefetch - runtime generateViewport does not block navigation', async () => {
|
|
if (isNextDev) {
|
|
// if generateViewport uses runtime data and we use a runtime prefetch,
|
|
// we'll have it available when navigating, so we won't block and validation should succeed.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/valid-runtime-viewport-in-runtime'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/valid-runtime-viewport-in-runtime'
|
|
)
|
|
expectNoBuildValidationErrors(result)
|
|
}
|
|
})
|
|
|
|
it('valid - blocking layout - dynamic viewport is allowed to block', async () => {
|
|
if (isNextDev) {
|
|
// if generateViewport uses dynamic data, it'll always block regardless of prefetching.
|
|
// however, this is valid if the page opts into blocking via `instant = false`.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/valid-dynamic-viewport-in-blocking'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/valid-dynamic-viewport-in-blocking'
|
|
)
|
|
// The only way to allow this is to have `instant = false` on the page,
|
|
// and no assertions in layouts above -- they can't pass because a dynamic
|
|
// generateViewport will always block the navigation.
|
|
// This test is just here to ensure this behavior doesn't break.
|
|
expectBuildValidationSkipped(result)
|
|
}
|
|
})
|
|
|
|
it('invalid - blocking page inside static - dynamic viewport is not allowed to block', async () => {
|
|
if (isNextDev) {
|
|
// if generateViewport uses dynamic data, it'll always block regardless of prefetching.
|
|
// this can be allowed if a page opts into blocking. but if it violates a static
|
|
// assertion on a parent layout, it should still fail.
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static'
|
|
)
|
|
// TODO(instant-validation): why aren't we pointing to `await connection()` here?
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1167",
|
|
"description": "Data that blocks navigation was accessed inside generateViewport()
|
|
|
|
Viewport metadata needs to be available on page load so accessing data that waits for a user navigation while producing it prevents Next.js from prerendering an initial UI. Uncached data such as fetch(...), cached data with a low expire time, or connection() are all examples of data that only resolve on navigation.
|
|
|
|
To fix this:
|
|
|
|
Move the asynchronous await into a Cache Component ("use cache"). This allows Next.js to statically prerender generateViewport() as part of the HTML document, so it's instantly visible to the user.
|
|
|
|
or
|
|
|
|
Put a <Suspense> around your document <body>.This indicate to Next.js that you are opting into allowing blocking navigations for any page.
|
|
|
|
Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport",
|
|
"environmentLabel": "Server",
|
|
"label": "Blocking Route",
|
|
"source": "app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/page.tsx (6:23) @ Module.generateViewport
|
|
> 6 | export async function generateViewport(): Promise<Viewport> {
|
|
| ^",
|
|
"stack": [
|
|
"Module.generateViewport app/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static/page.tsx (6:23)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static": Next.js encountered uncached data such as \`fetch(...)\` or \`connection()\` inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport
|
|
at ignore-listed frames
|
|
Build-time instant validation failed for route "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/head/invalid-dynamic-viewport-in-blocking-inside-static" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('route groups', () => {
|
|
it('invalid - config on route group layout - cookies() blocks below', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-config-only'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-config-only/(group)/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-config-only/(group)/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-config-only/(group)/page.tsx (4:16) @ Page
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/route-group-config-only/(group)/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-config-only/(group)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-config-only": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-only".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-config-only" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - config on both route group and segment layout - cookies() blocks below', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-config-and-segment-config'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-config-and-segment-config/(group)/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-config-and-segment-config/(group)/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-config-and-segment-config/(group)/page.tsx (4:16) @ Page
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/route-group-config-and-segment-config/(group)/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-config-and-segment-config/(group)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-config-and-segment-config": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-and-segment-config".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-config-and-segment-config" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - config on segment layout - cookies() blocks through route group below', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-segment-config-only'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-segment-config-only/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-segment-config-only/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-segment-config-only/(group)/page.tsx (4:16) @ Page
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/route-group-segment-config-only/(group)/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-segment-config-only/(group)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-segment-config-only": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-segment-config-only".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-segment-config-only" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - config on route group layout - cookies() blocks in deeper segment', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-config-with-deeper-segment/inner'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner/page.tsx (4:16) @ Page
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-config-with-deeper-segment/(group)/inner'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-config-with-deeper-segment/inner": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-config-with-deeper-segment/inner".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-config-with-deeper-segment/inner" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - config on segment layout inside route group - cookies() blocks below', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-deeper-segment-config/inner'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/layout.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/layout.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/page.tsx (4:16) @ Page
|
|
> 4 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner/page.tsx (4:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-deeper-segment-config/(group)/inner'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-deeper-segment-config/inner": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-deeper-segment-config/inner".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-deeper-segment-config/inner" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('route group shared boundary', () => {
|
|
// When navigating from /foo to /, (outer)/layout is shared — its
|
|
// Suspense doesn't apply to the new tree. (inner)/layout awaits
|
|
// cookies() without its own Suspense, so the navigation should
|
|
// block and produce a validation error. The group depth iteration
|
|
// catches this by treating (outer) as shared and (inner) as new.
|
|
it('invalid - blocking layout inside shared route group boundary', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/route-group-shared-boundary'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/page.tsx (6:33) @ unstable_instant
|
|
> 6 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/page.tsx (6:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/layout.tsx (13:16) @ InnerLayout
|
|
> 13 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"InnerLayout app/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)/layout.tsx (13:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/route-group-shared-boundary/(outer)/(inner)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/route-group-shared-boundary": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at a (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at b (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/route-group-shared-boundary".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/route-group-shared-boundary" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('parallel slots with different group depths', () => {
|
|
// @slot has 3 groups, children has 2 groups. The validation
|
|
// iterates from deepest group depth (3) down to 0. Deeper
|
|
// holes in one slot are detected before shallower holes in
|
|
// another slot because the shallower slot stays entirely
|
|
// shared at higher group depths.
|
|
|
|
it('invalid - deep hole in @slot detected before shallow hole in children', async () => {
|
|
// @slot/(g1)/(g2)/(g3)/layout.tsx has cookies() — the 3rd group blocks.
|
|
// (b1)/(b2)/layout.tsx has cookies() — the 2nd group blocks.
|
|
// At groupDepth=2: @slot's g2 is boundary, g3 enters new tree →
|
|
// g3's cookies() detected at Static stage. children only has
|
|
// 2 groups which is < groupDepth=2, so children stays entirely
|
|
// shared. Only @slot's error is reported.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/parallel-group-depths-deep-slot-hole'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/layout.tsx (7:16) @ G3Layout
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"G3Layout app/suspense-in-root/static/parallel-group-depths-deep-slot-hole/@slot/(g1)/(g2)/(g3)/layout.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/parallel-group-depths-deep-slot-hole/(b1)/(b2)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/parallel-group-depths-deep-slot-hole": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/parallel-group-depths-deep-slot-hole".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/parallel-group-depths-deep-slot-hole" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - children hole detected before shallow @slot hole', async () => {
|
|
// @slot/(g1)/layout.tsx has cookies() — the 1st group blocks.
|
|
// (b1)/(b2)/layout.tsx has cookies() — the 2nd group blocks.
|
|
// At groupDepth=1: @slot's g1 is boundary (shared, cookies()
|
|
// runs at Dynamic stage — not detected). children's b1 is
|
|
// boundary, b2 enters new tree → b2's cookies() detected.
|
|
// The "caused by" config source differs between bundlers due
|
|
// to parallel route key iteration order when slot markers
|
|
// aren't supported in webpack.
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/parallel-group-depths-shallow-slot-hole'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/page.tsx (1:33) @ unstable_instant
|
|
> 1 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/page.tsx (1:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/layout.tsx (5:16) @ B2Layout
|
|
> 5 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"B2Layout app/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)/layout.tsx (5:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/parallel-group-depths-shallow-slot-hole/(b1)/(b2)'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/parallel-group-depths-shallow-slot-hole" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
// TODO(instant-validation): The error message for this case is
|
|
// technically correct but confusing. The developer configured
|
|
// runtime prefetching on the inner layout, so they expect
|
|
// cookies() to be fine. But the parent layout above the config
|
|
// gets static prefetching by default, making cookies() a
|
|
// blocking violation. The error should explain that segments
|
|
// above the config use static prefetching and suggest either
|
|
// moving the config up or adding Suspense around the runtime
|
|
// data in the parent layout.
|
|
it('invalid - static layout above runtime config blocks navigation', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/runtime/static-layout-above-runtime-config/inner'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/runtime/static-layout-above-runtime-config/inner/layout.tsx (6:33) @ unstable_instant
|
|
> 6 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/runtime/static-layout-above-runtime-config/inner/layout.tsx (6:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/runtime/static-layout-above-runtime-config/layout.tsx (15:16) @ StaticLayout
|
|
> 15 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"StaticLayout app/suspense-in-root/runtime/static-layout-above-runtime-config/layout.tsx (15:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/runtime/static-layout-above-runtime-config/inner'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/runtime/static-layout-above-runtime-config/inner": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/runtime/static-layout-above-runtime-config/inner".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/runtime/static-layout-above-runtime-config/inner" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
describe('config depth preference', () => {
|
|
// When multiple slots have instant configs at different depths,
|
|
// the deepest config is preferred as the root cause. At equal
|
|
// depth, children is preferred over named slots.
|
|
|
|
it('invalid - deeper children config preferred over shallower slot config', async () => {
|
|
// children has config deep (deeper/still/deep/page.tsx, depth 2)
|
|
// @anotherSlot has config shallow (page.tsx, depth 0)
|
|
// @slot blocks with no config — cause should be children's deep config
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/config-depth-preference/deeper/still/deep'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/config-depth-preference/deeper/still/deep/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/config-depth-preference/deeper/still/deep/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/config-depth-preference/@slot/[...catchall]/page.tsx (8:16) @ CatchallSlotPage
|
|
> 8 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"CatchallSlotPage app/suspense-in-root/static/config-depth-preference/@slot/[...catchall]/page.tsx (8:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/config-depth-preference/deeper/still/deep'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error [InvariantError]: Invariant: An unexpected error occurred during instant validation. This is a bug in Next.js.
|
|
at ignore-listed frames {
|
|
[cause]: Error [InvariantError]: Invariant: Missing value for segment key: "catchall" with dynamic param type: c. This is a bug in Next.js.
|
|
at ignore-listed frames
|
|
}
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - deeper slot config preferred over shallower children catchall', async () => {
|
|
// @anotherSlot has config deep (still/deep/page.tsx, depth 2)
|
|
// children has config shallow ([...rest]/page.tsx, depth 1)
|
|
// @slot blocks with no config — cause should be @anotherSlot's deep config
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/config-depth-preference-slot-wins/deeper/still/deep'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/config-depth-preference-slot-wins/deeper/@anotherSlot/still/deep/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/config-depth-preference-slot-wins/deeper/@anotherSlot/still/deep/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/config-depth-preference-slot-wins/@slot/[...catchall]/page.tsx (7:16) @ CatchallSlotPage
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"CatchallSlotPage app/suspense-in-root/static/config-depth-preference-slot-wins/@slot/[...catchall]/page.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/config-depth-preference-slot-wins/deeper/[...rest]" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - children config preferred at equal depth', async () => {
|
|
// children and @other both have config at same depth (page level)
|
|
// @slot blocks with no config — cause should be children's config
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/config-children-preferred'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/config-children-preferred/page.tsx (4:33) @ unstable_instant
|
|
> 4 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/config-children-preferred/page.tsx (4:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/config-children-preferred/@slot/page.tsx (7:16) @ SlotPage
|
|
> 7 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"SlotPage app/suspense-in-root/static/config-children-preferred/@slot/page.tsx (7:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/config-children-preferred'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/static/config-children-preferred": Next.js encountered runtime data during the initial render.
|
|
|
|
\`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Use \`generateStaticParams\` to make route params static
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at div (<anonymous>)
|
|
at div (<anonymous>)
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/static/config-children-preferred".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/static/config-children-preferred" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('invalid - cross-slot blocking falls back to deep children config', async () => {
|
|
// @slot catchall blocks with no config
|
|
// children has config deep behind a second fork with @panel
|
|
// cause should fall back to children's deep config
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/static/cross-slot-blocking/inner/deep'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/static/cross-slot-blocking/inner/deep/page.tsx (5:33) @ unstable_instant
|
|
> 5 | export const unstable_instant = true
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/static/cross-slot-blocking/inner/deep/page.tsx (5:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1166",
|
|
"description": "Next.js encountered runtime data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/static/cross-slot-blocking/@slot/[...catchall]/page.tsx (8:16) @ CatchallSlotPage
|
|
> 8 | await cookies()
|
|
| ^",
|
|
"stack": [
|
|
"CatchallSlotPage app/suspense-in-root/static/cross-slot-blocking/@slot/[...catchall]/page.tsx (8:16)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/static/cross-slot-blocking/inner/deep'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error [InvariantError]: Invariant: An unexpected error occurred during instant validation. This is a bug in Next.js.
|
|
at ignore-listed frames {
|
|
[cause]: Error [InvariantError]: Invariant: Missing value for segment key: "catchall" with dynamic param type: c. This is a bug in Next.js.
|
|
at ignore-listed frames
|
|
}
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('disabling validation', () => {
|
|
it('in a layout', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/disable-validation/in-layout'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/disable-validation/in-layout'
|
|
)
|
|
expectBuildValidationSkipped(result)
|
|
}
|
|
})
|
|
|
|
it('in a page', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/disable-validation/in-page'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/disable-validation/in-page'
|
|
)
|
|
expectBuildValidationSkipped(result)
|
|
}
|
|
})
|
|
|
|
it('in a page with a parent that has a config', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/disable-validation/in-page-with-outer'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/disable-validation/in-page-with-outer'
|
|
)
|
|
expectBuildValidationSkipped(result)
|
|
}
|
|
})
|
|
|
|
it('disabling dev validation', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/disable-validation/disable-dev'
|
|
)
|
|
await expectNoDevValidationErrors(browser, await browser.url())
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/disable-validation/disable-dev'
|
|
)
|
|
expect(extractBuildValidationError(result.cliOutput))
|
|
.toMatchInlineSnapshot(`
|
|
"Error: Route "/suspense-in-root/disable-validation/disable-dev": Next.js encountered uncached data during the initial render.
|
|
|
|
\`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.
|
|
|
|
Ways to fix this:
|
|
- Cache the data access with \`"use cache"\`
|
|
- Move the data access into a child component within a <Suspense> boundary
|
|
- Set \`export const instant = false\` to allow a blocking route
|
|
|
|
Learn more: https://nextjs.org/docs/messages/blocking-route
|
|
at body (<anonymous>)
|
|
at html (<anonymous>)
|
|
at a (<anonymous>)
|
|
Build-time instant validation failed for route "/suspense-in-root/disable-validation/disable-dev".
|
|
To get a more detailed stack trace and pinpoint the issue, try one of the following:
|
|
- Start the app in development mode by running \`next dev\`, then open "/suspense-in-root/disable-validation/disable-dev" in your browser to investigate the error.
|
|
- Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.
|
|
Stopping prerender due to instant validation errors."
|
|
`)
|
|
expect(result.exitCode).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('disabling build validation', async () => {
|
|
if (isNextDev) {
|
|
const browser = await navigateTo(
|
|
'/suspense-in-root/disable-validation/disable-build'
|
|
)
|
|
await expect(browser).toDisplayCollapsedRedbox(`
|
|
{
|
|
"cause": [
|
|
{
|
|
"label": "Caused by: Instant Validation",
|
|
"source": "app/suspense-in-root/disable-validation/disable-build/page.tsx (3:33) @ unstable_instant
|
|
> 3 | export const unstable_instant = { unstable_disableBuildValidation: true }
|
|
| ^",
|
|
"stack": [
|
|
"unstable_instant app/suspense-in-root/disable-validation/disable-build/page.tsx (3:33)",
|
|
"Set.forEach <anonymous>",
|
|
],
|
|
},
|
|
],
|
|
"code": "E1164",
|
|
"description": "Next.js encountered uncached data during the initial render.",
|
|
"environmentLabel": "Server",
|
|
"label": "Instant",
|
|
"source": "app/suspense-in-root/disable-validation/disable-build/page.tsx (6:19) @ Page
|
|
> 6 | await connection()
|
|
| ^",
|
|
"stack": [
|
|
"Page app/suspense-in-root/disable-validation/disable-build/page.tsx (6:19)",
|
|
],
|
|
}
|
|
`)
|
|
} else {
|
|
const result = await prerender(
|
|
'/suspense-in-root/disable-validation/disable-build'
|
|
)
|
|
expectBuildValidationSkipped(result)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|