mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
Removes the "allow-runtime" prefetch config, and turns its behavior on implicitly wherever Partial Prefetching is enabled. The original motivation for "allow-runtime" was to give apps more control over server costs triggered by prefetches. Until a route explicitly opts in, prefetches would only be served from the CDN, not from the server. The problem, though, was it was very confusing to know when to add or remove this configuration. The incentive for many apps was to add it everywhere, with no clear signal for when to remove it. Our updated thinking is that Partial Prefetching itself already provides sufficient protection against runaway prefetching costs: per-link prefetches only happen on Link components that explicitly opt in with the prefetch prop. The optimizations landed earlier in this stack also make allow-runtime less necessary: on pages where all the content is statically renderable, prefetches are served from the static cache and no runtime request is ever issued; only a page that accesses non-static data is prefetched at runtime. The upshot of this decision is that runtime versus static becomes an internal optimization; the same content gets prefetched regardless of whether or how Next.js is able to optimize it.
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
import Link from 'next/link'
|
|
import { Suspense, cacheSignal } from 'react'
|
|
import { setTimeout } from 'timers/promises'
|
|
import { cookies } from 'next/headers'
|
|
|
|
// `'partial'` opts the route into Partial Prefetching (the two-phase Shell
|
|
// then Speculative flow) and marks it as needing a runtime request pass. A full
|
|
// prefetch (the Link below uses prefetch={true}) resolves the params-dependent
|
|
// content via a per-request runtime prefetch (PPRRuntime), which is what
|
|
// exercises the non-deterministic cache args during a runtime prefetch.
|
|
export const prefetch = 'partial'
|
|
|
|
const callCounts = new Map<string, number>()
|
|
|
|
function getNonDeterministicItems(slug: string) {
|
|
// For testing purposes, this creates an artificial deterministic scenario
|
|
// that in real-world apps would usually be triggered by a race condition
|
|
// only sometimes (e.g. concurrent async operations pushing results into a
|
|
// shared array in non-deterministic order, and that array then being passed
|
|
// as an argument to a cached function).
|
|
//
|
|
// Call 1 (prospective prerender) warms the RDC entry for ['b','a'].
|
|
// Call 2+ (final prerender) uses ['a','b'], which produces a different cache
|
|
// key that misses the RDC.
|
|
const count = (callCounts.get(slug) ?? 0) + 1
|
|
callCounts.set(slug, count)
|
|
return count === 1 ? ['b', 'a'] : ['a', 'b']
|
|
}
|
|
|
|
async function getCachedValue(items: string[]) {
|
|
'use cache'
|
|
await setTimeout(1000)
|
|
return items.join(',')
|
|
}
|
|
|
|
async function Content({ slug }: { slug: string }) {
|
|
if (slug === 'known') {
|
|
// Awaiting cookies() causes this to suspend during build-time prerendering
|
|
// (cookies are not available at build time). During runtime prefetch,
|
|
// cookies are available so rendering continues past this point and hits the
|
|
// non-deterministic cached function.
|
|
await cookies()
|
|
}
|
|
|
|
const items = getNonDeterministicItems(slug)
|
|
|
|
try {
|
|
const value = await getCachedValue(items)
|
|
return (
|
|
<p id="cached">
|
|
{slug} {value}
|
|
</p>
|
|
)
|
|
} catch (error) {
|
|
// We only log and re-throw errors that occur while the render is still
|
|
// active. After the prerender is aborted, the hanging promise rejects with
|
|
// an expected error that we can safely ignore. This is the intended use of
|
|
// `React.cacheSignal()`. Userspace code should use it the same way to
|
|
// distinguish real errors from expected prerender teardown.
|
|
if (!cacheSignal()?.aborted) {
|
|
console.error('getCachedValue error:', error)
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
export default async function Page({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const { slug } = await params
|
|
|
|
if (slug === 'with-runtime-prefetch') {
|
|
// Use a full prefetch so the per-request runtime prefetch (PPRRuntime)
|
|
// fires during the Speculative phase. An auto prefetch of a partial
|
|
// route only warms the reusable shell (RuntimeShell) instead.
|
|
return (
|
|
<Link href="/known" prefetch={true}>
|
|
Go to runtime-prefetchable page
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Suspense fallback={<p>Loading...</p>}>
|
|
<Content slug={slug} />
|
|
</Suspense>
|
|
)
|
|
}
|
|
|
|
export function generateStaticParams() {
|
|
return [{ slug: 'known' }, { slug: 'with-runtime-prefetch' }]
|
|
}
|