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,52 @@
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
return (
<>
<p>This page calls `cookies()` in a child component.</p>
<p>
With PPR this page can be partially static because the dynamic API usage
is inside a suspense boundary.
</p>
<p>
Without PPR this page is fully dynamic because a dynamic API was used.
</p>
<Suspense fallback="loading...">
<ComponentThatReadsCookies />
</Suspense>
<Suspense fallback="loading too...">
<OtherComponent />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentThatReadsCookies() {
let sentinelCookie
try {
const cookie = (await cookies()).get('x-sentinel')
if (cookie) {
sentinelCookie = cookie.value
} else {
sentinelCookie = '~not-found~'
}
} catch (e) {
sentinelCookie = '~thrown~'
// swallow any throw. We should still not be static
}
return (
<div>
This component read cookies: "<span id="value">{sentinelCookie}</span>"
</div>
)
}
async function OtherComponent() {
return <div>This component didn't read cookies</div>
}

View File

@@ -0,0 +1,50 @@
import { Suspense } from 'react'
import { headers } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
return (
<>
<p>This page calls `headers()` in a child component.</p>
<p>
With PPR this page can be partially static because the dynamic API usage
is inside a suspense boundary.
</p>
<p>
Without PPR this page is fully dynamic because a dynamic API was used.
</p>
<Suspense fallback="loading...">
<ComponentThatReadsHeaders />
</Suspense>
<Suspense fallback="loading too...">
<OtherComponent />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentThatReadsHeaders() {
let sentinelHeader
try {
sentinelHeader = (await headers()).get('x-sentinel')
if (!sentinelHeader) {
sentinelHeader = '~not-found~'
}
} catch (e) {
sentinelHeader = '~thrown~'
// swallow any throw. We should still not be static
}
return (
<div>
This component read headers: "<span id="value">{sentinelHeader}</span>"
</div>
)
}
async function OtherComponent() {
return <div>This component didn't read headers</div>
}

View File

@@ -0,0 +1,47 @@
import { Suspense } from 'react'
import { unstable_noStore as noStore } from 'next/cache'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
// We wait for metadata to finish before rendering this component which will trigger
// a synchronous abort
await new Promise((r) => process.nextTick(r))
return (
<>
<p>
This page calls `unstable_noStore()` in a child component with a parent
Suspense boundary.
</p>
<p>
With PPR this page can be partially static because the dynamic API usage
is inside a suspense boundary.
</p>
<p>
Without PPR this page is fully dynamic because a dynamic API was used.
</p>
<Suspense fallback="loading...">
<ComponentThatCallsNoStore />
</Suspense>
<Suspense fallback="loading too...">
<OtherComponent />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentThatCallsNoStore() {
try {
noStore()
} catch (e) {
// swallow any throw. We should still not be static
}
return <div>This component called unstable_noStore()</div>
}
async function OtherComponent() {
return <div>This component didn't call unstable_noStore()</div>
}

View File

@@ -0,0 +1,64 @@
'use client'
import { Suspense, use } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
export default function Page({ searchParams }: { searchParams: Promise<any> }) {
return (
<>
<p>
This page reads `searchParams.foo` in a client component context with a
parent Suspense boundary.
</p>
<p>
With PPR this page can be partially static because the dynamic API usage
is inside a suspense boundary.
</p>
<p>
Without PPR this page is fully dynamic because a dynamic API was used.
</p>
<Suspense fallback={<div id="fallback-component-one-">loading...</div>}>
<ComponentOne searchParams={searchParams} />
</Suspense>
<Suspense fallback={<Fallback>loading too...</Fallback>}>
<ComponentTwo />
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
function ComponentOne({ searchParams }: { searchParams: Promise<any> }) {
let sentinelSearch
const sp = use(searchParams)
try {
if (sp.sentinel) {
sentinelSearch = sp.sentinel
} else {
sentinelSearch = '~not-found~'
}
} catch (e) {
sentinelSearch = '~thrown~'
// swallow any throw. We should still not be static
}
return (
<div>
This component accessed `searchParams.sentinel`: "
<span id="value">{sentinelSearch}</span>"
</div>
)
}
function ComponentTwo() {
return (
<>
<div>This component didn't access any searchParams properties</div>
<div id="inner">{getSentinelValue()}</div>
</>
)
}
function Fallback({ children }) {
return children
}

View File

@@ -0,0 +1,62 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page({
searchParams,
}: {
searchParams: Promise<any>
}) {
return (
<>
<p>
This page reads `searchParams.foo` in a client component context with a
parent Suspense boundary.
</p>
<p>
With PPR this page can be partially static because the dynamic API usage
is inside a suspense boundary.
</p>
<p>
Without PPR this page is fully dynamic because a dynamic API was used.
</p>
<Suspense fallback="loading...">
<ComponentOne searchParams={searchParams} />
</Suspense>
<Suspense fallback="loading too...">
<ComponentTwo />
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne({ searchParams }: { searchParams: Promise<any> }) {
let sentinelSearch
const sp = await searchParams
try {
if (sp.sentinel) {
sentinelSearch = sp.sentinel
} else {
sentinelSearch = '~not-found~'
}
} catch (e) {
sentinelSearch = '~thrown~'
// swallow any throw. We should still not be static
}
return (
<div>
This component accessed `searchParams.sentinel`: "
<span id="value">{sentinelSearch}</span>"
</div>
)
}
function ComponentTwo() {
return (
<>
<div>This component didn't access any searchParams properties</div>
<div id="inner">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,40 @@
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders two components each performing one or more cached
fetches. There is no uncached IO in this example
</p>
<p>Niether component is wrapped in a Suspense boundary.</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<ComponentOne />
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await fetchRandomCached('a')}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await fetchRandomCached('b')}</div>
<div>message 2: {await fetchRandomCached('c')}</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()
}

View File

@@ -0,0 +1,58 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page fetches using cache inside one component and without cache
inside another component.
</p>
<p>Each component is wrapped in a Suspense boundary.</p>
<p>
With PPR this page should have a static shell that includes the cached
fetch result and a loading state for the boundary surrounding the
uncached fetch result.
</p>
<p>Without PPR this page should be dynamic.</p>
<Suspense fallback="loading...">
<ComponentOne />
</Suspense>
<Suspense fallback="loading too...">
<ComponentTwo />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await fetchRandomCached('a')}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await fetchRandom('b')}</div>
<div>message 2: {await fetchRandomCached('c')}</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()
}

View File

@@ -0,0 +1,44 @@
'use cache'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page({
searchParams: _unused,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>
}) {
return (
<>
<p>
This page renders two components. Both call a simulated IO function. The
whole page is wrapped in "use cache".
</p>
<p>Niether component is wrapped in a Suspense boundary</p>
<p>
With PPR this page should be entirely static because all IO is cached.
</p>
<p>Without PPR this page should be static because all IO is cached.</p>
<ComponentOne />
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await getMessage('hello cached fast', 2)}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await getMessage('hello cached fast', 0)}</div>
<div>message 3: {await getMessage('hello cached slow', 20)}</div>
</>
)
}
async function getMessage(echo, delay) {
await new Promise((r) => setTimeout(r, delay))
return echo
}

View File

@@ -0,0 +1,43 @@
import { unstable_cache as cache } from 'next/cache'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders two components. Both call a simulated IO function
wrapped in `unstable_cache`.
</p>
<p>Niether component is wrapped in a Suspense boundary</p>
<p>
With PPR this page should be entirely static because all IO is cached.
</p>
<p>Without PPR this page should be static because all IO is cached.</p>
<ComponentOne />
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await getCachedMessage('hello cached fast', 2)}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await getCachedMessage('hello cached fast', 0)}</div>
<div>message 3: {await getCachedMessage('hello cached slow', 20)}</div>
</>
)
}
async function getMessage(echo, delay) {
await new Promise((r) => setTimeout(r, delay))
return echo
}
const getCachedMessage = cache(getMessage)

