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,38 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page uses a short-lived private cache (with cacheLife("seconds")),
which should not be included in a static prefetch, but should be
included in a runtime prefetch, because it has a long enough stale time
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
<ShortLivedCache />
</Suspense>
</main>
)
}
async function ShortLivedCache() {
'use cache: private'
cacheLife('seconds')
await cachedDelay([__filename])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
Short-lived cached content
<div id="cached-value">{Date.now()}</div>
</div>
)
}

View File

@@ -0,0 +1,41 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page uses a short-lived private cache (staleTime &lt;
RUNTIME_PREFETCH_DYNAMIC_STALE, which is 30s), which should not be
included in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
<CachedButShortLived />
</Suspense>
</main>
)
}
async function CachedButShortLived() {
'use cache: private'
cacheLife({
stale: 5,
// the rest of the settings don't matter for private caches,
// because they are not persisted server-side
})
await cachedDelay([__filename])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
Short-lived cached content
<div id="cached-value">{Date.now()}</div>
</div>
)
}

View File

@@ -0,0 +1,38 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page uses a short-lived public cache (with cacheLife("seconds")),
which should not be included in a static prefetch, but should be
included in a runtime prefetch, because it has a long enough stale time
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
<ShortLivedCache />
</Suspense>
</main>
)
}
async function ShortLivedCache() {
'use cache'
cacheLife('seconds')
await cachedDelay([__filename])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
Short-lived cached content
<div id="cached-value">{Date.now()}</div>
</div>
)
}

View File

@@ -0,0 +1,42 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page uses a short-lived public cache (expire &lt; DYNAMIC_EXPIRE,
5min), which should not be included in a static prefetch, but should be
included in a runtime prefetch, because it has a long enough stale time
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
<ShortLivedCache />
</Suspense>
</main>
)
}
async function ShortLivedCache() {
'use cache'
cacheLife({
stale: 60, // > RUNTIME_PREFETCH_DYNAMIC_STALE
revalidate: 2 * 60,
expire: 3 * 60, // < DYNAMIC_EXPIRE
})
await cachedDelay([__filename])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
Short-lived cached content
<div id="cached-value">{Date.now()}</div>
</div>
)
}

View File

@@ -0,0 +1,42 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page uses a short-lived public cache (expire &lt; DYNAMIC_EXPIRE,
5min), which should not be included in a static prefetch, and should
also not be included in a runtime prefetch, because it has a short
enough stale time (&lt; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
<ShortLivedCache />
</Suspense>
</main>
)
}
async function ShortLivedCache() {
'use cache'
cacheLife({
stale: 20, // < RUNTIME_PREFETCH_DYNAMIC_STALE
revalidate: 2 * 60,
expire: 3 * 60, // < DYNAMIC_EXPIRE
})
await cachedDelay([__filename])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
Short-lived cached content
<div id="cached-value">{Date.now()}</div>
</div>
)
}

View File

@@ -0,0 +1,33 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { ErrorBoundary } from '../../../../components/error-boundary'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page errors after a cookies call, so we should only see the error
in a runtime prefetch or a navigation (and not during prerendering /
prefetching)
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<ErrorBoundary>
<One />
</ErrorBoundary>
</Suspense>
</main>
)
}
async function One(): Promise<never> {
const cookieStore = await cookies()
await cachedDelay(['/cookies', cookieStore.get('user-agent')?.value])
throw new Error('Kaboom')
}

View File

@@ -0,0 +1,38 @@
import { DebugLinkAccordion } from '../../../components/link-accordion'
export default async function Page() {
return (
<main>
<h1>Errors</h1>
<h2>thrown errors</h2>
<ul>
<li>
<DebugLinkAccordion href="/errors/error-after-cookies" />
</li>
</ul>
<h2>sync IO</h2>
<ul>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/cookies" />
</li>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/headers" />
</li>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/dynamic-params/123" />
</li>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/search-params?foo=bar" />
</li>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/private-cache" />
</li>
<li>
<DebugLinkAccordion href="/errors/sync-io-after-runtime-api/quickly-expiring-public-cache" />
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,28 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { DebugRenderKind } from '../../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after a cookies() call, so we should only see
the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await cookies()
return <div id="timestamp">Timestamp: {Date.now()}</div>
}

View File

@@ -0,0 +1,29 @@
import { Suspense } from 'react'
import { DebugRenderKind } from '../../../../../shared'
type Params = { id: string }
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page({ params }: { params: Promise<Params> }) {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after awaiting params, so we should only see
the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable params={params} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({ params }: { params: Promise<Params> }) {
await params
return <div id="timestamp">Timestamp: {Date.now()}</div>
}

View File

@@ -0,0 +1,28 @@
import { headers } from 'next/headers'
import { Suspense } from 'react'
import { DebugRenderKind } from '../../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after a headers() call, so we should only see
the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await headers()
return <div id="timestamp">Timestamp: {Date.now()}</div>
}

View File

@@ -0,0 +1,32 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after awaiting a private cache, so we should
only see the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await privateCache()
return <div id="timestamp">Timestamp: {Date.now()}</div>
}
async function privateCache() {
'use cache: private'
await cachedDelay([__dirname])
}

