first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
'use client'
import { use } from 'react'
export function Client({ io }: { io: Promise<string> }) {
const data = use(io)
return <div>Data: {data}</div>
}

View File

@@ -0,0 +1,7 @@
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,6 @@
import { setTimeout } from 'timers/promises'
import { Client } from './client'
export default async function Page() {
return <Client io={setTimeout(100, 'Hello, Dave!')} />
}

View File

@@ -0,0 +1,13 @@
'use client'
export function IndirectionOne({ children }) {
return children
}
export function IndirectionTwo({ children }) {
return children
}
export function IndirectionThree({ children }) {
return children
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,64 @@
import { Suspense } from 'react'
import { IndirectionOne, IndirectionTwo, IndirectionThree } from './indirection'
export default async function Page() {
return (
<>
<p>
This page calls fetch three times. One is cached and outside of a
Suspense boundary. The other two are uncached but inside Suspense
boundaries. We expect this page to be dynamic but not produce a build
error. If PPR is enabled we expect this page to be partially static.
uncached.
</p>
<IndirectionOne>
<FetchingComponent nonce="a" cached={true} />
</IndirectionOne>
<IndirectionTwo>
<Suspense fallback={<Fallback />}>
<FetchingComponent nonce="b" />
</Suspense>
</IndirectionTwo>
<IndirectionThree>
<Suspense fallback={<Fallback />}>
<FetchingComponent nonce="c" />
</Suspense>
</IndirectionThree>
</>
)
}
async function FetchingComponent({
nonce,
cached,
}: {
nonce: string
cached?: boolean
}) {
return (
<div data-message="">
message:{' '}
{cached ? await fetchRandomCached(nonce) : await fetchRandom(nonce)}
</div>
)
}
const fetchRandomCached = async (entropy: string) => {
const response = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy,
{ cache: 'force-cache' }
)
return response.text()
}
const fetchRandom = async (entropy: string) => {
const response = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
)
return response.text()
}
function Fallback() {
return <div data-fallback="">loading...</div>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,30 @@
import { Suspense } from 'react'
export async function generateMetadata() {
await new Promise((r) => setTimeout(r, 0))
return { title: 'Dynamic Metadata' }
}
export default async function Page() {
return (
<>
<p>
This page has dynamic content and generateMetadata is also dynamic.
generateMetadata being dynamic in this context is fine because it isn't
the only reason the page is dynamic.
</p>
<Suspense fallback={<Fallback />}>
<Dynamic />
</Suspense>
</>
)
}
function Fallback() {
return <div data-fallback="">loading...</div>
}
async function Dynamic() {
await new Promise((r) => setTimeout(r))
return <p id="dynamic">Dynamic</p>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,23 @@
export async function generateMetadata() {
await new Promise((r) => setTimeout(r, 0))
return { title: 'Dynamic Metadata' }
}
export default async function Page() {
return (
<>
<p>
This page has dynamic metadata and is also dynamic in the page without a
Suspense boundary. This is violation of cache components rules. This
test exists however because there was previously a bug that would
incorrectly report this as an invariant error.
</p>
<Dynamic />
</>
)
}
async function Dynamic() {
await new Promise((r) => setTimeout(r))
return <p id="dynamic">Dynamic</p>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,19 @@
export async function generateMetadata() {
await new Promise((r) => setTimeout(r, 0))
return { title: 'Dynamic Metadata' }
}
export default async function Page() {
return (
<>
<p>
This page is static except for metadata. Metadata can now be streamed in
so if PPR is enabled we expect that the visual content of this page will
be statically served and the metadata will be resumed dynamically. If
this project is not PPR then we expect this page to just render
dynamically.
</p>
<span id="sentinel">sentinel</span>
</>
)
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>
<Suspense>{children}</Suspense>
</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,18 @@
export async function generateMetadata() {
await new Promise((r) => setTimeout(r, 0))
return { title: 'Dynamic Metadata' }
}
export default async function Page() {
return (
<>
<p>
This page is static except for generateMetadata which does some IO. This
is a build error because metadata is not wrapped in a Suspense boundary.
We expect that if you intended for your metadata to be dynamic you will
ensure your page is dynamic too
</p>
<span id="sentinel">sentinel</span>
</>
)
}

View File

@@ -0,0 +1,9 @@
'use client'
export function IndirectionOne({ children }) {
return children
}
export function IndirectionTwo({ children }) {
return children
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,71 @@
import { Suspense } from 'react'
import { IndirectionOne, IndirectionTwo } from './indirection'
import { cookies } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page calls fetches eight times. Four are cached and Four are not.
In each set of Four, two are wrapped in Suspense. This leaves two
fetches that are uncached and not wrapped in Suspense which is
considered an error when cacheComponents is enabled. We expect the build
to fail with two component stacks that point to the offending IO
</p>
<IndirectionOne>
<FetchingComponent nonce="a" cached={true} />
<Suspense fallback="loading...">
<FetchingComponent nonce="b" cached={true} />
</Suspense>
</IndirectionOne>
<IndirectionTwo>
<FetchingComponent nonce="c" />
<Suspense fallback="loading...">
<FetchingComponent nonce="d" />
</Suspense>
</IndirectionTwo>
<FetchingComponent nonce="e" />
<Suspense fallback="loading...">
<FetchingComponent nonce="f" />
</Suspense>
</>
)
}
async function FetchingComponent({
nonce,
cached,
}: {
nonce: string
cached?: boolean
}) {
return (
<div>
message 1:{' '}
{cached ? await fetchRandomCached(nonce) : await fetchRandom(nonce)}
</div>
)
}
const fetchRandomCached = async (entropy: string) => {
const response = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy,
{ cache: 'force-cache' }
)
return response.text()
}
const fetchRandom = async (entropy: string) => {
// Hide uncached I/O behind a runtime API call, to ensure we still get the
// correct owner stack for the error.
await cookies()
const response = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
)
// The error should point at the fetch above, and not at the following fetch.
await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy + 'x'
)
return response.text()
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,33 @@
import { Suspense } from 'react'
export async function generateViewport() {
await new Promise((r) => setTimeout(r, 0))
return { themeColor: 'black' }
}
export default async function Page() {
return (
<>
<p>
This page is dynamic and also has dynamic `generateViewport`. This is a
build error because anything dynamic must be wrapped in a Suspense
boundary. While you aren't directly in control of where viewport renders
it semantically renders as part of the page preamble and so you must put
a Suspense boundary around the root layout to opt into allowing dynamic
in generateViewport.
</p>
<Suspense fallback={<Fallback />}>
<Dynamic />
</Suspense>
</>
)
}
function Fallback() {
return <div data-fallback="">loading...</div>
}
async function Dynamic() {
await new Promise((r) => setTimeout(r))
return <p id="dynamic">Dynamic</p>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,19 @@
export async function generateViewport() {
await new Promise((r) => setTimeout(r, 0))
return { themeColor: 'black' }
}
export default async function Page() {
return (
<>
<p>
This page is static except for `generateViewport`. This is a build error
because anything dynamic must be wrapped in a Suspense boundary. While
you aren't directly in control of where viewport renders it semantically
renders as part of the page preamble and so you must put a Suspense
boundary around the root layout to opt into allowing dynamic in
generateViewport.
</p>
</>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return <main>hello world</main>
}

View File

@@ -0,0 +1,13 @@
'use client'
export function SyncIO() {
// This is a sync IO access that should not cause an error
const data = new Date().toISOString()
return (
<main>
<h1>Sync IO Access</h1>
<p suppressHydrationWarning>Current date and time: {data}</p>
</main>
)
}

View File

@@ -0,0 +1,15 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
<div>
We add extra content here because it increases the size of the dev RSC
payload which exercises the preloading of the RSC chunks more. We want
a greater page weight than this simple test would otherwise have
produced.
</div>
</body>
</html>
)
}

View File

@@ -0,0 +1,42 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { SyncIO } from './client'
export default async function Page() {
return (
<main>
<section>
<p>
In this test we have two components. One reads `new Date()`
synchronously in a client component render. The other awaits cookies
in a Server Component.
</p>
<p>
In this version both components are wrapped in Suspense. We expect
that this page will prerender with fallbacks around both components
</p>
</section>
<section>
<Suspense fallback="">
<SyncIO />
</Suspense>
</section>
<section>
<Suspense fallback="">
<RequestData />
</Suspense>
</section>
</main>
)
}
async function RequestData() {
;(await cookies()).get('foo')
return (
<div>
<h2>Request Data Access</h2>
<p>This component accesses request data without a Suspense boundary.</p>
</div>
)
}

View File

@@ -0,0 +1,13 @@
'use client'
export function SyncIO() {
// This is a sync IO access that should not cause an error
const data = new Date().toISOString()
return (
<main>
<h1>Sync IO Access</h1>
<p suppressHydrationWarning>Current date and time: {data}</p>
</main>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,41 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { SyncIO } from './client'
export default async function Page() {
return (
<main>
<section>
<p>
In this test we have two components. One reads `new Date()`
synchronously in a client component render. The other awaits cookies
in a Server Component.
</p>
<p>
In this version the client component is not wrapped in a Suspense
boundary. We expect this to be a build failure with the reason
pointing to the line where `new Date()` was called.
</p>
</section>
<section>
<SyncIO />
</section>
<section>
<Suspense fallback="">
<RequestData />
</Suspense>
</section>
</main>
)
}
async function RequestData() {
;(await cookies()).get('foo')
return (
<div>
<h2>Request Data Access</h2>
<p>This component accesses request data without a Suspense boundary.</p>
</div>
)
}

View File

@@ -0,0 +1,13 @@
'use client'
export function SyncIO() {
// This is a sync IO access that should not cause an error
const data = new Date().toISOString()
return (
<main>
<h1>Sync IO Access</h1>
<p suppressHydrationWarning>Current date and time: {data}</p>
</main>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,41 @@
import { cookies } from 'next/headers'
import { SyncIO } from './client'
import { Suspense } from 'react'
export default async function Page() {
return (
<main>
<section>
<p>
In this test we have two components. One reads `new Date()`
synchronously in a client component render. The other awaits cookies
in a Server Component.
</p>
<p>
In this version the server component is not wrapped in a Suspense
boundary. We expect the build to error with the reason referencing
some dynamic data access. It should not mention `new Date()`.
</p>
</section>
<section>
<Suspense fallback={<p>Loading...</p>}>
<SyncIO />
</Suspense>
</section>
<section>
<RequestData />
</section>
</main>
)
}
async function RequestData() {
;(await cookies()).get('foo')
return (
<div>
<h2>Request Data Access</h2>
<p>This component accesses request data without a Suspense boundary.</p>
</div>
)
}

View File

@@ -0,0 +1,13 @@
'use client'
export function SyncIO() {
// This is a sync IO access that should not cause an error
const data = new Date().toISOString()
return (
<main>
<h1>Sync IO Access</h1>
<p suppressHydrationWarning>Current date and time: {data}</p>
</main>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,39 @@
import { cookies } from 'next/headers'
import { SyncIO } from './client'
export default async function Page() {
return (
<main>
<section>
<p>
In this test we have two components. One reads `new Date()`
synchronously in a client component render. The other awaits cookies
in a Server Component.
</p>
<p>
In this version neither component is wrapped in a Suspense boundary.
There are no defined semantics about which reason is more "valid" than
another but with the current implementation we report the `new Date()`
reason when failing the build.
</p>
</section>
<section>
<SyncIO />
</section>
<section>
<RequestData />
</section>
</main>
)
}
async function RequestData() {
;(await cookies()).get('foo')
return (
<div>
<h2>Request Data Access</h2>
<p>This component accesses request data without a Suspense boundary.</p>
</div>
)
}

View File

@@ -0,0 +1,23 @@
'use client'
export default function Page({ params }) {
return (
<>
<p>
This page accesses params synchronously. This does not trigger dynamic,
and the build should succeed. In dev mode, we do log an error for the
sync access though.
</p>
<ParamsReadingComponent params={params} />
</>
)
}
function ParamsReadingComponent({ params }) {
return (
<div>
this component read the `slug` param synchronously:{' '}
<span id="param">{String(params.slug)}</span>
</div>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,30 @@
'use client'
type SearchParams = { foo: string | string[] | undefined }
export default function Page(props: { searchParams: Promise<SearchParams> }) {
return (
<>
<p>
This page accesses searchParams synchronously. This does not trigger
dynamic, and the build should succeed. In dev mode, we do log an error
for the sync access though.
</p>
<SearchParamsReadingComponent searchParams={props.searchParams} />
</>
)
}
function SearchParamsReadingComponent({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const fooParam = (searchParams as any).foo
return (
<div>
this component reads the `foo` search param:{' '}
<span id="foo-param">{String(fooParam)}</span>
</div>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,31 @@
import { cookies } from 'next/headers'
import { connection } from 'next/server'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses cookies synchronously at runtime. This triggers a
type error. In dev mode, we also log an explicit error that `cookies()`
should be awaited.
</p>
<Suspense>
<CookiesReadingComponent />
</Suspense>
</>
)
}
async function CookiesReadingComponent() {
// Await a connection to test the subsequent sync cookies access at runtime.
await connection()
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const token = (cookies() as any).get('token')
return (
<div>
this component reads the `token` cookie synchronously: {token?.value}
</div>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,25 @@
import { cookies } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page accesses cookies synchronously. This triggers a type error. In
dev mode, we also log an explicit error that `cookies()` should be
awaited.
</p>
<CookiesReadingComponent />
</>
)
}
async function CookiesReadingComponent() {
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const token = (cookies() as any).get('token')
return (
<div>
this component reads the `token` cookie synchronously: {token?.value}
</div>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,39 @@
import { draftMode } from 'next/headers'
import { connection } from 'next/server'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses draftMode.isEnabled synchronously. This does not
trigger dynamic, and the build should succeed. In dev mode, we do log an
error for the sync access though.
</p>
<DraftModeReadingComponent />
<Suspense>
<Dynamic />
</Suspense>
</>
)
}
async function DraftModeReadingComponent() {
await new Promise((r) => process.nextTick(r))
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const isEnabled = (draftMode() as any).isEnabled
return (
<div>
this component read the draftMode isEnabled status synchronously:{' '}
<span id="draft-mode">{String(isEnabled)}</span>
</div>
)
}
// This component ensures that we're creating a partially prerendered page, so
// that we also test that there is no sync draftMode defined during the resume.
async function Dynamic() {
await connection()
return null
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,30 @@
import { headers } from 'next/headers'
import { connection } from 'next/server'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses headers synchronously at runtime. This triggers a
type error. In dev mode, we also log an explicit error that `headers()`
should be awaited.
</p>
<Suspense>
<HeadersReadingComponent />
</Suspense>
</>
)
}
async function HeadersReadingComponent() {
// Await a connection to test the subsequent sync headers access at runtime.
await connection()
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const userAgent = (headers() as any).get('user-agent')
return (
<div>
this component reads the `user-agent` header synchronously: {userAgent}
</div>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,24 @@
import { headers } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page accesses headers synchronously. This triggers a type error. In
dev mode, we also log an explicit error that `headers()` should be
awaited.
</p>
<HeadersReadingComponent />
</>
)
}
async function HeadersReadingComponent() {
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const userAgent = (headers() as any).get('user-agent')
return (
<div>
this component reads the `user-agent` header synchronously: {userAgent}
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses the current time `Date.now()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<DateReadingComponent />
</Suspense>
</>
)
}
async function DateReadingComponent() {
await new Promise((r) => process.nextTick(r))
return <div>{Date.now()}</div>
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses the current time `Date()` in a Server Component which
is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<DateReadingComponent />
</Suspense>
</>
)
}
async function DateReadingComponent() {
await new Promise((r) => process.nextTick(r))
return <div>{Date()}</div>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page accesses the current time `new Date()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<DateReadingComponent />
</Suspense>
</>
)
}
async function DateReadingComponent() {
await new Promise((r) => process.nextTick(r))
return <div>{new Date().toString()}</div>
}

View File

@@ -0,0 +1,34 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.generateKeyPairSync()` in a Server
Component which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto.generateKeyPairSync('rsa', keyGenOptions)
return <div>{first.publicKey}</div>
}
const keyGenOptions = {
modulusLength: 512,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
} as const

View File

@@ -0,0 +1,26 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto .generateKeySync()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto
.generateKeySync('hmac', {
length: 512,
})
.export()
return <div>{first.toString('hex')}</div>
}

View File

@@ -0,0 +1,22 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.generatePrimeSync()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = new Uint8Array(crypto.generatePrimeSync(128))
return <div>{first.toString()}</div>
}

View File

@@ -0,0 +1,23 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.getRandomValues()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = new Uint8Array(8)
crypto.getRandomValues(first)
return <div>{first.toString()}</div>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,22 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.randomBytes()` in a Server Component which
is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto.randomBytes(8)
return <div>{first.toString()}</div>
}

View File

@@ -0,0 +1,23 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.randomFillSync()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = new Uint8Array(16)
crypto.randomFillSync(first, 4, 8)
return <div>{first.toString()}</div>
}

View File

@@ -0,0 +1,22 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.randomInt(x, y)` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto.randomInt(128, 256)
return <div>{first}</div>
}

View File

@@ -0,0 +1,22 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.randomInt(x)` in a Server Component which
is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto.randomInt(128)
return <div>{first}</div>
}

View File

@@ -0,0 +1,22 @@
import crypto from 'node:crypto'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses Node's `crypto.randomInt(x)` in a Server Component which
is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const first = crypto.randomUUID()
return <div>{first}</div>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page produces a random number `Math.random()` in a Server Component
which is an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
return <div>{Math.random()}</div>
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses `crypto.getRandomValue()` in a Server Component which is
an error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
const buffer = new Uint8Array(8)
crypto.getRandomValues(buffer)
return <div>{buffer.toString()}</div>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses `crypto.randomUUID()` in a Server Component which is an
error unless preceded by something else dynamic
</p>
<Suspense fallback="loading...">
<SyncIOComponent />
</Suspense>
</>
)
}
async function SyncIOComponent() {
await new Promise((r) => process.nextTick(r))
return <div>{crypto.randomUUID()}</div>
}

View File

@@ -0,0 +1,13 @@
'use client'
export function IndirectionOne({ children }) {
return children
}
export function IndirectionTwo({ children }) {
return children
}
export function IndirectionThree({ children }) {
return children
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,76 @@
import { Suspense, use } from 'react'
import { IndirectionOne, IndirectionTwo, IndirectionThree } from './indirection'
type SearchParams = { foo: string | string[] | undefined }
export default async function Page(props: {
searchParams: Promise<SearchParams>
}) {
return (
<>
<p>
This page accesses Math.random() while prerendering but it does so late
enough in the render that all unfinished sub-trees have a defined
Suspense boundary. This is fine and doesn't need to error the build.
</p>
<Suspense fallback={<Fallback />}>
<IndirectionOne>
<RandomReadingComponent />
</IndirectionOne>
</Suspense>
<Suspense fallback={<Fallback />}>
<IndirectionTwo>
<LongRunningComponent />
</IndirectionTwo>
</Suspense>
<IndirectionThree>
<ShortRunningComponent />
</IndirectionThree>
</>
)
}
function RandomReadingComponent() {
if (typeof window === 'undefined') {
use(new Promise((r) => process.nextTick(r)))
}
const random = Math.random()
return (
<div>
<span id="rand">{random}</span>
</div>
)
}
function LongRunningComponent() {
if (typeof window === 'undefined') {
use(
new Promise((r) =>
process.nextTick(async () => {
await 1
process.nextTick(r)
})
)
)
}
return (
<div>
this component took a long time to resolve (but still before the
cacheComponents cutoff). It might not be done before the Math.random()
access happens.
</div>
)
}
function ShortRunningComponent() {
return (
<div>
This component runs quickly (in a microtask). It should be finished before
the Math.random() access happens.
</div>
)
}
function Fallback() {
return <div data-fallback="">loading...</div>
}

View File

@@ -0,0 +1,13 @@
'use client'
export function IndirectionOne({ children }) {
return children
}
export function IndirectionTwo({ children }) {
return children
}
export function IndirectionThree({ children }) {
return children
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,80 @@
import { Suspense, use } from 'react'
import { IndirectionOne, IndirectionTwo, IndirectionThree } from './indirection'
type SearchParams = { foo: string | string[] | undefined }
export default async function Page(props: {
searchParams: Promise<SearchParams>
}) {
return (
<>
<p>
This page accesses Math.random() while prerendering and does so before
something that is not wrapped in a Suspense boundary has finished
rendering
</p>
<Suspense fallback={<Fallback />}>
<IndirectionOne>
<RandomReadingComponent />
</IndirectionOne>
</Suspense>
<IndirectionTwo>
<LongRunningComponent />
</IndirectionTwo>
<IndirectionThree>
<ShortRunningComponent />
</IndirectionThree>
</>
)
}
function getRandomNumber() {
return Math.random()
}
function RandomReadingComponent() {
if (typeof window === 'undefined') {
use(new Promise((r) => process.nextTick(r)))
}
const random = getRandomNumber()
return (
<div>
<span id="rand">{random}</span>
</div>
)
}
function LongRunningComponent() {
if (typeof window === 'undefined') {
use(
new Promise((r) =>
process.nextTick(async () => {
await 1
process.nextTick(r)
})
)
)
}
return (
<div>
this component took a long time to resolve (but still before the
cacheComponents cutoff). It might not be done before the Math.random()
access happens.
</div>
)
}
function ShortRunningComponent() {
return (
<div>
This component runs quickly (in a microtask). It should be finished before
the Math.random() access happens.
</div>
)
}
function Fallback() {
return <div data-fallback="">loading...</div>
}

View File

@@ -0,0 +1,36 @@
import { connection } from 'next/server'
import { Suspense } from 'react'
export default async function Page({ params }) {
return (
<>
<p>
This page accesses params synchronously. This does not trigger dynamic,
and the build should succeed. In dev mode, we do log an error for the
sync access though.
</p>
<ParamsReadingComponent params={params} />
<Suspense>
<Dynamic />
</Suspense>
</>
)
}
async function ParamsReadingComponent({ params }) {
return (
<div>
this component read the `slug` param synchronously:{' '}
<span id="param">{String(params.slug)}</span>
</div>
)
}
// This component ensures that we're creating a partially prerendered page, so
// that we also test that there are no sync params defined during the
// resume.
async function Dynamic() {
await connection()
return null
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,45 @@
import { connection } from 'next/server'
import { Suspense } from 'react'
type SearchParams = { foo: string | string[] | undefined }
export default async function Page(props: {
searchParams: Promise<SearchParams>
}) {
return (
<>
<p>
This page accesses searchParams synchronously. This does not trigger
dynamic, and the build should succeed. In dev mode, we do log an error
for the sync access though.
</p>
<SearchParamsReadingComponent searchParams={props.searchParams} />
<Suspense>
<Dynamic />
</Suspense>
</>
)
}
async function SearchParamsReadingComponent({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
// Cast to any as we removed UnsafeUnwrapped types, but still need to test with the sync access
const fooParam = (searchParams as any).foo
return (
<div>
this component reads the `foo` search param:{' '}
<span id="foo-param">{String(fooParam)}</span>
</div>
)
}
// This component ensures that we're creating a partially prerendered page, so
// that we also test that there are no sync search params defined during the
// resume.
async function Dynamic() {
await connection()
return null
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>
<Suspense>{children}</Suspense>
</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,22 @@
export default async function Page() {
// This promise will reject before we abort during prerendering
Promise.reject('BOOM')
// This promise will reject after we abort during prerendering
setTimeout(() => {
Promise.reject('BAM')
}, 10)
return (
<>
<p>
This page tests unhandled rejection suppression after prerender abort.
</p>
<p>
With cache components enabled, this page produces a partial static shell
and it has one early rejection "BOOM" which will show up but two late
rejections which should not
</p>
</>
)
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>
<Suspense>{children}</Suspense>
</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,30 @@
import { connection } from 'next/server'
export default async function Page() {
await connection()
return (
<>
<p>
This page catches an error that's thrown in `'use cache'` at runtime.
</p>
<CatchingComponent />
</>
)
}
async function throwAnError() {
'use cache: remote'
throw new Error('Kaputt!')
}
async function CatchingComponent() {
try {
await throwAnError()
} catch (error) {
console.error(error)
}
return null
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,26 @@
import { cookies } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page accesses `cookies()` in `'use cache'`, which triggers an
error.
</p>
<CookiesReadingComponent />
</>
)
}
async function CookiesReadingComponent() {
'use cache'
// Reading cookies in a non-private cache context is not allowed. We're
// try/catching here to ensure that this error is shown even when it's caught
// in userland.
try {
await cookies()
} catch {}
return null
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,24 @@
import { draftMode } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page enables draft mode in `'use cache'`, which triggers an error.
</p>
<DraftModeEnablingComponent />
</>
)
}
async function DraftModeEnablingComponent() {
'use cache'
// Enabling draft mode in a cache context is not allowed. We're try/catching
// here to ensure that this error is shown even when it's caught in userland.
try {
;(await draftMode()).enable()
} catch {}
return null
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,25 @@
import { headers } from 'next/headers'
export default async function Page() {
return (
<>
<p>
This page accesses `headers()` in `'use cache'`, which triggers an
error.
</p>
<HeadersReadingComponent />
</>
)
}
async function HeadersReadingComponent() {
'use cache'
// Reading headers in a cache context is not allowed. We're try/catching here
// to ensure that this error is shown even when it's caught in userland.
try {
await headers()
} catch {}
return null
}

View File

@@ -0,0 +1,22 @@
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache: remote'
cacheLife({ expire: 299 }) // 1 second below the threshold of 5 minutes
// This cache can produce an entry microtaskily.
// We explicitly test it separately from "slow" caches,
// because of a dev bug where "fast" caches like this didn't register as a cache miss
// and thus sidestepped the usual logic for omitting short-lived caches.
return (
<>
<p>
This page is cached with a low expire time. Such a short-lived cache is
excluded from prerenders, and creates a dynamic hole. Without a parent
suspense boundary, this will cause an error during prerendering.
</p>
</>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,5 @@
import { Suspense } from 'react'
export default function Root({ children }: { children: React.ReactNode }) {
return <Suspense fallback={<p>Loading...</p>}>{children}</Suspense>
}

View File

@@ -0,0 +1,51 @@
import { cacheLife } from 'next/cache'
async function innerCache() {
'use cache'
cacheLife({ expire: 60 }) // 1 minute, under the 5 minute threshold
return Math.random()
}
async function outerCache() {
'use cache'
// Explicitly not setting a `cacheLife` here means this will use the implicit
// default cache life, i.e. the shortest cache life of any nested 'use cache'
// will be applied, or the values of the 'default' profile if none are nested.
return innerCache()
}
export default async function Page() {
let result: number | undefined
try {
result = await outerCache()
} catch {}
return (
<>
<p>
This page tests that a nested "use cache" with low expire time inside
another "use cache" without explicit cacheLife throws an error during
prerendering.
</p>
<p>
The inner cache function is cached with a 60 second expire time. Such a
short-lived cache would normally create a dynamic hole and be excluded
from prerenders. However, when nested inside another 'use cache' that
doesn't specify an explicit `cacheLife`, this will error during
prerendering, instead of silently creating a dynamic hole. This is to
prevent accidental misconfigurations, where a developer may forget to
set an explicit `cacheLife` on an outer 'use cache' boundary, not
knowing that a nested 'use cache' is using a short-lived cache, which
would degrade the outer 'use cache' to a dynamic hole. If there is an
outer suspense boundary, this might not be noticeable, so we error
during prerendering to make sure the developer is aware of the situation
and picks an explicit `cacheLife` for the outer 'use cache'.
</p>
<p>
This page also tests that the error cannot be caught by userland code
(the try/catch above should NOT suppress the build error).
</p>
<p>Result: {result}</p>
</>
)
}

View File

@@ -0,0 +1,20 @@
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache: remote'
cacheLife({ expire: 299 }) // 1 second below the threshold of 5 minutes
// This cache takes >1 task to fill, so it'll always show up as a cache miss in dev
await new Promise((resolve) => setTimeout(resolve, 5))
return (
<>
<p>
This page is cached with a low expire time. Such a short-lived cache is
excluded from prerenders, and creates a dynamic hole. Without a parent
suspense boundary, this will cause an error during prerendering.
</p>
</>
)
}

View File

@@ -0,0 +1,20 @@
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
'use cache'
const { slug } = await params
return (
<>
<p>
This page accesses params without defining any static params. This
excludes the page from prerenders, and creates a dynamic hole. Without a
parent suspense boundary, this will cause an error during prerendering.
</p>
<p>Slug: {slug}</p>
</>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,33 @@
import { connection } from 'next/server'
import { Suspense } from 'react'
export default async function Page() {
return (
<>
<p>
This page uses `connection()` inside `'use cache: private'`, which
triggers an error at runtime.
</p>
<Suspense fallback={<p>Loading...</p>}>
<Private />
</Suspense>
</>
)
}
async function Private() {
'use cache: private'
// TODO: this should be displayed as a Runtime Error even with this delay,
// but right now we might not read it in time and log it as as a console error instead.
await new Promise((resolve) => setTimeout(resolve))
// Calling connection() in a cache context is not allowed. We're try/catching
// here to ensure that, in dev mode, this error is shown even when it's caught
// in userland.
try {
await connection()
} catch {}
return <p id="private">Private</p>
}

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@@ -0,0 +1,27 @@
import { unstable_cache } from 'next/cache'
export default function Page() {
return (
<>
<p>
This page nests `'use cache: private'` in `unstable_cache`, which
triggers an error.
</p>
<ComponentWithCachedData />
</>
)
}
async function ComponentWithCachedData() {
const data = await getCachedData()
return <p>{data}</p>
}
const getCachedData = unstable_cache(async () => {
'use cache: private'
return fetch('https://next-data-api-endpoint.vercel.app/api/random').then(
(res) => res.json()
)
})

View File

@@ -0,0 +1,9 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}

Some files were not shown because too many files have changed in this diff Show More