Need Some Help?
I help companies find and fix site-speed issues. Performance audits, training, consultancy, and more.























(last updated on )
Written by on CSS Wizardry.
Independent writing is brought to you via my wonderful Supporters.
No-Vary-Search DoesNo-Vary-Search Syntaxes
No-Vary-Search RulesI’ve written,
spoken, and
generally gone on at
length about
caching for years now, but
a newer addition to the conversation is No-Vary-Search: an HTTP response
header that helps us solve a surprisingly common problem with cache keys in HTTP
cache (or browser cache).
The short version is, URLs that are materially the same often fail to reuse the same cached response simply because their query strings differ. Sometimes that difference matters to the content and, therefore, the end user. For example:
?colour=red?colour=blueAnd sometimes, it really doesn’t matter at all:
?utm_source=google?utm_source=chatgptThe former are very likely different pages, or at least pages that ought to produce different content. We would not want them cached under the same key.
The latter should, in almost every sane setup, return the exact same HTML. They are the same page, just with different tracking baggage attached.
And yet, to an HTTP cache, different query strings traditionally equate to completely different URLs, which means different cache entries. That is wasteful.
Need Some Help?
I help companies find and fix site-speed issues. Performance audits, training, consultancy, and more.
By default, caches are cautious. If the URL differs, the cache key differs, and, in a cautious world, that is usually the right thing to do.
This is why:
/products/shoes?colour=red/products/shoes?colour=blue…should remain distinct. The query parameter materially changes the content of the page.
But this also means the cache will usually treat these as distinct pages, too—even if all three return byte-for-byte identical HTML.
/sale?utm_source=google/sale?utm_source=chatgpt/sale?utm_source=newsletterAt best, that means wasted cache space; at worst, it means unnecessary trips across the network because the browser cannot reuse a perfectly good response that it already has stored.
This is exactly what No-Vary-Search addresses.
No-Vary-Search DoesNo-Vary-Search
is a response header that tells the cache how to treat query parameters when
matching a URL to an existing cache entry.
In other words, it lets the server say, via a header, these search parameters
do not meaningfully change the response, so do not let them fragment the
cache
.
This is a little reminiscent of the well known
Vary
header, but aimed squarely at URL search parameters rather than request headers.
That distinction matters. Vary says this response depends on
or Accept-Languagethis response depends on
, for
example. Accept-EncodingNo-Vary-Search says this response does not depend on
or utm_sourcethe order of these parameters should not matter
, and
so on.
If you know that certain parameters are irrelevant to the response body, you can tell the cache to ignore them, effectively allow- or blocklisting them for cache key purposes.
Imagine a landing page that is heavily used in campaigns, ads, email, and social posts:
/offer?utm_source=google/offer?utm_source=chatgpt/offer?utm_source=linkedinIf those all return the same page, you can tell the cache to ignore
utm_source:
No-Vary-Search: params=("utm_source")
Now, as far as cache matching is concerned, those URLs may all reuse the same
stored response. They will no longer be treated as separate cache entries just
because of the different utm_source values. Much more effective reuse of
cached content.
That is the crucial thing to understand: No-Vary-Search is not changing the
URL, and it is not rewriting requests. It is changing how cache matching treats
the differing query string.
No-Vary-Search SyntaxesThe header has a few useful, different forms.
This is the form I suspect most people will use most often:
No-Vary-Search: params=("utm_source" "utm_medium" "utm_campaign" "fbclid" "gclid")
This says: if the only differences between two URLs are those parameters, treat them as the same cache key.
This is ideal for analytics and campaign tagging, where the query string is useful to you but should not change the response for the user. These standard tracking and social parameters could probably be safely applied to most, if not all, sites.
If your page genuinely does not vary by query string at all, you can be much broader:
A quick update: the params boolean syntax shown below
is likely to change in the spec to an empty list, params=(), instead. At the
time of writing, this newer syntax is not yet implemented in browsers, so the
original params form remains what you’re most likely to encounter in
practice. Thanks to Barry
Pollard for the heads-up. Keep an eye on the HTTPWG discussion and the Chromium issue for progress.
That is the boolean form of params, and it tells the cache to ignore all
search parameters for matching purposes. This works perfectly for my site which
has zero back end, and thus cannot possibly vary by query string.
This is obviously powerful, but also the easiest way to shoot yourself in the foot, so only use it if you really mean it.
Sometimes the inverse is easier to express. Perhaps most parameters are irrelevant, but a small number genuinely change the response:
No-Vary-Search: params, except=("colour" "size")
This says: ignore all query parameters except colour and size.
That would be a decent fit for a page where:
utm_* tags do not matter,?colour, ?size) do.In that world:
/products/shoes?utm_source=google&colour=red/products/shoes?utm_source=chatgpt&colour=red/products/shoes?colour=red&sort=pricecould share a cache entry, but:
/products/shoes?colour=red/products/shoes?colour=blueshould not.
Sometimes the parameters themselves matter, but their order does not:
/search?q=shoes&sort=price/search?sort=price&q=shoesThese should usually be treated as equivalent. For that, there is key-order:
No-Vary-Search: key-order
That tells the cache not to create separate entries just because the same parameters arrived, only in a different order.
No-Vary-Search RulesYou can combine directives:
No-Vary-Search: key-order, params, except=("colour" "size")
That tells the cache:
colour and size.This means the three following URLs could all share a cache entry:
/products/shoes?utm_source=google&colour=red/products/shoes?colour=red&sort=price/products/shoes?sort=price&colour=redThat is probably the most expressive form, and in real systems it may prove the most useful. This is phenomenally powerful.
No-Vary-Search uses Structured
Fields syntax, so the parameter lists
are space-separated quoted strings:
No-Vary-Search: params=("utm_source" "utm_medium" "gclid")
…not comma-separated values that you may be used to in most other places.
That is a small detail, but one worth being aware of.
Note that this also creates a slightly unusual debugging scenario. A very common way to force what we assume will be a fresh trip to the server is to throw a random search parameter on the end of the URL. I’m sure we’ve all done something like this before:
/?foo/?test/?asdfUsually, that gives us a different URL and therefore a different cache key. But
if the main document is using No-Vary-Search, that assumption may no longer
hold. Appending search params may not bypass cache for this document because the
cache has explicitly been told those parameters do not matter.
Honestly, I would love DevTools to surface this more clearly. Something like the
existing ⚠️ iconography in the Network panel’s title would be really helpful
here: not because anything is wrong per se, but because the browser may be doing
something surprising unless you know to look for the No-Vary-Search header.
One of the trickier practical problems with No-Vary-Search is keeping it all
in sync. The marketing team may start using a new utm_* parameter, or an
ecommerce team may ship a new filter or facet, and unless that change is
reflected in the No-Vary-Search header, caching behaviour may well differ from
the ideal scenario.
For that reason, the sensible and defensive default is to always let new and unknown parameters vary the cache key. In other words, if you do not yet know whether a parameter is meaningful, treat it as semantic until proven otherwise.
Imagine marketing starts adding new params, e.g.:
?utm_campaign_variant=summer-a?utm_campaign_variant=summer-bIf those parameters are absent from No-Vary-Search, then yes, you will miss
out on some caching opportunities, but that is no slower than the situation
we’ve lived with all along. The worst case scenario is just the status quo:
separate cache entries for URLs that are materially the same.
On the flip side, imagine the ecommerce team introduces:
?material=leather?material=canvasIf those parameters are absent from No-Vary-Search, that is by far the safer
outcome. We’d much rather let them produce separate cache entries than
accidentally fold distinct pages into one.
This is the same basic principle I recently wrote about in When All You Can Do Is All or Nothing, Do Nothing: when the system lacks the context to be precise, the safer fallback is usually the less clever one. In this case, that means allowing unknown parameters to bust cache until someone has explicitly decided they are safe to ignore.
Again, this is not wasteful—it’s just the same behaviour we’ve always had.
Need Some Help?
I help companies find and fix site-speed issues. Performance audits, training, consultancy, and more.
This header is only as good as the assumptions behind it: if two URLs really do return meaningfully different content, then they need different cache entries. I’d rather be served the correct page a little more slowly than the wrong page quickly.
This means No-Vary-Search is best suited to parameters that are:
If a parameter affects the HTML, do not ignore it.
It’s also worth noting that, at the time of writing, this is still an experimental feature and support is not yet universal, so I would treat it as a progressive enhancement rather than a foundational part of your caching strategy. That’s exactly what I’ve done with my site for now.
What I like about No-Vary-Search is that it acknowledges how the web actually
works. URLs pick up baggage: marketing tags get appended, tracking parameters
are added, client-side state makes its way into the address bar. Two URLs that
are materially the same page often arrive looking totally different.
Historically, caches had to treat those as entirely separate keys, but
No-Vary-Search gives us a way to be a little more deliberate. If the response
is the same, we can say so. And if only certain parameters matter, we can say
that, too.
For teams who care about getting more out of the HTTP cache, that is a very welcome addition!
No-Vary-Search is an HTTP response header that tells caches which URL search parameters can be ignored when matching requests to cached responses.
It reduces cache fragmentation caused by irrelevant query parameters such as UTM tags, so materially identical URLs can reuse the same cached response.
Use it when some query parameters do not meaningfully change the response body, such as analytics, campaign, or other tracking parameters.
Do not use it for parameters that change the HTML or otherwise alter the response in a meaningful way, such as product variants or content filters rendered on the server.
Yes. Today, the `params` form can tell caches to ignore all search parameters, but the spec is moving toward `params=()` instead. In either case, it should only be used when the response truly does not vary by query string.
Yes. Appending a throwaway query string to try to bypass cache may no longer work if the document uses No-Vary-Search and the cache has been told those parameters do not matter.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。