demo · v141
Header Migration Guide
Chrome 141 stops sending the legacy Purpose: prefetch header and standardises on Sec-Purpose: prefetch. This guide shows the full timeline, server migration patterns, and a "Is my server ready?" code scanner — so nothing breaks after the update.
Header evolution timeline
Chrome < 103
Legacy header only
Chrome sent Purpose: prefetch on prefetch requests. Non-standard — no CORS alignment, no browser consensus.
Chrome 103–140
Both headers (transition period)
Chrome sent both Purpose: prefetch and the new Sec-Purpose: prefetch. Servers could migrate gradually while keeping compatibility.
Chrome 141+ (current)
Sec-Purpose only
Legacy Purpose: prefetch is removed. Only Sec-Purpose: prefetch is sent. Servers must be updated or they lose prefetch detection.
Future
Cross-browser standardisation
Firefox and Safari expected to adopt Sec-Purpose as part of navigation speculation spec standardisation.
Sec-Purpose values matrix
| Sec-Purpose value | Meaning | Old Purpose header equivalent | When Chrome sends it |
|---|---|---|---|
prefetch |
Request is a prefetch — fetch-only, not rendered | Purpose: prefetch |
Speculation rules with prefetch type; link rel=prefetch |
prefetch;prerender |
Request is part of a prerender — full render, not yet activated | (no equivalent — prerender is new) | Speculation rules with prerender type; sub-resources of a prerender |
| (absent) | Normal navigation or regular fetch | (absent) | All other requests |
Server code: before and after
// ❌ OLD — breaks in Chrome 141
app.use((req, res, next) => {
if (req.headers['purpose'] === 'prefetch') {
// serve prefetch-optimized response
res.setHeader('Cache-Control', 'no-store');
}
next();
});
// ✅ NEW — Chrome 141 compatible
app.use((req, res, next) => {
const secPurpose = req.headers['sec-purpose'] || '';
if (secPurpose === 'prefetch') {
// prefetch request (fetch only, not yet rendered)
res.setHeader('Cache-Control', 'no-store');
} else if (secPurpose === 'prefetch;prerender') {
// prerender request (full render, not yet activated)
res.setHeader('Cache-Control', 'no-store');
}
next();
});
// ✅ TRANSITION — supports Chrome 103–140 AND Chrome 141+
app.use((req, res, next) => {
const secPurpose = req.headers['sec-purpose'] || '';
const legacyPurpose = req.headers['purpose'] || '';
const isPrefetch = secPurpose.startsWith('prefetch') ||
legacyPurpose === 'prefetch';
if (isPrefetch) {
res.setHeader('Vary', 'Sec-Purpose, Purpose');
res.setHeader('Cache-Control', 'no-store');
}
next();
});
# ❌ OLD — breaks in Chrome 141
if ($http_purpose = "prefetch") {
add_header X-Prefetch-Mode "true";
}
# ✅ NEW — Chrome 141 compatible
if ($http_sec_purpose ~* "prefetch") {
add_header X-Prefetch-Mode "true";
}
# For Vary header (cache keying):
# OLD: Vary: Purpose
# NEW: Vary: Sec-Purpose
add_header Vary "Sec-Purpose";
# TRANSITION (keep both during rollout):
map $http_sec_purpose $is_prefetch {
~prefetch 1;
default $http_purpose; # fallback to legacy
}
// Cloudflare Workers / Deno Deploy / Vercel Edge
// ❌ OLD
export default {
async fetch(request) {
if (request.headers.get('Purpose') === 'prefetch') {
return new Response('prefetch', { headers: { 'Cache-Control': 'no-store' } });
}
return fetch(request);
}
};
// ✅ NEW — Chrome 141
export default {
async fetch(request) {
const secPurpose = request.headers.get('Sec-Purpose') ?? '';
if (secPurpose.startsWith('prefetch')) {
const response = await fetch(request);
return new Response(response.body, {
headers: {
...Object.fromEntries(response.headers),
'Cache-Control': 'no-store',
'Vary': 'Sec-Purpose',
}
});
}
return fetch(request);
}
};
Request simulator
Choose a Chrome version and request type to see which headers would be present on the request, and which your server must look for.
Choose options and press "Simulate request".
"Is my server ready?" checker
Paste your server middleware or route handler. The checker looks for legacy Purpose header references and suggests migration.
Migration checklist
-
01
Audit Vary headers. If your CDN caches on
Vary: Purpose, update toVary: Sec-Purpose. Old cached responses keyed onPurposewill be served to all Chrome 141+ users as normal traffic — potential analytics inflation. -
02
Update analytics suppression logic. The most common use of
Purpose: prefetchwas to skip recording pageviews for prefetched pages. After Chrome 141, those requests no longer carry the old header — you must checkSec-Purposeor risk double-counting prerenders as pageviews. -
03
Update A/B test exposure logic. Experiments that fire on page load must check
Sec-Purposeto avoid assigning users to variants before they've seen the page. -
04
Keep dual-header support during rollout. Chrome 103–140 still sends both headers. Accept
Sec-Purposefirst, fall back toPurposefor backward compatibility. Remove the fallback once Chrome 140 falls below your traffic floor. -
05
Add Speculation-Rules response header to cacheable pages. Servers can now signal allowed speculation rules back to the browser, improving cache coherence with
Sec-Purpose-aware caching layers.
see also
- Stop Sending Purpose: Prefetch Header — feature index
- ChromeStatus entry
- Spec