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
| Option | Default | Description |
|---|---|---|
active | false | Master switch. Everything below only applies when true. |
header | x-deployment-id | Request/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. |
assetQueryParam | false | When 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:
ARC_DEPLOYMENT_IDenvironment variable (set it in CI to pin an explicit id);VERCEL_DEPLOYMENT_ID(provided automatically by Vercel);- 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-skewresponse header. - Assets can be versioned. With
assetQueryParamset, 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:
- Shared immutable asset store (recommended). Publish
dist/clientto a bucket/CDN and pointoptimization.asset.hostat 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. - Ingress version affinity. Route by the
arc-deploymentcookie /x-deployment-idheader to the matching ReplicaSet during the rollout overlap window (e.g. nginx canary-by-cookie or Istio header matching). - 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.