惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
雷峰网
雷峰网
Recent Announcements
Recent Announcements
月光博客
月光博客
G
Google Developers Blog
腾讯CDC
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
T
Tenable Blog
云风的 BLOG
云风的 BLOG
W
WeLiveSecurity
博客园 - 【当耐特】
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 聂微东
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
P
Privacy International News Feed
MyScale Blog
MyScale Blog
K
Kaspersky official blog
T
The Blog of Author Tim Ferriss
Attack and Defense Labs
Attack and Defense Labs
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
aimingoo的专栏
aimingoo的专栏
I
Intezer
Vercel News
Vercel News
小众软件
小众软件
Simon Willison's Weblog
Simon Willison's Weblog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
Latest news
Latest news
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
S
Security Affairs
P
Proofpoint News Feed
博客园 - 三生石上(FineUI控件)
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
美团技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
Security @ Cisco Blogs
L
LINUX DO - 热门话题
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
D
Docker
L
Lohrmann on Cybersecurity
F
Full Disclosure

CSS Wizardry

Front-End’s Missing Metric: The TBT Window Meet Your Users Where They Are with Obs.js font-family Doesn’t Fall Back the Way You Think What Is CSS Containment and How Can I Use It? When All You Can Do Is All or Nothing, Do Nothing Obs.js: Context-Aware Web Performance for Everyone Low- and Mid-Tier Mobile for the Real World (2025) The Fastest Site in the Tour de France Making Sense of the Performance Extensibility API Why Do We Have a Cache-Control Request Header? HTML Is Not a Programming Language… Build for the Web, Build on the Web, Build with the Web Licensing Code on CSS Wizardry A Layered Approach to Speculation Rules Designing (and Evolving) a New Web Performance Score Core Web Vitals Colours The Ultimate Contract Templates for Tech Consultants: Protect Your Business and Get Paid Optimising for High Latency Environments Cache Grab: How Much Are You Leaving on the Table? blocking=render: Why would you do that?! Correctly Configure (Pre) Connections The Three Cs: 🤝 Concatenate, 🗜️ Compress, 🗳️ Cache What Is the Maximum max-age? How to Clear Cache and Cookies on a Customer’s Device The Ultimate Low-Quality Image Placeholder Technique Core Web Vitals for Search Engine Optimisation: What Do We Need to Know? The HTTP/1-liness of HTTP/2 In Defence of DOM­Content­Loaded Site-Speed Topography Remapped Why Not document.write()? Speeding Up Async Snippets Critical CSS? Not So Fast! Measure What You Impact, Not What You Influence Optimising Largest Contentful Paint Measuring Web Performance in Mobile Safari Site-Speed Topography Speed Up Google Fonts Real-World Effectiveness of Brotli Performance Budgets, Pragmatically Lazy Pre-Browsing with Prefetch Making Cloud.typography Fast(er) Time to First Byte: What It Is and How to Improve It Self-Host Your Static Assets Tips for Technical Interviews Cache-Control for Civilians Bandwidth or Latency: When to Optimise for Which ITCSS × Skillshare What If? CSS and Network Performance The Three Types of Performance Testing Getting to Know a Legacy Codebase Image Inconsistencies: How and When Browsers Download Images Identifying, Auditing, and Discussing Third Parties My Digital Music Setup Measuring the Hard-to-Measure Finding Dead CSS The Fallacies of Distributed Computing (Applied to Front-End Performance) Ten Years Old Relative Requirements Airplanes and Ashtrays Performance and Resilience: Stress-Testing Third Parties Refactoring Tunnels Little Things I Like to Do with Git Writing Tidy Code Configuring Git and Vim Base64 Encoding & Performance, Part 2: Gathering Data Base64 Encoding & Performance, Part 1: What’s Up with Base64? Code Smells in CSS Revisited Typography for Developers Moving CSS Wizardry onto HTTPS and HTTP/2 Ack for CSS Developers A New Year, a New Focus Preparing Vim for Apple’s Touch Bar Choosing the Correct Average CSS Shorthand Syntax Considered an Anti-Pattern CSS Wizardry Newsletter Nesting Your BEM? Improving Perceived Performance with Multiple Background Images Continue Normalising Your CSS Pure CSS Content Filter Pragmatic, Practical, and Progressive Theming with Custom Properties Refactoring CSS: The Three I’s Speaker’s Checklist: Before and After Your Talk Improving Your CSS with Parker The Importance of !important: Forcing Immutability in CSS Mixins Better for Performance Managing Typography on Large Apps White October Events Workshop Partnership BEMIT: Taking the BEM Naming Convention a Step Further Travelling Like You Want to, When You Have To Contextual Styling: UI Components, Nesting, and Implementation Detail Subtleties with Self-Chained Classes Cyclomatic Complexity: Logic in CSS Immutable CSS Can CSS Be Too Modular? More Transparent UI Code with Namespaces When to use @extend; when to use a mixin The Specificity Graph CSS Wizardry Ltd.: Year 1 in review CSS Guidelines 2.0.0
Better Browser Caching with No-Vary-Search
Harry Roberts · 2026-05-08 · via CSS Wizardry

