mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
b18acf6712
Documents are now served with `no-store` in development, so a browser never restores one from its HTTP cache and the page scripts never re-execute against a debug channel that has already delivered its data. The persistence and restore machinery that existed for that case has no remaining trigger, so this removes it: the `IndexedDB` write scheduled on every page load, the cache-restore detection across `PerformanceNavigationTiming` fields and `deliveryType`, the `pageshow` deferral for browsers that populate those fields late, and the `location.reload()` fallback for a missing entry. It was built up over #92892, #93486, #94128, #94317 and #94243, and takes `debug-channel.ts` from 535 lines to 121. The per-consumer `tee()` and the LRU-bounded pair map stay. They were added for an unrelated reason, namely that one response can be decoded more than once, so this is not a revert to the state before the persistence landed. The rejection handler on `writer.closed` also stays, because an errored stream would otherwise surface as an unhandled rejection now that nothing else observes it. `bfcache-regression` keeps the original regression test, which loads a page, navigates away, comes back and asserts that the counter is still interactive. That case now fails if the development `Cache-Control` value ever goes back to `no-cache`, because the restored document would block hydration with no reload to recover, so it is worth keeping as is. The other three tests lose their premise and are deleted along with the routes only they used: the pruning case that was skipped when the header changed, the recovery case that needs a restore path to recover into, and the streaming case that guarded the detection against treating an in-flight response as a restore. The `large-debug-data` route goes too. It existed only to make the persistence write expensive enough to profile by hand when it moved to `IndexedDB`.
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
import type * as Playwright from 'playwright'
|
|
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
import { readFile, writeFile } from 'fs/promises'
|
|
import { join } from 'path'
|
|
|
|
describe('dev Cache-Control', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
it('sends no-store for an app router document', async () => {
|
|
const res = await next.fetch('/')
|
|
expect(res.headers.get('Cache-Control')).toBe('no-store')
|
|
})
|
|
|
|
it('sends no-store for a pages router document', async () => {
|
|
const res = await next.fetch('/pages-route')
|
|
expect(res.headers.get('Cache-Control')).toBe('no-store')
|
|
})
|
|
|
|
it('keeps serving static assets from the browser cache', async () => {
|
|
const browser = await next.browser('/')
|
|
const assetStatusCodes: number[] = []
|
|
|
|
browser.on('response', (response: Playwright.Response) => {
|
|
const url = new URL(response.url())
|
|
|
|
// The webpack dev bundler adds a `v` query to some of its own chunks to
|
|
// bust the browser cache on every page load. Those are never cache hits
|
|
// by design.
|
|
if (
|
|
url.pathname.startsWith('/_next/static/') &&
|
|
!url.searchParams.has('v')
|
|
) {
|
|
assetStatusCodes.push(response.status())
|
|
}
|
|
})
|
|
|
|
// Only the responses of the second page load are of interest.
|
|
assetStatusCodes.length = 0
|
|
await browser.refresh()
|
|
|
|
await retry(async () => {
|
|
expect(assetStatusCodes.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
// The dev server answers the revalidation of an unchanged asset with 304,
|
|
// so the browser reuses the body from its cache instead of downloading it
|
|
// again. `no-store` would force a full download on every page load.
|
|
expect([...new Set(assetStatusCodes)]).toEqual([304])
|
|
})
|
|
|
|
// Runs last because it edits a file that the other test cases rely on.
|
|
it('serves an edited page after a back navigation', async () => {
|
|
const browser = await next.browser('/')
|
|
expect(await browser.elementByCss('#value').text()).toBe('Value A')
|
|
|
|
// A plain anchor triggers a document navigation, so the browser can keep
|
|
// the page it navigates away from in its HTTP cache.
|
|
await browser.elementByCss('#to-about').click()
|
|
await browser.waitForElementByCss('#about')
|
|
|
|
const valueFile = join(next.testDir, 'app/value.ts')
|
|
const value = await readFile(valueFile, 'utf8')
|
|
await writeFile(valueFile, value.replace('Value A', 'Value B'))
|
|
|
|
// The dev server must serve the edited value before going back, so that a
|
|
// stale page can only come from the browser.
|
|
await retry(async () => {
|
|
const $ = await next.render$('/')
|
|
expect($('#value').text()).toBe('Value B')
|
|
})
|
|
|
|
await browser.back({ waitUntil: 'commit' })
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('#value').text()).toBe('Value B')
|
|
})
|
|
})
|
|
})
|