demo · v138
Server-side tuning by Sec-Purpose
The point of the header is so the server can do different things on a speculative request. Watch a simulated origin route each request: skip A/B test bucketing, skip analytics, return a slimmer body for speculative GETs — and run the full path on the eventual real navigation.
request as the server sees it
GET /article/138-features HTTP/2 Host: example.com User-Agent: Chrome/138 Accept: text/html Sec-Fetch-Dest: document
server’s decision tree
awaiting a request…
| time | request | Sec-Purpose | server behaviour |
|---|
the routing logic
app.get("/article/:slug", (req, res) => {
const sp = req.headers["sec-purpose"];
const isSpec = sp?.includes("prefetch") || sp?.includes("prerender");
// A/B test bucket assignment: only for real navigations
if (!isSpec) assignBucket(req);
// Analytics: don't fire on speculative
if (!isSpec) trackPageview(req);
// Cache-Control: speculative responses should be revalidated cheap
res.set("Cache-Control", isSpec ? "private, max-age=30" : "private, max-age=300");
// Body: speculative may return a "warming" payload that excludes
// personalised recommendations; promote runs the full path.
res.send(renderPage(req, { skipPersonalised: isSpec }));
});
Before 138, <link rel=prefetch> arrived as a normal GET. Servers had to either treat every page-fetch as speculative (costly) or run all the side effects on every prefetch (corrupting A/B tests and analytics). With Sec-Purpose, the server can branch precisely.
see also
- Sec-Purpose: prefetch header — feature index
- Sec-Purpose basics
- ChromeStatus entry