View File

@@ -0,0 +1,52 @@
import { Suspense } from 'react'
import { unstable_cache as cache } from 'next/cache'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders two components. The first calls a simulated IO
function wrapped in `unstable_cache`. The second calls the simulated IO
function without being wrapped in `unstable_cache`.
</p>
<p>Each component is wrapped in a Suspense boundary</p>
<p>
With PPR this page should have a static shell that includes the cached
IO result and a loading state for the boundary surrounding the uncached
IO result.
</p>
<p>Without PPR this page should be dynamic.</p>
<Suspense fallback="loading...">
<ComponentOne />
</Suspense>
<Suspense fallback="loading too...">
<ComponentTwo />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await getCachedMessage('hello cached fast', 2)}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await getMessage('hello uncached fast', 0)}</div>
<div>message 3: {await getCachedMessage('hello cached slow', 20)}</div>
</>
)
}
async function getMessage(echo, delay) {
await new Promise((r) => setTimeout(r, delay))
return echo
}
const getCachedMessage = cache(getMessage)

View File

@@ -0,0 +1,15 @@
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page only has microtask async points and no Suspense boundaries.
</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,43 @@
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders a deep tree of deeply async components that still
resolve within microtasks.
</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<Foo>
<Bar>1</Bar>
<Bar>2</Bar>
<Bar>
<Foo>
<Bar>a</Bar>
<Bar>b</Bar>
<Bar>c</Bar>
</Foo>
</Bar>
<Bar>3</Bar>
<Bar>4</Bar>
</Foo>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function Foo({ children }) {
await 1
await 1
return <ul>Foo: {children}</ul>
}
async function Bar({ children }) {
let length = 7
for (let i = 0; i < length; i++) {
await 1
}
return <li>Bar: {children}</li>
}

View File

