mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
3de2d1a213
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.
138 lines
5.2 KiB
TypeScript
138 lines
5.2 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry, waitFor } from 'next-test-utils'
|
|
|
|
// Partial prefetching is enabled here (and Cache Components), so a client
|
|
// navigation to a route with `export const prefetch = 'partial'` reveals
|
|
// the runtime shell. Caches that resolve in the runtime stage (a `'use cache'`
|
|
// read after `await params`, or a private cache) are therefore part of the
|
|
// shell for that navigation, so a cold cache must show the Cold cache badge,
|
|
// unlike on an initial (static-shell) load, where those same caches sit after
|
|
// the shell. (The static-shell behavior is covered in the `cache-indicator`
|
|
// suite.)
|
|
describe('cache-indicator-partial-prefetching', () => {
|
|
const { next, skipped } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
if (skipped) {
|
|
return
|
|
}
|
|
|
|
it('cache after session data triggers cold cache indicator', async () => {
|
|
const browser = await next.browser('/')
|
|
|
|
// Cold navigation: the cache read sits after `await params`, so it resolves
|
|
// in the app shell for this navigation, so a cold cache there shows the badge.
|
|
await browser.elementByCss('a[href="/cookies"]').click()
|
|
await browser.elementById('cookies')
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
true
|
|
)
|
|
})
|
|
|
|
// Navigate to the static home (a fresh render clears the badge), then wait
|
|
// for the background fill to settle so the next navigation is a warm hit.
|
|
await browser.elementByCss('a[href="/"]').click()
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
false
|
|
)
|
|
})
|
|
await waitFor(2000)
|
|
|
|
// Warm navigation: the runtime shell is a cache hit, so no badge. An absence
|
|
// can't be retried on, so wait out the replay window, then assert it never
|
|
// appeared.
|
|
await browser.elementByCss('a[href="/cookies"]').click()
|
|
await browser.elementById('cookies')
|
|
await waitFor(500)
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(false)
|
|
})
|
|
|
|
describe('cache after link data', () => {
|
|
it('does not trigger cold cache indicator without prefetch={true}', async () => {
|
|
const browser = await next.browser('/')
|
|
|
|
// Cold navigation: the cache read sits after `await params`, so it resolves
|
|
// in the runtime stage, part of the runtime shell for this navigation, so a
|
|
// cold cache there shows the badge.
|
|
await browser.elementByCss('a[href="/params/prefetch-auto"]').click()
|
|
await browser.elementById('params')
|
|
|
|
await waitFor(500)
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
false
|
|
)
|
|
})
|
|
|
|
// TODO(app-shells): simulate `prefetch={true}` behavior
|
|
it.failing(
|
|
'triggers cold cache indicator with prefetch={true}',
|
|
async () => {
|
|
const browser = await next.browser('/')
|
|
|
|
// Cold navigation: the cache read sits after `await params`, so it resolves
|
|
// in the runtime stage as part of the speculative prefetch shell for this navigation.
|
|
// a cold cache there shows the badge.
|
|
await browser.elementByCss('a[href="/params/prefetch-true"]').click()
|
|
await browser.elementById('params')
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
true
|
|
)
|
|
})
|
|
|
|
// Navigate to the static home (a fresh render clears the badge), then wait
|
|
// for the background fill to settle so the next navigation is a warm hit.
|
|
await browser.elementByCss('a[href="/"]').click()
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
false
|
|
)
|
|
})
|
|
await waitFor(2000)
|
|
|
|
// Warm navigation: the cache is a hit, so no badge.
|
|
await browser.elementByCss('a[href="/params/prefetch-true"]').click()
|
|
await browser.elementById('params')
|
|
await waitFor(500)
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
false
|
|
)
|
|
}
|
|
)
|
|
})
|
|
|
|
it('shows the Cold cache badge on a cold runtime-prefetch navigation to a private-cache route, not on a warm one', async () => {
|
|
const browser = await next.browser('/')
|
|
|
|
// Cold navigation: the private cache resolves in the app shell, so a
|
|
// cold cache there shows the badge.
|
|
await browser.elementByCss('a[href="/private"]').click()
|
|
await browser.elementById('private')
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
true
|
|
)
|
|
})
|
|
|
|
// Navigate to the static home (a fresh render clears the badge), then wait
|
|
// for the background fill to settle.
|
|
await browser.elementByCss('a[href="/"]').click()
|
|
await retry(async () => {
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(
|
|
false
|
|
)
|
|
})
|
|
await waitFor(2000)
|
|
|
|
// Warm navigation: the private entry is served from the dev store, so no
|
|
// badge.
|
|
await browser.elementByCss('a[href="/private"]').click()
|
|
await browser.elementById('private')
|
|
await waitFor(500)
|
|
expect(await browser.hasElementByCss('[data-cold-cache-badge]')).toBe(false)
|
|
})
|
|
})
|