Note
Reload without navigating after a deploy: which cache answered?
Symptom: after a deploy, clicking a link in an already-open tab reloads the page once and stays on the same page. Seen in two projects (Nuxt 2 and Next.js) on Vercel behind Cloudflare, with Skew Protection on. The cause is not settled.
Expected behavior (docs)
- Skew Protection pins framework-managed requests (assets, client-side navigation data, prefetches, Server Actions) to the deployment that served the page, using
?dpl=orx-deployment-id. - Full-page navigations are not pinned. Vercel serves the latest deployment, the client detects the mismatch, and the page reloads once. One reload after a deploy is expected.
- Next.js: on a mismatch it does a hard navigation to the target URL (issue #99165, quoting the docs). Staying on the same page is not described.
- Pro and Enterprise plans only. Nuxt is listed without a version. Nuxt 2 support is not confirmed by any source found.
Conditions that change it
- Enabling. Projects created before November 19, 2024 need System Environment Variables enabled, the switch turned on, and a redeploy.
- Maximum Age. Default one day. Older deployment IDs are ignored and the latest deployment answers.
- The
__vdplcookie. Pins document navigations too, so no reload after a deploy. Meant for long sessions. - Custom code. Requests without the deployment ID reach the latest deployment. In TheNinjaRPG PR #2201, a custom build-version check called
window.location.reload()on route change and caused reloads after nearly every deploy.
Headers on the HTML request
| Header | Value | Meaning |
|---|---|---|
cf-cache-status | DYNAMIC | Cloudflare treated the response as not eligible for cache and went to the origin |
x-vercel-cache | HIT | Served from Vercel's cache |
DYNAMICrules out Cloudflare holding old HTML, so a Cache Rule or purge would not address it.HITis inconclusive: Vercel scopes cached responses to the deployment that produced them.
Header fix tried
Cache-Control: no-store, must-revalidate on everything except static asset paths:
export default {
async headers() {
return [
{
source: "/((?!_next/static|_next/image|favicon.ico).*)",
headers: [{ key: "Cache-Control", value: "no-store, must-revalidate" }],
},
];
},
};- Next.js docs: immutable assets cannot be overridden, other responses can. Whether
no-storetakes effect on prerendered pages on Vercel was not verified. - Nuxt 2: the
render.staticoption covers only thestatic/directory, so it is an unlikely place for a header on rendered pages. headersinvercel.jsonwas not tried.
Open questions
- Whether the header change affected the problem (result not recorded).
- Which deployment served the page after the reload. The
?dpl=value on asset URLs before and after should show it. - Whether Skew Protection was redeployed after being switched on, and whether the tab had been open longer than the Maximum Age.
- Whether either project has custom reload code (version checker, error boundary, chunk-error handler).