Files
T
Andrew ClarkandGitHub 3de2d1a213 Unify allow-runtime with Partial Prefetching (#96106)
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.
2026-07-28 11:51:29 -04:00

44 lines
1.4 KiB
TypeScript

import { Suspense } from 'react'
import { setTimeout } from 'timers/promises'
import { cacheLife } from 'next/cache'
export const prefetch = 'partial'
// A distinct slug per test keys a separate cache entry (so the first request
// for each slug is a genuine cold miss), while both tests share this one
// runtime-prefetchable page. Declaring the slugs also keeps `params` statically
// known, so the page shell doesn't depend on dynamic params. In development
// this does not pre-fill the cache.
export function generateStaticParams() {
return [{ slug: 'nav' }, { slug: 'reload' }]
}
async function getExpireZeroValue(slug: string) {
'use cache'
// An explicit short `expire` opts this public cache into a dynamic,
// client-only life: excluded from the static shell, but included in the
// runtime prefetch. The slug keys the entry; the value itself is just a
// timestamp.
cacheLife({ expire: 0 })
await setTimeout(1500)
return new Date().toISOString()
}
async function ExpireZeroCached({ slug }: Promise<{ slug: string }>) {
const value = await getExpireZeroValue(slug)
return <p id="expire-zero">{value}</p>
}
export default function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
return (
<Suspense fallback={<p id="expire-zero-fallback">Loading...</p>}>
<ExpireZeroCached slug={params.then(({ slug }) => slug)} />
</Suspense>
)
}