Skip to main content

Deployment Skew Protection

When you deploy a new version, browsers that are still running the previous version keep the session alive. They continue to request old, content-hashed JS/CSS chunks (which the new deployment may no longer serve — a ChunkLoadError and broken lazy navigation) and to call the BFF/API, now served by the new version with a potentially changed contract.

Skew protection keeps those clients working during a rollout. It is provider-agnostic — the generic implementation works anywhere (including Kubernetes) — with a dedicated Vercel provider that maps it onto Vercel's platform Skew Protection.

The feature is off by default and fully inert until you enable it: no change to bundles, headers, cookies, or asset URLs.

Enabling it

Add a skewProtection block to server in your archibald.json:

{
"server": {
"skewProtection": {
"active": true
}
}
}

Options

OptionDefaultDescription
activefalseMaster switch. Everything below only applies when true.
headerx-deployment-idRequest/response header carrying the deployment id.
cookie{ name: "arc-deployment", path: <public path> }Cookie written on SSR HTML responses to pin a client to its deployment. Set to false to disable.
assetQueryParamfalseWhen set (e.g. "dpl"), SSR-emitted asset URLs get ?<param>=<deploymentId> appended.
onMismatch"header"Client reaction to a version mismatch: ignore, header (response header only), or reload (full-reload on the next navigation).

The deployment id

Each build is stamped with a deployment id, resolved in this order:

  1. ARC_DEPLOYMENT_ID environment variable (set it in CI to pin an explicit id);
  2. VERCEL_DEPLOYMENT_ID (provided automatically by Vercel);
  3. the short git commit hash (best-effort fallback).

The id is baked into both the client and server bundles, so an old client reports the version it was built from. On the server, a runtime environment variable (ARC_DEPLOYMENT_ID / VERCEL_DEPLOYMENT_ID) wins over the baked value — so a platform that injects the id at deploy time (e.g. a Kubernetes pod spec) overrides it without a rebuild.

How it works

  • Requests are tagged. Every client→BFF request carries the client's deployment id in the configured header.
  • Responses expose the serving version. SSR responses set the deployment-id header and (unless disabled) the pinning cookie. When a request's client id differs from the serving id, the server adds an x-deployment-skew response header.
  • Assets can be versioned. With assetQueryParam set, SSR-emitted <script>/<link> URLs are suffixed with the deployment id so an infrastructure layer can route them.
  • Clients recover. A failed dynamic-import chunk triggers a single, session-guarded page reload (no reload loops). With onMismatch: "reload", a client that observes a version mismatch full-reloads on its next navigation — never mid-interaction.

Kubernetes

There is no platform Skew Protection on Kubernetes, so compose the generic primitives with your infrastructure. In rough order of preference:

  1. Shared immutable asset store (recommended). Publish dist/client to a bucket/CDN and point optimization.asset.host at it. Because chunk filenames are content-hashed, old and new chunks never collide and old chunks stay resolvable indefinitely — skew reduces to API-contract drift, which the mismatch detection surfaces.
  2. Ingress version affinity. Route by the arc-deployment cookie / x-deployment-id header to the matching ReplicaSet during the rollout overlap window (e.g. nginx canary-by-cookie or Istio header matching).
  3. Reload fallback. The ChunkLoadError reload-once and onMismatch: "reload" behaviours are the safety net when neither of the above is in place.

Not covered

  • Retaining several previous builds on a single pod's local disk.
  • Routing between application versions (that is your ingress/platform's responsibility).
  • SmartEdit / CMS preview skew.