@@ -0,0 +1,14 @@
import { getSentinelValue } from '../../getSentinelValue'
export default function NotFound() {
return (
<>
<p>
This 404 page is made up of entirely static content in a sync function.
</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,7 @@
import { notFound } from 'next/navigation'
export default async function Page() {
notFound()
return <p>This will never render</p>
}

View File

@@ -0,0 +1,16 @@
import { cookies } from 'next/headers'
import { getSentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
const sentinel = (await cookies()).get('sentinel')
return (
<>
<p>
cookie slot (sentinel):{' '}
<span id="value">{sentinel ? sentinel.value : 'no cookie'}</span>
</p>
<div id="page-slot">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,5 @@
import { notFound } from 'next/navigation'
export default function Default() {
notFound()
}

View File

@@ -0,0 +1,11 @@
import { getSentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>microtask slot</p>
<div id="page-slot">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,15 @@
import { unstable_noStore as noStore } from 'next/cache'
import { getSentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
// We wait for metadata to finish before calling noStore
await new Promise((r) => process.nextTick(r))
noStore()
return (
<>
<p>noStore slot</p>
<div id="page-slot">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static slot</p>
<div id="page-slot">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,11 @@
import { getSentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
await new Promise((r) => setTimeout(r, 0))
return (
<>
<p>task slot</p>
<div id="page-slot">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static children</p>
<div id="page-children">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,16 @@
import { Suspense } from 'react'
export default async function Layout({ children, slot }) {
return (
<>
<section>
<h1>slot</h1>
<Suspense fallback="loading slot...">{slot}</Suspense>
</section>
<section>
<h1>children</h1>
<Suspense fallback="loading children...">{children}</Suspense>
</section>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static children</p>
<div id="page-children">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static children</p>
<div id="page-children">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static children</p>
<div id="page-children">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,10 @@
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
return (
<>
<p>static children</p>
<div id="page-children">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,12 @@
'use client'
export function Time() {
return (
<p>
The time is{' '}
<span id="time" suppressHydrationWarning>
{new Date().toISOString()}
</span>
</p>
)
}

View File

@@ -0,0 +1,20 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
import { Time } from './client'
export default function Page() {
return (
<>
<p>
This page is static in RSC but dynamic in the client. It should serve a
static fallback that updates in the browser.
</p>
<Suspense fallback={<span>Loading...</span>}>
<Time />
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,14 @@
import { getSentinelValue } from '../../getSentinelValue'
export default function Page() {
return (
<>
<p>
This page is made up of entirely static content in an sync function.
</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,14 @@
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
return (
<>
<p>
This page is made up of entirely static content in an async function.
</p>
<p>With PPR this page should be entirely static.</p>
<p>Without PPR this page should be static.</p>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,34 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders a component that requires async IO. It is simulated
using setTimeout(f, 1000).
</p>
<p>
This component simulated IO component is rendered inside a Suspense
boundary
</p>
<p>
With PPR this page should be partially static, showing the fallback of
the Suspense boundary surrounding our simulated IO.
</p>
<p>Without PPR this page should be dynamic.</p>
<Suspense fallback="loading...">
<ComponentWithIO />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentWithIO() {
await new Promise<void>((r) => setTimeout(r, 1000))
return <p>hello IO</p>
}

View File

@@ -0,0 +1,40 @@
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders two components. Both call a simulated IO function
wrapped in `"use cache"`.
</p>
<p>Niether component is wrapped in a Suspense boundary</p>
<p>
With PPR this page should be entirely static because all IO is cached.
</p>
<p>Without PPR this page should be static because all IO is cached.</p>
<ComponentOne />
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await getCachedMessage('hello cached fast', 2)}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await getCachedMessage('hello cached fast', 0)}</div>
<div>message 3: {await getCachedMessage('hello cached slow', 20)}</div>
</>
)
}
async function getCachedMessage(echo, delay) {
'use cache'
await new Promise((r) => setTimeout(r, delay))
return echo
}

View File

@@ -0,0 +1,54 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../getSentinelValue'
export default async function Page() {
await 1
return (
<>
<p>
This page renders two components. The first calls a simulated IO
function wrapped in `"use cache""`. The second calls the simulated IO
function without being wrapped in `"use cache""`.
</p>
<p>Each component is wrapped in a Suspense boundary</p>
<p>
With PPR this page should have a static shell that includes the cached
IO result and a loading state for the boundary surrounding the uncached
IO result.
</p>
<p>Without PPR this page should be dynamic.</p>
<Suspense fallback="loading...">
<ComponentOne />
</Suspense>
<Suspense fallback="loading too...">
<ComponentTwo />
<div id="inner">{getSentinelValue()}</div>
</Suspense>
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function ComponentOne() {
return <div>message 1: {await getCachedMessage('hello cached fast', 2)}</div>
}
async function ComponentTwo() {
return (
<>
<div>message 2: {await getMessage('hello uncached fast', 0)}</div>
<div>message 3: {await getCachedMessage('hello cached slow', 20)}</div>
</>
)
}
async function getMessage(echo, delay) {
await new Promise((r) => setTimeout(r, delay))
return echo
}
async function getCachedMessage(echo, delay) {
'use cache'
return getMessage(echo, delay)
}

View File

@@ -0,0 +1,23 @@
export function createWaiter() {
let cached = null
function wait() {
if (typeof window === 'undefined') {
if (cached) {
return cached
}
cached = new Promise((r) => process.nextTick(r))
return cached
} else {
if (cached) {
return cached
}
cached = Promise.resolve()
return cached
}
}
function cleanup() {
cached = null
}
return { wait, cleanup }
}

View File

@@ -0,0 +1,34 @@
import { Suspense } from 'react'
import { connection } from 'next/server'
import { getSentinelValue } from '../../../getSentinelValue'
/**
* This test case is constructed to demonstrate how using the async form of cookies can lead to a better
* prerender with cache components when PPR is on. There is no difference when PPR is off. When PPR is on the second component
* can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest
* to Cookies access is read
*/
export default async function Page() {
return (
<>
<Suspense fallback="loading...">
<Component />
</Suspense>
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function Component() {
await connection()
return (
<div>
cookie <span id="foo">foo</span>
</div>
)
}
function ComponentTwo() {
return <p>footer</p>
}

View File

@@ -0,0 +1,46 @@
import { Suspense } from 'react'
import { connection } from 'next/server'
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const pendingConnection = connection()
return (
<section>
<h1>Deep Connection Reader</h1>
<p>
This component was passed the connection promise returned by
`connection()`. It is rendered inside a Suspense boundary.
</p>
<p>
If cacheComponents is turned off the `connection()` call would trigger a
dynamic point at the callsite and the suspense boundary would also be
blocked for over one second
</p>
<Suspense
fallback={
<>
<p>loading connection...</p>
<div id="fallback">{getSentinelValue()}</div>
</>
}
>
<DeepConnectionReader pendingConnection={pendingConnection} />
</Suspense>
</section>
)
}
async function DeepConnectionReader({
pendingConnection,
}: {
pendingConnection: ReturnType<typeof connection>
}) {
await pendingConnection
return (
<>
<p>The connection was awaited</p>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,270 @@
import type { cookies } from 'next/headers'
type ReadonlyRequestCookies = Awaited<ReturnType<typeof cookies>>
export function AllComponents({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
return (
<>
<ForOf cookies={cookies} expression={expression} />
<Spread cookies={cookies} expression={expression} />
<Size cookies={cookies} expression={expression} />
<GetAndGetAll cookies={cookies} expression={expression} />
<Has cookies={cookies} expression={expression} />
<Set cookies={cookies} expression={expression} />
<Delete cookies={cookies} expression={expression} />
<Clear cookies={cookies} expression={expression} />
<ToString cookies={cookies} expression={expression} />
</>
)
}
function ForOf({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
let output = []
for (let [cookieName, cookie] of cookies) {
if (cookieName.startsWith('x-sentinel')) {
output.push(
<div key={cookieName} id={'for-of-' + cookieName}>
<pre>{print(cookie)}</pre>
</div>
)
}
}
return (
<section>
<h2>for...of cookies()</h2>
{output.length ? output : <div>no cookies found</div>}
</section>
)
}
function Spread({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
let output = [...cookies]
.filter(([cookieName]) => cookieName.startsWith('x-sentinel'))
.map((v) => {
return (
<div key={v[0]} id={'spread-' + v[0]}>
<pre>{print(v[1])}</pre>
</div>
)
})
return (
<section>
<h2>...cookies()</h2>
{output.length ? output : <div>no cookies found</div>}
</section>
)
}
function Size({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
return (
<section>
<h2>cookies().size</h2>
<div id={'size-cookies'}>{cookies.size}</div>
</section>
)
}
function GetAndGetAll({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
return (
<section>
<h2>cookies().get('...')</h2>
<div id={'get-x-sentinel'}>
<pre>{print(cookies.get('x-sentinel'))}</pre>
</div>
<h2>{"cookies().get({ name: '...' })"}</h2>
<div id={'get-x-sentinel-path'}>
<pre>
{print(cookies.get({ name: 'x-sentinel-path', value: undefined }))}
</pre>
</div>
<h2>cookies().getAll('...')</h2>
<div id={'get-x-sentinel-rand'}>
<pre>{print(cookies.getAll('x-sentinel-rand'))}</pre>
</div>
</section>
)
}
function Has({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
return (
<section>
<h2>cookies().has('...')</h2>
<ul>
<li>
<label>x-sentinel</label>
<span id={'has-x-sentinel'}>: {'' + cookies.has('x-sentinel')}</span>
</li>
<li>
<label>x-sentinel-foobar</label>
<span id={'has-x-sentinel-foobar'}>
: {'' + cookies.has('x-sentinel-foobar')}
</span>
</li>
</ul>
</section>
)
}
function Set({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
let result = 'no error'
try {
cookies.set('x-sentinel', 'goodbye')
} catch (e) {
result = e.message
}
return (
<section>
<h2>cookies().set('...')</h2>
<ul>
<li>
<label>cookies().set('x-sentinel', 'goodbye')</label>
<span id={'set-result-x-sentinel'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'set-value-x-sentinel'}>
: {cookies.get('x-sentinel').value}
</span>
</li>
</ul>
</section>
)
}
function Delete({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
let result = 'no error'
try {
cookies.delete('x-sentinel')
} catch (e) {
result = e.message
}
return (
<section>
<h2>cookies().delete('...')</h2>
<ul>
<li>
<label>cookies().delete('x-sentinel')</label>
<span id={'delete-result-x-sentinel'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'delete-value-x-sentinel'}>
: {cookies.get('x-sentinel').value}
</span>
</li>
</ul>
</section>
)
}
function Clear({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
let result = 'no error'
try {
;(cookies as any).clear()
} catch (e) {
result = e.message
}
return (
<section>
<h2>cookies().clear()</h2>
<ul>
<li>
<label>cookies().clear()</label>
<span id={'clear-result'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'clear-value-x-sentinel'}>
: {cookies.get('x-sentinel').value}
</span>
</li>
</ul>
</section>
)
}
function ToString({
cookies,
expression,
}: {
cookies: ReadonlyRequestCookies
expression: string
}) {
// filter out real cookies, no point in leaking and not stable for testing
let result = cookies
.toString()
.split('; ')
.filter((p) => p.startsWith('x-sentinel'))
.join('; ')
return (
<section>
<h2>cookies().toString()</h2>
<ul>
<li>
<label>cookies().toString()</label>
<pre id="toString">{result}</pre>
</li>
</ul>
</section>
)
}
function print(model: any) {
return JSON.stringify(model, null, 2)
}

View File

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

View File

@@ -0,0 +1,20 @@
import { cookies } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
import { AllComponents } from './components'
export default async function Page() {
const allCookies = await cookies()
return (
<>
<p>
This page will exercise a number of APIs on the cookies() instance by
first awaiting it. This is the correct way to consume cookies() and this
test partially exists to ensure the behavior between sync and async
access is consistent for the time where you are permitted to do either
</p>
<AllComponents cookies={allCookies} expression="(await cookies())" />
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,38 @@
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
/**
* This test case is constructed to demonstrate how using the async form of cookies can lead to a better
* prerender with cache components when PPR is on. There is no difference when PPR is off. When PPR is on the second component
* can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest
* to Cookies access is read
*/
export default async function Page() {
return (
<Suspense fallback="loading...">
<Suspense fallback="inner loading...">
<Component />
</Suspense>
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</Suspense>
)
}
async function Component() {
const cookie = (await cookies()).get('x-sentinel')
if (cookie && cookie.value) {
return (
<div>
cookie <span id="x-sentinel">{cookie.value}</span>
</div>
)
} else {
return <div>no cookie found</div>
}
}
function ComponentTwo() {
return <p>footer</p>
}

View File

@@ -0,0 +1,66 @@
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const pendingCookies = cookies()
return (
<section>
<h1>Deep Cookie Reader</h1>
<p>
This component was passed the cookies promise returned by `cookies()`.
It is rendered inside a Suspense boundary and it takes a second to
resolve so when rendering the page you should see the Suspense fallback
content before revealing the cookie value even though cookies was called
at the page root.
</p>
<p>
If cacheComponents is turned off the `cookies()` call would trigger a
dynamic point at the callsite and the suspense boundary would also be
blocked for over one second
</p>
<Suspense
fallback={
<>
<p>loading cookie data...</p>
<div id="fallback">{getSentinelValue()}</div>
</>
}
>
<DeepCookieReader pendingCookies={pendingCookies} />
</Suspense>
</section>
)
}
async function DeepCookieReader({
pendingCookies,
}: {
pendingCookies: ReturnType<typeof cookies>
}) {
let output: Array<React.ReactNode> = []
for (const [name, cookie] of await pendingCookies) {
if (name.startsWith('x-sentinel')) {
output.push(
<tr>
<td>{name}</td>
<td>{cookie.value}</td>
</tr>
)
}
}
await new Promise((r) => setTimeout(r, 1000))
return (
<>
<table>
<tr>
<th>Cookie name</th>
<th>Cookie Value</th>
</tr>
{output}
</table>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,19 @@
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const date = await getDate()
return (
<div>
<label>Date()</label>
<span id="value">{date}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getDate() {
'use cache'
return Date()
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default async function Layout({
children,
}: {
children: React.ReactNode
}) {
return (
<Suspense fallback={<div data-fallback="">loading...</div>}>
{children}
</Suspense>
)
}

View File

@@ -0,0 +1,22 @@
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const newDate = await getNewDate()
return (
<div>
<label>new Date()</label>
<span id="value">{newDate}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getNewDate() {
'use cache'
// We should really return the date object but
// at the time of writing "use cache" can't serialize the Date
// back to the server
return new Date().toString()
}

View File

@@ -0,0 +1,19 @@
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const now = await getNow()
return (
<div>
<label>now</label>
<span id="value">{now}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getNow() {
'use cache'
return Date.now()
}

View File

@@ -0,0 +1,22 @@
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const staticDate = await getStaticDate()
return (
<div>
<label>new Date(0)</label>
<span id="value">{staticDate}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getStaticDate() {
'use cache'
// We should really return the date object but
// at the time of writing "use cache" can't serialize the Date
// back to the server
return new Date(0).toString()
}

View File

@@ -0,0 +1,17 @@
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
if (typeof window === 'undefined') {
await new Promise((r) => process.nextTick(r))
}
const staticDate = new Date(0)
return (
<div>
<label>new Date(0)</label>
<span id="value">{staticDate.toString()}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}

View File

@@ -0,0 +1,17 @@
'use client'
import { useRouter } from 'next/navigation'
export default function ToggleButton() {
const router = useRouter()
return (
<button
onClick={() =>
fetch('/draftmode/async/toggle').then(() => router.refresh())
}
>
Toggle Draft Mode
</button>
)
}

View File

@@ -0,0 +1,29 @@
import { draftMode } from 'next/headers'
import { getSentinelValue } from '../getSentinelValue'
import ToggleButton from './ToggleButton'
export default async function Page() {
return (
<>
<p>
This page uses `(await draftMode()).isEnabled`. This is now a promise
however reading it during prerender is fine because it is always false
during prerender so we don't expect it to trigger any dynamic behavior
</p>
<Component />
<ToggleButton />
<div id="page">{getSentinelValue()}</div>
</>
)
}
async function Component() {
return (
<div>
draftMode enabled?{' '}
<span id="draft-mode">{'' + (await draftMode()).isEnabled}</span>
</div>
)
}

View File

@@ -0,0 +1,12 @@
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
const isEnabled = (await draftMode()).isEnabled
if (isEnabled) {
;(await draftMode()).disable()
return new Response('Draft mode is disabled')
} else {
;(await draftMode()).enable()
return new Response('Draft mode is enabled')
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,11 @@
const { PHASE_PRODUCTION_BUILD } = require('next/constants')
export function getSentinelValue() {
return process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD
? 'at buildtime'
: 'at runtime'
}
export function SentinelValue() {
return getSentinelValue()
}

View File

@@ -0,0 +1,341 @@
export function AllComponents<T extends Headers>({
headers,
xSentinelValues,
expression,
}: {
headers: T
xSentinelValues: Set<string>
expression: string
}) {
return (
<>
<Append headers={headers} expression={expression} />
<Delete headers={headers} expression={expression} />
<Get headers={headers} expression={expression} />
<Has headers={headers} expression={expression} />
<SetExercise headers={headers} expression={expression} />
<GetSetCookie headers={headers} expression={expression} />
<ForEach headers={headers} expression={expression} />
<Keys headers={headers} expression={expression} />
<Values
headers={headers}
expression={expression}
xSentinelValues={xSentinelValues}
/>
<Entries headers={headers} expression={expression} />
<ForOf headers={headers} expression={expression} />
<Spread headers={headers} expression={expression} />
</>
)
}
function Append({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let result: string
try {
headers.append('x-sentinel', ' world')
result = 'no error'
} catch (e) {
result = e.message
}
return (
<section>
<h2>{expression}.append('...')</h2>
<ul>
<li>
<label>{expression}.append('x-sentinel', ' world')</label>
<span id={'append-result-x-sentinel'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'append-value-x-sentinel'}>
: {headers.get('x-sentinel')}
</span>
</li>
</ul>
</section>
)
}
function Delete({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let result = 'no error'
try {
headers.delete('x-sentinel')
} catch (e) {
result = e.message
}
return (
<section>
<h2>{expression}.delete('...')</h2>
<ul>
<li>
<label>{expression}.delete('x-sentinel')</label>
<span id={'delete-result-x-sentinel'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'delete-value-x-sentinel'}>
: {headers.get('x-sentinel')}
</span>
</li>
</ul>
</section>
)
}
function Get({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
return (
<section>
<h2>{expression}.get('...')</h2>
<div>
<pre id={'get-x-sentinel'}>{headers.get('x-sentinel')}</pre>
</div>
</section>
)
}
function Has({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
return (
<section>
<h2>{expression}.has('...')</h2>
<ul>
<li>
<label>x-sentinel</label>
<span id={'has-x-sentinel'}>: {'' + headers.has('x-sentinel')}</span>
</li>
<li>
<label>x-sentinel-foobar</label>
<span id={'has-x-sentinel-foobar'}>
: {'' + headers.has('x-sentinel-foobar')}
</span>
</li>
</ul>
</section>
)
}
function SetExercise({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let result = 'no error'
try {
headers.set('x-sentinel', 'goodbye')
} catch (e) {
result = e.message
}
return (
<section>
<h2>{expression}.set('...')</h2>
<ul>
<li>
<label>{expression}.set('x-sentinel', 'goodbye')</label>
<span id={'set-result-x-sentinel'}>: {result}</span>
</li>
<li>
<label>x-sentinel value</label>
<span id={'set-value-x-sentinel'}>: {headers.get('x-sentinel')}</span>
</li>
</ul>
</section>
)
}
function GetSetCookie({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
const result = headers.getSetCookie()
return (
<section>
<h2>{expression}.getSetCookie()</h2>
<pre id="get-set-cookie">{JSON.stringify(result, null, 2)}</pre>
</section>
)
}
function ForEach({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let output = []
headers.forEach((value, header) => {
if (header.startsWith('x-sentinel')) {
output.push(
<div key={header}>
<pre id={'for-each-' + header}>{value}</pre>
</div>
)
}
})
return (
<section>
<h2>{expression}.forEach(...)</h2>
{output.length ? output : <div>no headers found</div>}
</section>
)
}
function Keys({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let output = []
for (let header of headers.keys()) {
if (header.startsWith('x-sentinel')) {
output.push(
<li key={header} id={'keys-' + header}>
{header}
</li>
)
}
}
return (
<section>
<h2>{expression}.keys(...)</h2>
{output.length ? <ul>{output}</ul> : <div>no headers found</div>}
</section>
)
}
function Values({
headers,
expression,
xSentinelValues,
}: {
headers: Headers
expression: string
xSentinelValues: Set<string>
}) {
let output = []
for (let value of headers.values()) {
if (xSentinelValues.has(value)) {
output.push(
<li key={value} data-class={'values'}>
{value}
</li>
)
}
}
return (
<section>
<h2>{expression}.values()</h2>
{output.length ? <ul>{output}</ul> : <div>no headers found</div>}
</section>
)
}
function Entries({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let output = []
for (let entry of headers.entries()) {
if (entry[0].startsWith('x-sentinel')) {
output.push(
<li key={entry[0]} id={'entries-' + entry[0]}>
{entry[1]}
</li>
)
}
}
return (
<section>
<h2>{expression}.entries()</h2>
{output.length ? <ul>{output}</ul> : <div>no headers found</div>}
</section>
)
}
function ForOf({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let output = []
for (let [headerName, value] of headers) {
if (headerName.startsWith('x-sentinel')) {
output.push(
<div key={headerName}>
<pre id={'for-of-' + headerName}>{value}</pre>
</div>
)
}
}
return (
<section>
<h2>for...of {expression}</h2>
{output.length ? output : <div>no headers found</div>}
</section>
)
}
function Spread({
headers,
expression,
}: {
headers: Headers
expression: string
}) {
let output = [...headers]
.filter(([headerName]) => headerName.startsWith('x-sentinel'))
.map((v) => {
return (
<div key={v[0]}>
<pre id={'spread-' + v[0]}>{v[1]}</pre>
</div>
)
})
return (
<section>
<h2>...{expression}</h2>
{output.length ? output : <div>no headers found</div>}
</section>
)
}

View File

@@ -0,0 +1,5 @@
import { Suspense } from 'react'
export default function Layout({ children }) {
return <Suspense fallback="loading...">{children}</Suspense>
}

View File

@@ -0,0 +1,32 @@
import { headers } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
import { AllComponents } from './components'
export default async function Page() {
const allHeaders = await headers()
const xSentinelValues = new Set<string>()
for (let [headerName, headerValue] of allHeaders.entries()) {
if (headerName.startsWith('x-sentinel')) {
xSentinelValues.add(headerValue)
}
}
return (
<>
<p>
This page will exercise a number of APIs on the headers() instance by
first awaiting it. This is the correct way to consume headers() and this
test partially exists to ensure the behavior between sync and async
access is consistent for the time where you are permitted to do either
</p>
<AllComponents
headers={allHeaders}
xSentinelValues={xSentinelValues}
expression={'(await headers())'}
/>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,39 @@
import { Suspense } from 'react'
import { headers } from 'next/headers'
import { getSentinelValue } from '../../getSentinelValue'
/**
* This test case is constructed to demonstrate how using the async form of cookies can lead to a better
* prerender with cache components when PPR is on. There is no difference when PPR is off. When PPR is on the second component
* can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest
* to Cookies access is read
*/
export default async function Page() {
return (
<Suspense fallback="loading...">
<Suspense fallback="inner loading...">
<Component />
</Suspense>
<ComponentTwo />
<div id="page">{getSentinelValue()}</div>
</Suspense>
)
}
async function Component() {
const hasHeader = (await headers()).has('x-sentinel')
if (hasHeader) {
return (
<div>
header{' '}
<span id="x-sentinel">{(await headers()).get('x-sentinel')}</span>
</div>
)
} else {
return <div>no header found</div>
}
}
function ComponentTwo() {
return <p>footer</p>
}

View File

@@ -0,0 +1,66 @@
import { Suspense } from 'react'
import { headers } from 'next/headers'
import { getSentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const pendingHeaders = headers()
return (
<section>
<h1>Deep Header Reader</h1>
<p>
This component was passed the headers promise returned by `headers()`.
It is rendered inside a Suspense boundary and it takes a second to
resolve so when rendering the page you should see the Suspense fallback
content before revealing the header value even though headers was called
at the page root.
</p>
<p>
If cacheComponents is turned off the `headers()` call would trigger a
dynamic point at the callsite and the suspense boundary would also be
blocked for over one second
</p>
<Suspense
fallback={
<>
<p>loading header data...</p>
<div id="fallback">{getSentinelValue()}</div>
</>
}
>
<DeepHeaderReader pendingHeaders={pendingHeaders} />
</Suspense>
</section>
)
}
async function DeepHeaderReader({
pendingHeaders,
}: {
pendingHeaders: ReturnType<typeof headers>
}) {
let output: Array<React.ReactNode> = []
for (const [name, value] of await pendingHeaders) {
if (name.startsWith('x-sentinel')) {
output.push(
<tr>
<td>{name}</td>
<td>{value}</td>
</tr>
)
}
}
await new Promise((r) => setTimeout(r, 1000))
return (
<>
<table>
<tr>
<th>Header Name</th>
<th>Header Value</th>
</tr>
{output}
</table>
<div id="page">{getSentinelValue()}</div>
</>
)
}

View File

@@ -0,0 +1,12 @@
import { getSentinelValue } from './getSentinelValue'
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<main>{children}</main>
<div id="layout">{getSentinelValue()}</div>
</body>
</html>
)
}

View File

@@ -0,0 +1,40 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getGeneratedKeyPair(1)
const second = await getGeneratedKeyPair(2)
return (
<div>
<dl>
<dt>
[first] require('node:crypto').generateKeyPairSync(type, options)
</dt>
<dd id="first">{first.publicKey}</dd>
<dt>
[second] require('node:crypto').generateKeyPairSync(type, options)
</dt>
<dd id="second">{second.publicKey}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getGeneratedKeyPair(_nonce: number) {
'use cache'
return crypto.generateKeyPairSync('rsa', {
modulusLength: 512,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
})
}

View File

@@ -0,0 +1,30 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getGeneratedKey(1)
const second = await getGeneratedKey(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').generateKeySync(type, options)</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').generateKeySync(type, options)</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getGeneratedKey(_nonce: number) {
'use cache'
return crypto
.generateKeySync('hmac', {
length: 512,
})
.export()
}

View File

@@ -0,0 +1,26 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getGeneratedPrime(1)
const second = await getGeneratedPrime(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').generatePrimeSync(size)</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').generatePrimeSync(size)</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getGeneratedPrime(_nonce: number) {
'use cache'
return new Uint8Array(crypto.generatePrimeSync(128))
}

View File

@@ -0,0 +1,28 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getRandomValues(1)
const second = await getRandomValues(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').getRandomValues()</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').getRandomValues()</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandomValues(_nonce: number) {
'use cache'
const randomBytes = new Uint8Array(8)
crypto.getRandomValues(randomBytes)
return randomBytes
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default async function Layout({
children,
}: {
children: React.ReactNode
}) {
return (
<Suspense fallback={<div data-fallback="">loading...</div>}>
{children}
</Suspense>
)
}

View File

@@ -0,0 +1,30 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getRandomBytes(1)
const second = await getRandomBytes(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').randomBytes(size)</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').randomBytes(size)</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandomBytes(nonce: number) {
'use cache'
if (nonce === 2) {
return crypto.randomBytes(8, undefined) as unknown as Buffer
} else {
return crypto.randomBytes(8)
}
}

View File

@@ -0,0 +1,28 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getFilledSync(1)
const second = await getFilledSync(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').randomFillSync(buffer)</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').randomBytes(buffer)</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getFilledSync(_nonce: number) {
'use cache'
const randomBytes = new Uint8Array(16)
crypto.randomFillSync(randomBytes, 4, 8)
return randomBytes
}

View File

@@ -0,0 +1,32 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
const first = await getRandomIntBetween(1)
const second = await getRandomIntBetween(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').randomInt(min, max)</dt>
<dd id="first">{first}</dd>
<dt>[second] require('node:crypto').randomInt(min, max)</dt>
<dd id="second">{second}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandomIntBetween(nonce: number) {
'use cache'
if (nonce === 2) {
// We want to exercise the case where the function arguments are length 3
// but the third arg is still not a callback so it runs sync
return crypto.randomInt(0, 2 ** 48 - 1, undefined) as undefined as number
} else {
return crypto.randomInt(0, 2 ** 48 - 1)
}
}

View File

@@ -0,0 +1,26 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../../getSentinelValue'
export default async function Page() {
const first = await getRandomIntUpTo(1)
const second = await getRandomIntUpTo(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').randomInt(max)</dt>
<dd id="first">{first}</dd>
<dt>[second] require('node:crypto').randomInt(max)</dt>
<dd id="second">{second}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandomIntUpTo(_nonce: number) {
'use cache'
return crypto.randomInt(2 ** 48 - 1)
}

View File

@@ -0,0 +1,26 @@
import crypto from 'node:crypto'
import { SentinelValue } from '../../../getSentinelValue'
export default async function Page() {
const first = await getRandomUUID(1)
const second = await getRandomUUID(2)
return (
<div>
<dl>
<dt>[first] require('node:crypto').randomUUID()</dt>
<dd id="first">{first.toString()}</dd>
<dt>[second] require('node:crypto').randomUUID()</dt>
<dd id="second">{second.toString()}</dd>
</dl>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandomUUID(_nonce: number) {
'use cache'
return crypto.randomUUID()
}

View File

@@ -0,0 +1,37 @@
import { Suspense } from 'react'
export async function generateStaticParams() {
const set = new Set()
set.add(await fetchRandom('a'))
set.add(await fetchRandom('a'))
return Array.from(set).map((value) => {
return {
slug: ('' + value).slice(2),
}
})
}
export default async function Layout({ children, params }) {
return (
<Suspense fallback="loading">
<Inner params={params}>{children}</Inner>
</Suspense>
)
}
async function Inner({ children, params }) {
return (
<>
<h1>{(await params).slug}</h1>
<section>{children}</section>
</>
)
}
const fetchRandom = async (entropy: string) => {
const response = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
)
return response.text()
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return 'Hello World'
}

View File

@@ -0,0 +1,30 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
return (
<section>
<p>
This Layout accesses params in a client component inside a high
cardinality and low cardinality dynamic params
</p>
<div>
page lowcard: <span id="param-lowcard">{use(params).lowcard}</span>
</div>
<div>
page highcard: <span id="param-highcard">{use(params).highcard}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,27 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
return (
<section>
<p>
This Layout accesses params in a server component inside a high
cardinality and low cardinality dynamic params
</p>
<div>
page lowcard: <span id="param-lowcard">{(await params).lowcard}</span>
</div>
<div>
page highcard:{' '}
<span id="param-highcard">{(await params).highcard}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,39 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
return (
<section>
<p>
This Layout does key checking of the params prop in a client component
</p>
<div>
page lowcard:{' '}
<span id="param-has-lowcard">
{'' + Reflect.has(use(params), 'lowcard')}
</span>
</div>
<div>
page highcard:{' '}
<span id="param-has-highcard">
{'' + Reflect.has(use(params), 'highcard')}
</span>
</div>
<div>
page foo:{' '}
<span id="param-has-foo">{'' + Reflect.has(use(params), 'foo')}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,35 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
return (
<section>
<p>
This Layout does key checking of the params prop in a server component
</p>
<div>
page lowcard:{' '}
<span id="param-has-lowcard">
{'' + Reflect.has(await params, 'lowcard')}
</span>
</div>
<div>
page highcard:{' '}
<span id="param-has-highcard">
{'' + Reflect.has(await params, 'highcard')}
</span>
</div>
<div>
page foo:{' '}
<span id="param-has-foo">{'' + Reflect.has(await params, 'foo')}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,34 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
const copied = { ...use(params) }
return (
<section>
<p>
This Layout spreads params in a client component after `use`ing them
</p>
<div>
page lowcard: <span id="param-copied-lowcard">{copied.lowcard}</span>
</div>
<div>
page highcard: <span id="param-copied-highcard">{copied.highcard}</span>
</div>
<div>
param key count:{' '}
<span id="param-key-count">{Object.keys(copied).length}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,30 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
children,
}: {
params: Promise<{ lowcard: string; highcard: string }>
children: React.ReactNode
}) {
const copied = { ...(await params) }
return (
<section>
<p>
This Layout spreads params in a server component after awaiting them
</p>
<div>
page lowcard: <span id="param-copied-lowcard">{copied.lowcard}</span>
</div>
<div>
page highcard: <span id="param-copied-highcard">{copied.highcard}</span>
</div>
<div>
param key count:{' '}
<span id="param-key-count">{Object.keys(copied).length}</span>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,28 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../../../getSentinelValue'
export async function generateStaticParams() {
return [
{
highcard: 'build',
},
]
}
export default function HighardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<Suspense
fallback={<div id="highcard-fallback">loading highcard children</div>}
>
{children}
</Suspense>
<span id="highcard">{getSentinelValue()}</span>
</>
)
}

View File

@@ -0,0 +1,27 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
return (
<section>
<p>
This Page access params in a page component inside a high cardinality
and low cardinality dynamic params
</p>
<div>
page lowcard: <span id="param-lowcard">{use(params).lowcard}</span>
</div>
<div>
page highcard: <span id="param-highcard">{use(params).highcard}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,24 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
return (
<section>
<p>
This Page access params in a page component inside a high cardinality
and low cardinality dynamic params
</p>
<div>
page lowcard: <span id="param-lowcard">{(await params).lowcard}</span>
</div>
<div>
page highcard:{' '}
<span id="param-highcard">{(await params).highcard}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,36 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
return (
<section>
<p>
This Page does key checking of the params prop in a client component
</p>
<div>
page lowcard:{' '}
<span id="param-has-lowcard">
{'' + Reflect.has(use(params), 'lowcard')}
</span>
</div>
<div>
page highcard:{' '}
<span id="param-has-highcard">
{'' + Reflect.has(use(params), 'highcard')}
</span>
</div>
<div>
page foo:{' '}
<span id="param-has-foo">{'' + Reflect.has(use(params), 'foo')}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,32 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
return (
<section>
<p>
This Page does key checking of the params prop in a server component
</p>
<div>
page lowcard:{' '}
<span id="param-has-lowcard">
{'' + Reflect.has(await params, 'lowcard')}
</span>
</div>
<div>
page highcard:{' '}
<span id="param-has-highcard">
{'' + Reflect.has(await params, 'highcard')}
</span>
</div>
<div>
page foo:{' '}
<span id="param-has-foo">{'' + Reflect.has(await params, 'foo')}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,29 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
const copied = { ...use(params) }
return (
<section>
<p>This Page spreads params in a client component after `use`ing them</p>
<div>
page lowcard: <span id="param-copied-lowcard">{copied.lowcard}</span>
</div>
<div>
page highcard: <span id="param-copied-highcard">{copied.highcard}</span>
</div>
<div>
param key count:{' '}
<span id="param-key-count">{Object.keys(copied).length}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,25 @@
import { getSentinelValue } from '../../../../../../getSentinelValue'
export default async function Page({
params,
}: {
params: Promise<{ lowcard: string; highcard: string }>
}) {
const copied = { ...(await params) }
return (
<section>
<p>This Page spreads params in a server component after `use`ing them</p>
<div>
page lowcard: <span id="param-copied-lowcard">{copied.lowcard}</span>
</div>
<div>
page highcard: <span id="param-copied-highcard">{copied.highcard}</span>
</div>
<div>
param key count:{' '}
<span id="param-key-count">{Object.keys(copied).length}</span>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}

View File

@@ -0,0 +1,28 @@
import { Suspense } from 'react'
import { getSentinelValue } from '../../../getSentinelValue'
export async function generateStaticParams() {
return [
{
lowcard: 'one',
},
]
}
export default function LowCardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<Suspense
fallback={<div id="lowcard-fallback">loading lowcard children</div>}
>
{children}
</Suspense>
<span id="lowcard">{getSentinelValue()}</span>
</>
)
}

View File

@@ -0,0 +1,54 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../../../getSentinelValue'
export default function Layout({
params,
children,
}: {
params: Promise<{ dyn: string; then: string; value: string; status: string }>
children: React.ReactNode
}) {
const copied = { ...use(params) }
return (
<section>
<p>
This Layout accesses params that have name collisions with Promise
properties. When synchronous access is available we assert that you can
access non colliding param names directly and all params if you await
</p>
<ul>
<li>
dyn: <span id="param-dyn">{getValueAsString(use(params).dyn)}</span>
</li>
<li>
then:{' '}
<span id="param-then">{getValueAsString(use(params).then)}</span>
</li>
<li>
value:{' '}
<span id="param-value">{getValueAsString(use(params).value)}</span>
</li>
<li>
status:{' '}
<span id="param-status">{getValueAsString(use(params).status)}</span>
</li>
</ul>
<div>
copied: <pre>{JSON.stringify(copied)}</pre>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}
function getValueAsString(value: any) {
if (typeof value === 'string') {
return value
}
return String(value)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,53 @@
import { getSentinelValue } from '../../../../../../../../getSentinelValue'
export default async function Page({
params,
children,
}: {
params: Promise<{ dyn: string; then: string; value: string; status: string }>
children: React.ReactNode
}) {
const copied = { ...(await params) }
return (
<section>
<p>
This Layout accesses params that have name collisions with Promise
properties. When synchronous access is available we assert that you can
access non colliding param names directly and all params if you await
</p>
<ul>
<li>
dyn:{' '}
<span id="param-dyn">{getValueAsString((await params).dyn)}</span>
</li>
<li>
then:{' '}
<span id="param-then">{getValueAsString((await params).then)}</span>
</li>
<li>
value:{' '}
<span id="param-value">{getValueAsString((await params).value)}</span>
</li>
<li>
status:{' '}
<span id="param-status">
{getValueAsString((await params).status)}
</span>
</li>
</ul>
<div>
copied: <pre>{JSON.stringify(copied)}</pre>
</div>
<span id="page">{getSentinelValue()}</span>
{children}
</section>
)
}
function getValueAsString(value: any) {
if (typeof value === 'string') {
return value
}
return String(value)
}

View File

@@ -0,0 +1,3 @@
export default async function Page() {
return 'page'
}

View File

@@ -0,0 +1,51 @@
'use client'
import { use } from 'react'
import { getSentinelValue } from '../../../../../../../../getSentinelValue'
export default function Page({
params,
}: {
params: Promise<{ dyn: string; then: string; value: string; status: string }>
}) {
const copied = { ...use(params) }
return (
<section>
<p>
This Page accesses params that have name collisions with Promise
properties. When synchronous access is available we assert that you can
access non colliding param names directly and all params if you await
</p>
<ul>
<li>
dyn: <span id="param-dyn">{getValueAsString(use(params).dyn)}</span>
</li>
<li>
then:{' '}
<span id="param-then">{getValueAsString(use(params).then)}</span>
</li>
<li>
value:{' '}
<span id="param-value">{getValueAsString(use(params).value)}</span>
</li>
<li>
status:{' '}
<span id="param-status">{getValueAsString(use(params).status)}</span>
</li>
</ul>
<div>
copied: <pre>{JSON.stringify(copied)}</pre>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}
function getValueAsString(value: any) {
if (typeof value === 'string') {
return value
}
return String(value)
}

View File

@@ -0,0 +1,50 @@
import { getSentinelValue } from '../../../../../../../../getSentinelValue'
export default async function Page({
params,
}: {
params: Promise<{ dyn: string; then: string; value: string; status: string }>
}) {
const copied = { ...(await params) }
return (
<section>
<p>
This Page accesses params that have name collisions with Promise
properties. When synchronous access is available we assert that you can
access non colliding param names directly and all params if you await
</p>
<ul>
<li>
dyn:{' '}
<span id="param-dyn">{getValueAsString((await params).dyn)}</span>
</li>
<li>
then:{' '}
<span id="param-then">{getValueAsString((await params).then)}</span>
</li>
<li>
value:{' '}
<span id="param-value">{getValueAsString((await params).value)}</span>
</li>
<li>
status:{' '}
<span id="param-status">
{getValueAsString((await params).status)}
</span>
</li>
</ul>
<div>
copied: <pre>{JSON.stringify(copied)}</pre>
</div>
<span id="page">{getSentinelValue()}</span>
</section>
)
}
function getValueAsString(value: any) {
if (typeof value === 'string') {
return value
}
return String(value)
}

View File

@@ -0,0 +1,5 @@
import { Suspense } from 'react'
export default function Layout({ children }) {
return <Suspense fallback="loading">{children}</Suspense>
}

View File

@@ -0,0 +1,19 @@
import { SentinelValue } from '../../getSentinelValue'
export default async function Page() {
const random = await getRandom()
return (
<div>
<label>random</label>
<span id="value">{random}</span>
<span id="page">
<SentinelValue />
</span>
</div>
)
}
async function getRandom() {
'use cache'
return Math.random()
}

View File

@@ -0,0 +1,13 @@
import { Suspense } from 'react'
export default async function Layout({
children,
}: {
children: React.ReactNode
}) {
return (
<Suspense fallback={<div data-fallback="">loading...</div>}>
{children}
</Suspense>
)
}

View File

@@ -0,0 +1,25 @@
import type { NextRequest } from 'next/server'
import { getSentinelValue } from '../../getSentinelValue'
export async function generateStaticParams() {
return [
{
dyn: '1',
},
]
}
export async function GET(
request: NextRequest,
props: { params: Promise<{ dyn: string }> }
) {
const { dyn } = await props.params
return new Response(
JSON.stringify({
value: getSentinelValue(),
type: 'dynamic params',
param: dyn,
})
)
}

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