View File

@@ -0,0 +1,34 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../../shared'
import { cacheLife } from 'next/cache'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after awaiting a quickly-expiring public
cache, so we should only see the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await publicCache()
return <div id="timestamp">Timestamp: {Date.now()}</div>
}
async function publicCache() {
'use cache'
cacheLife('seconds')
await cachedDelay([__dirname])
}

View File

@@ -0,0 +1,37 @@
import { Suspense } from 'react'
import { DebugRenderKind } from '../../../../shared'
type AnySearchParams = { [key: string]: string | string[] | undefined }
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
return (
<main>
<DebugRenderKind />
<p id="intro">
This page performs sync IO after awaiting searchParams, so we should
only see the error in a runtime prefetch
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable searchParams={searchParams} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
await searchParams
return <div id="timestamp">Timestamp: {Date.now()}</div>
}

View File

@@ -0,0 +1,23 @@
// This is technically unnecessary, because this page is static
// and a runtime prefetch won't do any better than a static one,
// but it's useful to exercise this codepath.
// In the future, this test can be used to check whether we correctly
// *skip* a runtime prefetch if a page was prerendered as static.
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<h1>Fully static</h1>
<p id="intro">Hello from a fully static page!</p>
<p>
{new Array({ length: 1000 })
.fill(null)
.map(() => 'Lorem ipsum dolor sit amet.')}
</p>
</main>
)
}

View File

@@ -0,0 +1,34 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses cookies and no uncached IO, So it should be completely
prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const cookieStore = await cookies()
const cookieValue = cookieStore.get('testCookie')?.value ?? null
await cachedDelay([__filename, cookieValue])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
</div>
)
}

View File