(last updated on )

Written by on CSS Wizardry.

Table of Contents

Independent writing is brought to you via my wonderful Supporters.

  1. The Problem We’re Solving
  2. What No-Vary-Search Does
  3. A Simple Example
  4. No-Vary-Search Syntaxes
    1. Ignore Specific Parameters
    2. Ignore All Parameters
    3. Ignore Everything Except Certain Parameters
    4. Ignore Parameter Order
    5. Combining No-Vary-Search Rules
  5. A Small Syntax Gotcha
  6. A Small Debugging Gotcha
  7. Unknown Params Should Bust the Cache
  8. Use It Carefully
  9. A Nice Fit for Messy Real-World URLs

I’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=blue

And sometimes, it really doesn’t matter at all:

  • ?utm_source=google
  • ?utm_source=chatgpt

The 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.

The Problem We’re Solving

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=newsletter

At 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.

What No-Vary-Search Does

No-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 Accept-Language or this response depends on Accept-Encoding, for example. No-Vary-Search says this response does not depend on utm_source or the 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.

A Simple Example

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=linkedin

If 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 Syntaxes

The header has a few useful, different forms.

Ignore Specific Parameters

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.

Ignore All Parameters

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.

Ignore Everything Except Certain Parameters

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,
  • client-side sorting/filtering or tracking parameters do not matter (remember, the HTML itself doesn’t change) but,
  • product variants (?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=price

could share a cache entry, but:

  • /products/shoes?colour=red
  • /products/shoes?colour=blue

should not.

Ignore Parameter Order

Sometimes the parameters themselves matter, but their order does not:

  • /search?q=shoes&sort=price
  • /search?sort=price&q=shoes

These 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.

Combining No-Vary-Search Rules

You can combine directives:

No-Vary-Search: key-order, params, except=("colour" "size")

That tells the cache:

  • parameter order does not matter, and
  • all parameters may be ignored except 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=red

That is probably the most expressive form, and in real systems it may prove the most useful. This is phenomenally powerful.

A Small Syntax Gotcha

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.

A Small Debugging Gotcha

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
  • /?asdf

Usually, 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.

Unknown Params Should Bust the Cache

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-b

If 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=canvas

If 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.

Use It Carefully

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:

  • purely analytical;
  • purely presentational on the client side, or;
  • otherwise irrelevant to the server-rendered response.

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.

A Nice Fit for Messy Real-World URLs

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!


Frequently Asked Questions

What is No-Vary-Search?

No-Vary-Search is an HTTP response header that tells caches which URL search parameters can be ignored when matching requests to cached responses.

What problem does No-Vary-Search solve?

It reduces cache fragmentation caused by irrelevant query parameters such as UTM tags, so materially identical URLs can reuse the same cached response.

When should I use No-Vary-Search?

Use it when some query parameters do not meaningfully change the response body, such as analytics, campaign, or other tracking parameters.

When should I not use No-Vary-Search?

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.

Can No-Vary-Search ignore all query parameters?

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.

Does No-Vary-Search affect debugging?

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.