Skip to content
Son Tran (Snow)
All posts

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= or x-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 __vdpl cookie. 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

HeaderValueMeaning
cf-cache-statusDYNAMICCloudflare treated the response as not eligible for cache and went to the origin
x-vercel-cacheHITServed from Vercel's cache

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-store takes effect on prerendered pages on Vercel was not verified.
  • Nuxt 2: the render.static option covers only the static/ directory, so it is an unlikely place for a header on rendered pages.
  • headers in vercel.json was 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).