Skip to content

Commit 657f64b

Browse files
committed
add more information
1 parent b76edc7 commit 657f64b

File tree

1 file changed

+301
-45
lines changed

1 file changed

+301
-45
lines changed

src/content/docs/workers/configuration/versions-and-deployments/skew-protection.mdx

Lines changed: 301 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Skew Protection
44
head: []
55
description: Revert to an older version of your Worker.
66
---
7-
import { DashButton } from "~/components";
7+
import { Render, WranglerConfig, DashButton } from "~/components";
88

99
Skew Protection ensures that client-side JavaScript code continues to communicate with the same version of your Worker that originally served it, preventing version mismatch errors during deployments.
1010

@@ -13,69 +13,325 @@ Version skew occurs when a user's browser has loaded JavaScript from one version
1313
Cloudflare's Skew Protection resolves this problem automatically by ensuring all requests from a client session are routed to the same Worker version that served the initial page load. This works seamlessly with gradual deployments, allowing you to safely roll out new versions without breaking active user sessions.
1414

1515
### How it works
16+
Skew Protection keeps multiple versions of your Worker deployed simultaneously. Your application includes version information in each request, and Cloudflare automatically routes those requests to the matching Worker version.
1617

17-
Skew Protection works automatically for all requests without requiring any configuration or code changes. Cloudflare handles version consistency at the platform level for both client-to-server requests and Worker-to-Worker service binding calls.
18+
When Skew Protection is enabled on a Worker,
19+
- **Automatic version routing**: Requests are automatically routed to specific versions using the `Cloudflare-Workers-Version-Overrides` header or `?dpl=` query parameter
20+
- **Configurable version TTL**: Versions remain routable for a configurable period (default: 2 days) after deployment, ensuring users on older versions can complete their sessions
21+
- **Version cutoff**: Manually mark a specific version as the cutoff to immediately make all earlier versions unroutable, useful for critical security fixes or breaking changes
22+
- **Version control**: Disable routing to individual faulty versions without affecting other versions in the TTL window
1823

19-
#### Automatic version tracking for clients
24+
#### Framework support
25+
If you're using one of the following frameworks with their official Cloudflare adapters, Skew Protection will be automatically enabled by default during the build process:
26+
- SvelteKit
27+
- Remix
28+
- ...
2029

21-
When a user first loads your application, Cloudflare automatically:
30+
These adapters automatically configure Skew Protection and implement the routing logic. If you'd like to disable skew protection, you can do so.
2231

23-
1. Sets a version cookie (`__cf_worker_version`) containing the deployment ID that served the initial page load
24-
2. Includes this cookie in all subsequent requests from the same browser session
25-
3. Routes requests to the same Worker version that set the cookie
26-
4. Maintains old deployments for active sessions during gradual rollouts
32+
#### Version Identification
2733

28-
#### Skew protection during a gradual rollout
34+
Cloudflare identifies which version to route requests to using the following mechanisms, in priority order:
2935

30-
1. User visits your site and gets Version A (latest deployment)
31-
2. Cloudflare sets cookie: `__cf_worker_version=version-id`
32-
3. You deploy Version B with breaking API changes
33-
4. You create a gradual deployment: Version B at 20%, Version A at 80%
34-
5. User's browser makes an API call with the cookie
35-
6. Cloudflare routes the request to Version A (matching the cookie)
36-
7. User's session continues working without errors
36+
1. **Query parameter**: `?dpl=<version-id>` (highest priority)
37+
- Best for static assets (images, CSS, JavaScript files), iframes, WebSockets, or any request where custom headers cannot be set
38+
- Automatically added by framework adapters to dynamic imports and asset URLs
39+
- Example: `<img src="/logo.png?dpl=v870e20d">`
40+
- Example: `<script src="/_next/static/chunks/main.js?dpl=v870e20d"></script>`
3741