@@ -0,0 +1,61 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses cookies and some uncached IO, so parts of it should be
runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
<form
action={async (formData: FormData) => {
'use server'
const cookieStore = await cookies()
const cookieValue = formData.get('cookie') as string | null
if (cookieValue) {
cookieStore.set('testCookie', cookieValue)
}
}}
>
<input type="text" name="cookie" />
<button type="submit">Update cookie</button>
</form>
</main>
)
}
async function RuntimePrefetchable() {
const cookieStore = await cookies()
const cookieValue = cookieStore.get('testCookie')?.value ?? null
await cachedDelay([__filename, cookieValue])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,48 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { id: 'test' } }],
}
type Params = { id: string }
export default async function Page({ params }: { params: Promise<Params> }) {
return (
<main>
<DebugRenderKind />
<p>
This page uses params and some uncached IO, so parts of it should be
runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable params={params} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({ params }: { params: Promise<Params> }) {
const { id } = await params
await cachedDelay([__filename, id])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="param-value">{`Param: ${id}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,48 @@
import { headers } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ headers: [['host', 'test-host']] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses headers and some uncached IO, so parts of it should be
runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const headersStore = await headers()
const headerValue = headersStore.get('host') === null ? 'missing' : 'present'
await cachedDelay([__filename, headerValue])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="header-value">{`Header: ${headerValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,56 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ searchParams: { key: 'value' } }],
}
type AnySearchParams = { [key: string]: string | string[] | undefined }
export default async function Page({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
return (
<main>
<DebugRenderKind />
<p>
This page uses search params and some uncached IO, so parts of it should
be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable searchParams={searchParams} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
const { searchParam } = await searchParams
await cachedDelay([__filename, searchParam])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="search-param-value">{`Search param: ${searchParam}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,40 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses cookies (from a private cache) and no uncached IO, So it
should be completely prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const cookieValue = await privateCache()
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
</div>
)
}
async function privateCache() {
'use cache: private'
const cookieStore = await cookies()
const cookieValue = cookieStore.get('testCookie')?.value ?? null
await cachedDelay([__filename, cookieValue])
return cookieValue
}

View File

@@ -0,0 +1,67 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses cookies (from inside a private cache) and some uncached
IO, so parts of it should be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
<form
action={async (formData: FormData) => {
'use server'
const cookieStore = await cookies()
const cookieValue = formData.get('cookie') as string | null
if (cookieValue) {
cookieStore.set('testCookie', cookieValue)
}
}}
>
<input type="text" name="cookie" />
<button type="submit">Update cookie</button>
</form>
</main>
)
}
async function RuntimePrefetchable() {
const cookieValue = await privateCache()
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache() {
'use cache: private'
const cookieStore = await cookies()
const cookieValue = cookieStore.get('testCookie')?.value ?? null
await cachedDelay([__filename, cookieValue])
return cookieValue
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,52 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses Date.now (in a private cache) and some uncached IO, so
parts of it should be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const now = await privateCache()
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="timestamp">{`Timestamp: ${now}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache() {
'use cache: private'
const now = Date.now()
await cachedDelay([__filename])
return now
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,54 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { id: 'test' } }],
}
type Params = { id: string }
export default async function Page({ params }: { params: Promise<Params> }) {
return (
<main>
<DebugRenderKind />
<p>
This page uses params (passed to a private cache) and some uncached IO,
so parts of it should be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable params={params} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({ params }: { params: Promise<Params> }) {
const id = await privateCache(params)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="param-value">{`Param: ${id}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache(params: Promise<Params>) {
'use cache: private'
const { id } = await params
await cachedDelay([__filename, id])
return id
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,54 @@
import { headers } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ headers: [['host', 'test-host']] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses headers (from inside a private cache) and some uncached
IO, so parts of it should be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const headerValue = await privateCache()
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="header-value">{`Header: ${headerValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache() {
'use cache: private'
const headersStore = await headers()
const headerValue = headersStore.get('host') === null ? 'missing' : 'present'
await cachedDelay([__filename, headerValue])
return headerValue
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,62 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ searchParams: { key: 'value' } }],
}
type AnySearchParams = { [key: string]: string | string[] | undefined }
export default async function Page({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
return (
<main>
<DebugRenderKind />
<p>
This page uses search params (passed to a private cache) and some
uncached IO, so parts of it should be runtime-prefetchable.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable searchParams={searchParams} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
const searchParam = await privateCache(searchParams)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="search-param-value">{`Search param: ${searchParam}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache(searchParams: Promise<AnySearchParams>) {
'use cache: private'
const { searchParam } = await searchParams
await cachedDelay([__filename, searchParam])
return searchParam
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,23 @@
import Link from 'next/link'
import { ReactNode } from 'react'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html>
<body style={{ fontFamily: 'monospace' }}>
<Header />
{children}
</body>
</html>
)
}
function Header() {
return (
<header>
<Link href="/" prefetch={false}>
Home
</Link>
</header>
)
}

View File

@@ -0,0 +1,224 @@
import { DebugLinkAccordion } from '../../components/link-accordion'
export default async function Page() {
return (
<main>
<h1>Home</h1>
<h2>directly in a page</h2>
<ul>
<li>
cookies + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-page/cookies" />
</li>
</ul>
</li>
<li>
headers + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-page/headers" />
</li>
</ul>
</li>
<li>
search params + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-page/search-params?searchParam=123" />
</li>
<li>
<DebugLinkAccordion href="/in-page/search-params?searchParam=456" />
</li>
</ul>
</li>
<li>
dynamic params + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-page/dynamic-params/123" />
</li>
<li>
<DebugLinkAccordion href="/in-page/dynamic-params/456" />
</li>
</ul>
</li>
<li>
only cookies
<ul>
<li>
<DebugLinkAccordion href="/in-page/cookies-only" />
</li>
</ul>
</li>
</ul>
<h2>
<code>use cache: private</code>
</h2>
<ul>
<li>
cookies in private cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/cookies" />
</li>
</ul>
</li>
<li>
headers in private cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/headers" />
</li>
</ul>
</li>
<li>
dynamic params in private cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/dynamic-params/123" />
</li>
<li>
<DebugLinkAccordion href="/in-private-cache/dynamic-params/456" />
</li>
</ul>
</li>
<li>
search params in private cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/search-params?searchParam=123" />
</li>
<li>
<DebugLinkAccordion href="/in-private-cache/search-params?searchParam=456" />
</li>
</ul>
</li>
<li>
only cookies in private cache
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/cookies-only" />
</li>
</ul>
</li>
<li>
Date.now() in private cache
<ul>
<li>
<DebugLinkAccordion href="/in-private-cache/date-now" />
</li>
</ul>
</li>
</ul>
<h2>
<code>runtime promise passed to public cache</code>
</h2>
<ul>
<li>
cookies() promise passed to public cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/cookies" />
</li>
</ul>
</li>
<li>
headers() promise passed to public cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/headers" />
</li>
</ul>
</li>
<li>
dynamic params promise passed to public cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/dynamic-params/123" />
</li>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/dynamic-params/456" />
</li>
</ul>
</li>
<li>
search params promise passed to public cache + dynamic content
<ul>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/search-params?searchParam=123" />
</li>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/search-params?searchParam=456" />
</li>
</ul>
</li>
<li>
only cookies passed to public cache (no dynamic content)
<ul>
<li>
<DebugLinkAccordion href="/passed-to-public-cache/cookies-only" />
</li>
</ul>
</li>
</ul>
<h2>short-lived caches</h2>
<ul>
<li>
private, short stale
<ul>
<li>
<DebugLinkAccordion href="/caches/private-short-stale" />
</li>
</ul>
</li>
<li>
public, short expire, long enough stale
<ul>
<li>
<DebugLinkAccordion href="/caches/public-short-expire-long-stale" />
</li>
</ul>
</li>
<li>
public, short expire, short stale
<ul>
<li>
<DebugLinkAccordion href="/caches/public-short-expire-short-stale" />
</li>
</ul>
</li>
<li>
public, cacheLife("seconds")
<ul>
<li>
<DebugLinkAccordion href="/caches/public-seconds" />
</li>
</ul>
</li>
<li>
private, cacheLife("seconds")
<ul>
<li>
<DebugLinkAccordion href="/caches/private-seconds" />
</li>
</ul>
</li>
</ul>
<h2>misc</h2>
<ul>
<li>
<DebugLinkAccordion href="/fully-static" />
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,47 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses cookies (from a private cache) and no uncached IO, So it
should be completely prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await cookies() // Guard from being statically prerendered, which would make the cache hang
// We've already awaited cookies, but we still want to make sure
// that the cache doesn't consider them a hanging promise
const cookieValue = await publicCache(
cookies().then(
(cookieStore) => cookieStore.get('testCookie')?.value ?? null
)
)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
</div>
)
}
async function publicCache(cookiePromise: Promise<string | null>) {
'use cache'
const cookieValue = await cookiePromise
await cachedDelay([__filename, cookieValue])
return cookieValue
}

View File

@@ -0,0 +1,73 @@
import { cookies } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page passes cookies to a public cache, and uses some uncached IO,
so parts of it should be prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
<form
action={async (formData: FormData) => {
'use server'
const cookieStore = await cookies()
const cookieValue = formData.get('cookie') as string | null
if (cookieValue) {
cookieStore.set('testCookie', cookieValue)
}
}}
>
<input type="text" name="cookie" />
<button type="submit">Update cookie</button>
</form>
</main>
)
}
async function RuntimePrefetchable() {
await cookies() // Guard from being statically prerendered, which would make the cache hang
// We've already awaited cookies, but we still want to make sure
// that the cache doesn't consider them a hanging promise
const cookieValue = await publicCache(
cookies().then(
(cookieStore) => cookieStore.get('testCookie')?.value ?? null
)
)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="cookie-value">{`Cookie: ${cookieValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function publicCache(cookiePromise: Promise<string | null>) {
'use cache'
const cookieValue = await cookiePromise
await cachedDelay([__filename, cookieValue])
return cookieValue
}
async function Dynamic() {
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,57 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../../shared'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { id: 'test' } }],
}
type Params = { id: string }
export default async function Page({ params }: { params: Promise<Params> }) {
return (
<main>
<DebugRenderKind />
<p>
This page passes dynamic params to a public cache, and uses some
uncached IO, so parts of it should be prefetchable with a runtime
prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable params={params} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({ params }: { params: Promise<Params> }) {
await cookies() // Guard from being statically prerendered, which would make the cache hang
const id = await publicCache(params)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="param-value">{`Param: ${id}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function publicCache(params: Promise<Params>) {
'use cache'
const { id } = await params
await cachedDelay([__filename, id])
return id
}
async function Dynamic() {
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,60 @@
import { headers } from 'next/headers'
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { connection } from 'next/server'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ headers: [['host', 'test-host']] }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page passes headers to a public cache, and uses some uncached IO,
so parts of it should be prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await headers() // Guard from being statically prerendered, which would make the cache hang
// We've already awaited headers, but we still want to make sure
// that the cache doesn't consider them a hanging promise
const headerValue = await publicCache(
headers().then((headersStore) =>
headersStore.get('host') === null ? 'missing' : 'present'
)
)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="header-value">{`Header: ${headerValue}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function publicCache(headerPromise: Promise<string>) {
'use cache'
const headerValue = await headerPromise
await cachedDelay([__filename, headerValue])
return headerValue
}
async function Dynamic() {
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,64 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind } from '../../../shared'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ searchParams: { key: 'value' } }],
}
type AnySearchParams = { [key: string]: string | string[] | undefined }
export default async function Page({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
return (
<main>
<DebugRenderKind />
<p>
This page passes search params to a public cache, and uses some uncached
IO, so parts of it should be prefetchable with a runtime prefetch.
</p>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 1...</div>}>
<RuntimePrefetchable searchParams={searchParams} />
</Suspense>
</main>
)
}
async function RuntimePrefetchable({
searchParams,
}: {
searchParams: Promise<AnySearchParams>
}) {
await cookies() // Guard from being statically prerendered, which would make the cache hang
const searchParam = await publicCache(searchParams)
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="search-param-value">{`Search param: ${searchParam}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function publicCache(searchParams: Promise<AnySearchParams>) {
'use cache'
const { searchParam } = await searchParams
await cachedDelay([__filename, searchParam])
return searchParam
}
async function Dynamic() {
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,33 @@
import { cacheLife } from 'next/cache'
import { setTimeout } from 'timers/promises'
export async function uncachedIO() {
await setTimeout(0)
}
export async function cachedDelay(key: any) {
'use cache'
cacheLife('minutes')
await setTimeout(1)
}
export function DebugRenderKind() {
const { workUnitAsyncStorage } =
require('next/dist/server/app-render/work-unit-async-storage.external') as typeof import('next/dist/server/app-render/work-unit-async-storage.external')
const workUnitStore = workUnitAsyncStorage.getStore()!
return (
<div>
workUnitStore.type: {workUnitStore.type}
{(() => {
switch (workUnitStore.type) {
case 'prerender':
return '(static prefetch)'
case 'prerender-runtime':
return '(runtime prefetch)'
default:
return null
}
})()}
</div>
)
}

View File

@@ -0,0 +1,51 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../../shared'
import { connection } from 'next/server'
import { lang } from 'next/root-params'
import { cookies } from 'next/headers'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { lang: 'en' } }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses root params and some uncached IO. Root params should
always be available in static prerenders, so a runtime prefetch should
have them too.
</p>
<Suspense fallback="Loading 1...">
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
await cookies()
const currentLang = await lang()
await cachedDelay([__filename, currentLang])
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="root-param-value">{`Lang: ${currentLang}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,55 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../../shared'
import { connection } from 'next/server'
import { lang } from 'next/root-params'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { lang: 'en' } }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses root params (inside a private cache) and some uncached
IO. Private caches are only rendered during runtime prefetches and
navigation requests, so they won't be part of a static prefetch, but
they should be part of a runtime prefetch.
</p>
<Suspense fallback="Loading 1...">
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const currentLang = await privateCache()
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="root-param-value">{`Lang: ${currentLang}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function privateCache() {
'use cache: private'
const currentLang = await lang()
await cachedDelay([__filename, currentLang])
return currentLang
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,34 @@
import Link from 'next/link'
import { lang } from 'next/root-params'
import { ReactNode } from 'react'
export default async function RootLayout({
children,
}: {
children: ReactNode
}) {
const currentLang = await lang()
return (
<html lang={currentLang}>
<body style={{ fontFamily: 'monospace' }}>
<Header />
{children}
</body>
</html>
)
}
async function Header() {
const currentLang = await lang()
return (
<header>
<Link href={`/with-root-param/${currentLang}`} prefetch={false}>
Home (for lang: {currentLang})
</Link>
</header>
)
}
export function generateStaticParams() {
return [{ lang: 'en' }]
}

View File

@@ -0,0 +1,73 @@
import { lang } from 'next/root-params'
import { DebugLinkAccordion } from '../../../components/link-accordion'
export default async function Page() {
const currentLang = await lang()
const otherLang = currentLang === 'en' ? 'de' : 'en'
return (
<main>
<h1>Home - with root param ({currentLang})</h1>
<h2>directly in a page</h2>
<ul>
<li>
root params + dynamic content
<ul>
<li>
<DebugLinkAccordion
href={`/with-root-param/${currentLang}/in-page/root-params`}
/>
</li>
<li>
<DebugLinkAccordion
href={`/with-root-param/${otherLang}/in-page/root-params`}
/>
</li>
</ul>
</li>
</ul>
<h2>
<code>use cache: private</code>
</h2>
<ul>
<li>
root params + dynamic content
<ul>
<li>
<DebugLinkAccordion
href={`/with-root-param/${currentLang}/in-private-cache/root-params`}
/>
</li>
<li>
<DebugLinkAccordion
href={`/with-root-param/${otherLang}/in-private-cache/root-params`}
/>
</li>
</ul>
</li>
</ul>
<h2>
<code>promise passed to public cache</code>
</h2>
<ul>
<li>
root params + dynamic content
<ul>
<li>
<DebugLinkAccordion
href={`/with-root-param/${currentLang}/passed-to-public-cache/root-params`}
/>
</li>
<li>
<DebugLinkAccordion
href={`/with-root-param/${otherLang}/passed-to-public-cache/root-params`}
/>
</li>
</ul>
</li>
</ul>
</main>
)
}

View File

@@ -0,0 +1,55 @@
import { Suspense } from 'react'
import { cachedDelay, DebugRenderKind, uncachedIO } from '../../../../shared'
import { connection } from 'next/server'
import { lang } from 'next/root-params'
export const unstable_instant = {
prefetch: 'runtime',
samples: [{ params: { lang: 'en' } }],
}
export default async function Page() {
return (
<main>
<DebugRenderKind />
<p>
This page uses root params (passed to a private cache) and some uncached
IO. Root params should always be available in static prerenders, so a
runtime prefetch should have them too, and they should not be considered
a hanging input.
</p>
<Suspense fallback="Loading 1...">
<RuntimePrefetchable />
</Suspense>
</main>
)
}
async function RuntimePrefetchable() {
const currentLang = await publicCache(lang())
return (
<div style={{ border: '1px solid blue', padding: '1em' }}>
<div id="root-param-value">{`Lang: ${currentLang}`}</div>
<Suspense fallback={<div style={{ color: 'grey' }}>Loading 2...</div>}>
<Dynamic />
</Suspense>
</div>
)
}
async function publicCache(currentLangPromise: Promise<string>) {
'use cache'
const currentLang = await currentLangPromise
await cachedDelay([__filename, currentLang])
return currentLang
}
async function Dynamic() {
await uncachedIO()
await connection()
return (
<div style={{ border: '1px solid tomato', padding: '1em' }}>
<div id="dynamic-content">Dynamic content</div>
</div>
)
}

View File

@@ -0,0 +1,24 @@
'use client'
import React from 'react'
export class ErrorBoundary extends React.Component<{
children: React.ReactNode
}> {
state = { error: null }
static getDerivedStateFromError(error) {
return { error }
}
render() {
if (this.state.error) {
return (
<div id="error-boundary">
Error boundary: {this.state.error.message}
</div>
)
}
return this.props.children
}
}

View File

@@ -0,0 +1,61 @@
'use client'
import Link, { type LinkProps } from 'next/link'
import { ComponentProps, useState } from 'react'
export function LinkAccordion({
href,
children,
prefetch,
}: {
href: string
children: React.ReactNode
prefetch?: LinkProps['prefetch']
}) {
const [isVisible, setIsVisible] = useState(false)
return (
<>
<input
type="checkbox"
checked={isVisible}
onChange={() => setIsVisible(!isVisible)}
data-link-accordion={href}
data-prefetch={getPrefetchKind(prefetch)}
/>
{isVisible ? (
<Link href={href} prefetch={prefetch}>
{children}
</Link>
) : (
<>{children} (link is hidden)</>
)}
</>
)
}
export function DebugLinkAccordion({
href,
prefetch,
}: Omit<ComponentProps<typeof LinkAccordion>, 'children'>) {
const prefetchKind = getPrefetchKind(prefetch)
return (
<LinkAccordion href={href} prefetch={prefetch} data-prefetch={prefetch}>
{href} ({prefetchKind})
</LinkAccordion>
)
}
function getPrefetchKind(prefetch: LinkProps['prefetch']) {
switch (prefetch) {
case false:
return 'disabled'
case undefined:
case null:
case 'auto':
return 'auto'
case true:
return 'true'
default:
prefetch satisfies never
}
}

View File

@@ -0,0 +1,8 @@
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
productionBrowserSourceMaps: true,
}
export default nextConfig

File diff suppressed because it is too large Load Diff