Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dynamicIO]: dev navigations should show disallowed dynamic errors #71595

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,12 +522,36 @@ async function generateDynamicFlightRenderResult(
onFlightDataRenderError
)

const rscPayload = await generateDynamicRSCPayload(ctx, options)
const RSCPayload: RSCPayload & {
/** Only available during dynamicIO development builds. Used for logging errors. */
_validation?: Promise<React.ReactNode>
} = await generateDynamicRSCPayload(ctx, options)

if (
// We only want this behavior when running `next dev`
renderOpts.dev &&
// We only want this behavior when we have React's dev builds available
process.env.NODE_ENV === 'development' &&
// We only have a Prerender environment for projects opted into dynamicIO
renderOpts.experimental.dynamicIO
) {
const [resolveValidation, validationOutlet] = createValidationOutlet()
RSCPayload._validation = validationOutlet

spawnDynamicValidationInDev(
resolveValidation,
ctx.componentMod.tree,
ctx,
false,
ctx.clientReferenceManifest,
ctx.workStore.route
)
}

// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
// which contains the subset React.
const flightReadableStream = ctx.componentMod.renderToReadableStream(
rscPayload,
RSCPayload,
ctx.clientReferenceManifest.clientModules,
{
onError,
Expand Down Expand Up @@ -1546,15 +1570,18 @@ async function renderToStream(
renderOpts.experimental.dynamicIO
) {
// This is a dynamic render. We don't do dynamic tracking because we're not prerendering
const RSCPayload = await workUnitAsyncStorage.run(
const RSCPayload: InitialRSCPayload & {
/** Only available during dynamicIO development builds. Used for logging errors. */
_validation?: Promise<React.ReactNode>
} = await workUnitAsyncStorage.run(
requestStore,
getRSCPayload,
tree,
ctx,
res.statusCode === 404
)
const [resolveValidation, validationOutlet] = createValidationOutlet()
;(RSCPayload as any)._validation = validationOutlet
RSCPayload._validation = validationOutlet

const reactServerStream = await workUnitAsyncStorage.run(
requestStore,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default async function Page() {
const random = Math.random()
return <div id="another-random">{random}</div>
}
9 changes: 9 additions & 0 deletions test/development/app-dir/dynamic-io-dev-errors/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Link from 'next/link'

export default async function Page() {
return <Link href="/error">To /error</Link>
}
5 changes: 5 additions & 0 deletions test/development/app-dir/dynamic-io-dev-errors/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Link from 'next/link'

export default async function Page() {
return <Link href="/no-error">To /no-error</Link>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { nextTestSetup } from 'e2e-utils'
import {
getRedboxDescription,
hasErrorToast,
retry,
waitForAndOpenRuntimeError,
} from 'next-test-utils'

describe('Dynamic IO Dev Errors', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should show a red box error on the SSR render', async () => {
const browser = await next.browser('/error')

await retry(async () => {
expect(await hasErrorToast(browser)).toBe(true)

await waitForAndOpenRuntimeError(browser)

expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"[ Server ] Error: Route "/error" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random"`
)
})
})

it('should show a red box error on client navigations', async () => {
const browser = await next.browser('/no-error')

expect(await hasErrorToast(browser)).toBe(false)

await browser.elementByCss("[href='/error']").click()

await retry(async () => {
expect(await hasErrorToast(browser)).toBe(true)

await waitForAndOpenRuntimeError(browser)

expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"[ Server ] Error: Route "/error" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random"`
)
})
})
})
10 changes: 10 additions & 0 deletions test/development/app-dir/dynamic-io-dev-errors/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
dynamicIO: true,
},
}

module.exports = nextConfig
Loading