38-
New users visiting the site have a 20% chance of getting Version B and will consistently use Version B for their session.
42+
2. **HTTP header**: `Cloudflare-Workers-Version-Overrides: <worker-name>="<version-id>"` (fallback)
43+
- Best for API calls, fetch requests, or any request where you can control the HTTP headers
44+
- Uses [Dictionary Structured Header](https://www.rfc-editor.org/rfc/rfc8941#name-dictionaries) format to support multiple Workers in service bindings
45+
- Automatically added by framework adapters to all fetch() calls
46+
- Example: `fetch('/api/data', { headers: { 'Cloudflare-Workers-Version-Overrides': 'my-worker="v870e20d"' } })`
47+
- Example for multiple workers: `Cloudflare-Workers-Version-Overrides: worker-a="v123", worker-b="v456"`
3948

40-
### Service bindings and version consistency
49+
Query parameters take priority over headers when both are present. This ensures maximum compatibility since query parameters work universally for all request types (including static assets loaded by the browser via HTML tags), while headers only work for programmatic requests where you control the fetch call.
4150

42-
When using service bindings to connect multiple Workers, Skew Protection ensures version consistency across the entire chain of Worker calls.
51+
:::note
52+
Cloudflare's Skew Protection does not use cookies for version tracking. Cookies break multi-tab usage: if a user opens your site in two tabs and refreshes one tab to get a new version, the cookie forces the other tab to also switch versions, causing errors. Query parameters and headers avoid this problem by letting each tab maintain its own version independently.
53+
:::
4354

44-
#### How version cookies work with service bindings
55+
#### Request routing
4556

46-
**On the initial page load:**
57+
When a request includes version routing information:
4758

48-
1. Client requests Worker A
49-
2. Worker A sets `__cf_worker_version_worker_a` cookie
50-
3. Worker A calls Worker B
51-
4. Worker B sets `__cf_worker_version_worker_b` cookie in response
52-
5. Worker A forwards this cookie to the client
53-
6. Client now holds both cookies
59+
1. **Header-based routing**: `Cloudflare-Workers-Version-Overrides: my-worker="v870e20d"`
60+
2. **Query parameter routing** (for static assets): `https://example.com/style.css?dpl=v870e20d`
5461

55-
**On subsequent requests:**
62+
Cloudflare will attempt to route the request to the specified version. The routing behavior is:
5663

57-
1. Client sends both `__cf_worker_version_worker_a` and `__cf_worker_version_worker_b` cookies
58-
2. Worker A reads its own cookie to select the correct version
59-
3. Worker A extracts Worker B's cookie and includes it when calling Worker B
60-
4. Worker B reads its cookie to select the correct version
64+
**If the version is routable** (within TTL, after cutoff, not disabled):
65+
- Request is routed to the specified version
6166

62-
This ensures that both Worker A and Worker B maintain version consistency with the original deployment that served the client, even when they are deployed independently.
67+
**If the version is NOT routable** (expired, before cutoff, or manually disabled):
68+
- Request is **automatically rerouted to the active deployment**
69+
- No error is returned to the user
70+
- This ensures graceful degradation when cached version IDs become invalid
6371

64-
### Session expiration and version cleanup
65-
Sessions naturally expire after 12 hours (or your configured duration):
72+
### Enable Skew Protection
6673

67-
- When a session expires, the user's next request creates a new session with current versions
68-
- Old Worker versions remain available as long as there are active sessions using them
69-
- Once all sessions using an old version have expired, that version can be safely deleted
74+
You can enable Skew Protection via API, Dashboard, or Wrangler.
7075

71-
## Setting a version cutoff
76+
**Configuration Options:**
77+
- `enabled`: Enable or disable Skew Protection for the Worker
78+
- `version_ttl_hours`: Number of hours versions remain routable after deployment. Default: 48 hours.
7279

73-
For critical security fixes, you can set a version cutoff to immediately stop serving older versions.
80+
##### Wrangler configuration
81+
82+
<WranglerConfig>
83+
84+
```toml
85+
[skew_protection]
86+
enabled = true
87+
version_ttl_hours = 48
88+
```
89+
</WranglerConfig>
90+
91+
##### API Configuration
92+
93+
Endpoint: PATCH `/accounts/{account_id}/workers/scripts/{script_name}/settings`
94+
95+
Example request:
96+
```
97+
curl -X PATCH \
98+
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/settings" \
99+
-H "Authorization: Bearer $API_TOKEN" \
100+
-H "Content-Type: application/json" \
101+
-d '{
102+
"skew_protection": {
103+
"enabled": true,
104+
"version_ttl_hours": 48
105+
}
106+
}'
107+
```
108+
109+
110+
### List versions with routable status
111+
Endpoint: GET `/accounts/{account_id}/workers/scripts/{script_name}/versions`
112+
113+
Query parameter:
114+
- `routable` (boolean): Filter to only routable (true) or non-routable (false) versions
115+
116+
Example:
117+
```
118+
# Get only routable versions
119+
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/versions?routable=true" \
120+
-H "Authorization: Bearer $API_TOKEN"
121+
```
122+
123+
### Version cutoff
124+
125+
Mark a specific version as the cutoff point, making all earlier versions unroutable:
126+
127+
Endpoint: `POST /accounts/{account_id}/workers/scripts/{script_name}/versions/{version_id}/cutoff`
128+
129+
Example request:
130+
```
131+
curl -X POST \
132+
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/versions/va227d9d8/cutoff" \
133+
-H "Authorization: Bearer $API_TOKEN" \
134+
-H "Content-Type: application/json"
135+
```
136+
137+
Response:
138+
```json
139+
{
140+
"success": true,
141+
"errors": [],
142+
"messages": [],
143+
"result": {
144+
"version_id": "va227d9d8",
145+
"cutoff_applied": true,
146+
"cutoff_timestamp": "2025-10-25T12:00:00Z",
147+
"unroutable_versions": [
148+
{
149+
"version_id": "v3f8c2a1",
150+
"deployed_at": "2025-10-24T12:00:00Z",
151+
"previous_status": "Routable",
152+
"new_status": "Not Routable"
153+
},
154+
{
155+
"version_id": "v7ee6df6e",
156+
"deployed_at": "2025-10-20T08:00:00Z",
157+
"previous_status": "Routable",
158+
"new_status": "Not Routable"
159+
}
160+
],
161+
"total_versions_affected": 2
162+
}
163+
}
164+
```
165+
166+
The response includes:
167+
- `cutoff_timestamp`: When the version cutoff was enabled
168+
- `unroutable_versions`: Array of versions that were previously routable but are now unroutable due to the cutoff
169+
- Each affected version shows its `previous_status` and `new_status`
170+
171+
:::note
172+
173+
Requests with the `Cloudflare-Workers-Version-Overrides` header or `?dpl= query` parameter targeting versions before the cutoff will be automatically rerouted to the active deployment instead of failing. This ensures that users don't experience errors when their cached version IDs become invalid.
174+
175+
:::
176+
177+
### Disable routing for a specific version
178+
Endpoint: `Endpoint: PATCH /accounts/{account_id}/workers/scripts/{script_name}/versions/{version_id}`
179+
180+
Example Request:
74181
```bash
75-
# Cut off all versions before a specific deployment
76-
npx wrangler versions cutoff set
182+
curl -X PATCH \
183+
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/versions/v8ab1748b" \
184+
-H "Authorization: Bearer $API_TOKEN" \
185+
-H "Content-Type: application/json" \
186+
-d '{
187+
"routable": false
188+
}'
189+
```
190+
191+
### Enable routing for a specific version
192+
Endpoint: `PATCH /accounts/{account_id}/workers/scripts/{script_name}/versions/{version_id}`
193+
Example request:
194+
```bash
195+
curl -X PATCH \
196+
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/versions/v8ab1748b" \
197+
-H "Authorization: Bearer $API_TOKEN" \
198+
-H "Content-Type: application/json" \
199+
-d '{
200+
"routable": true
201+
}'
202+
```
203+
204+
## Framework Maintainers
205+
206+
If you maintain a Cloudflare adapter for a framework, you can implement Skew Protection by using the version ID that is exposed in the Worker.
207+
208+
Skew Protection has two parts that work together:
209+
- **Server-side:** Your Worker reads its version ID and sends it to the browser
210+
- **Client-side:** Browser code reads the version ID and includes it in all subsequent requests back to the Worker
211+
212+
### Server-Side Implementation
213+
214+
**Step 1: Expose Worker version information**
215+
216+
Configure the Worker to expose runtime information it needs for skew protection. This will require exposing:
217+
- Version ID: This will be exposed through the version metadata binding and will provide access to the Worker's version ID.
218+
- Worker name: The name of your Worker. This will be used in the header sent on the client side.
219+
220+
The version metadata binding will expose the Worker's current version ID at runtime through `env.CF_VERSION_METADATA`. Your adapter should add this to the Wrangler config file during the build process.
221+
222+
For skew protection, you only need the `id` field.
223+
224+
Additionally, you will need to expose the name of the Worker. You can do this by setting the Worker name as an environment variable.
225+
226+
<WranglerConfig>
227+
228+
```toml
229+
# Expose version metadata as CF_VERSION_METADATA binding
230+
[version_metadata]
231+
binding = "CF_VERSION_METADATA"
232+
233+
# Set the worker name as an environment variable
234+
[vars]
235+
WORKER_NAME = "my-app-worker"
236+
```
237+
</WranglerConfig>
238+
239+
**Step 2: Read version ID and Worker name in the Worker**
240+
241+
In your Worker's `fetch` handler, read the version ID and Worker name from the binding, so you can send it to the client.
242+
243+
```ts
244+
export default {
245+
async fetch(request, env, ctx) {
246+
// Read version ID
247+
const versionId = env.CF_VERSION_METADATA.id;
248+
249+
// Pass it to your framework's render function
250+
const html = await framework.render(request, { versionId });
251+
}
252+
};
253+
```
254+
**Step 3: Inject Version ID and Worker name Into HTML**
255+
256+
Insert the version ID and Worker name into your HTML response so the client can read it. This should happen After your framework renders the HTML, but before returning the response.
257+
258+
**Example server-side implementation**
259+
```ts
260+
export default {
261+
async fetch(request, env, ctx) {
262+
// Read version ID and worker name
263+
const versionId = env.CF_VERSION_METADATA.id;
264+
const workerName = env.WORKER_NAME;
265+
266+
// Render your framework's application
267+
let html = await renderFramework(request);
268+
269+
// Inject version ID and worker name into HTML
270+
html = html.replace('</head>',
271+
`<script>
272+
window.__CF_WORKER_VERSION__="${versionId}";
273+
window.__CF_WORKER_NAME__="${workerName}";
274+
</script></head>`
275+
);
276+
277+
return new Response(html, {
278+
headers: {
279+
'Content-Type': 'text/html'
280+
}
281+
});
282+
}
283+
};
284+
```
285+
286+
### Client-side implementation
287+
288+
The client side is responsible for reading the version information that the server injected into the HTML and including it in all subsequent requests back to the Worker. This ensures that once a user loads a page, all their API calls and asset requests continue to talk to the same Worker version.
289+
290+
**Understanding what the client needs to do**
291+
292+
When the page loads, the browser needs to:
293+
1. Read the version ID and worker name that the server injected
294+
2. Intercept all `fetch()` calls to add the version header
295+
3. Modify dynamic asset URLs to add the`?dpl=` query parameter
296+
297+
#### Understanding the version header format
298+
The `Cloudflare-Workers-Version-Overrides header` uses a Dictionary Structured Header as the format.
299+
300+
```
301+
Cloudflare-Workers-Version-Overrides: worker-name="version-id"
302+
Cloudflare-Workers-Version-Overrides: my-app-worker="db7cd8d3-4425-4fe7-8c81-01bf963b6067"
303+
```
304+
305+
When Cloudflare receives a request with this header:
306+
1. It parses the worker name and version ID
307+
2. Checks that it can route the request to this version of the Worker
308+
3. If the version specified is "unroutable" then the request will be routed to the current deployment
309+
310+
*Handling static assets**
311+
The `Cloudflare-Workers-Version-Overrides` header only works for fetch requests. For static assets loaded by HTML tags like `<img>, <link>, and <script>`, browsers don't allow you to add custom headers. Instead, you must add the version as a query parameter: `?dpl=<version-id>`
312+
313+
**Example server-side implementation**
314+
315+
```js
316+
const versionId = window.__CF_WORKER_VERSION__;
317+
const workerName = window.__CF_WORKER_NAME__;
77318

78-
# Or cut off to the latest
79-
npx wrangler versions cutoff set --latest
319+
if (versionId && workerName) {
320+
// Build the version header
321+
const versionHeader = `${workerName}="${versionId}"`;
322+
323+
// Intercept fetch to add version header
324+
const originalFetch = window.fetch;
325+
window.fetch = function(url, options = {}) {
326+
options.headers = {
327+
...options.headers,
328+
'Cloudflare-Workers-Version-Overrides': versionHeader
329+
};
330+
return originalFetch(url, options);
331+
};
332+
333+
// Handle static assets
334+
// Framework-specific: modify asset URLs to include ?dpl= parameter
335+
// Example: /image.png becomes /image.png?dpl=abc123
336+
}
80337
```
81-
This forces all users to the cutoff version or newer on their next request, even if their session hasn't expired.

0 commit comments

Comments
 (0)