URLのクエリ差分を賢く扱う!No-Vary-Searchでキャッシュ命中率を爆上げする

ウェブサイトのパフォーマンスを改善する上で、キャッシュの活用は不可欠です。しかし、同じコンテンツを表示するページでも、URLに付与されたクエリパラメータ(例: ?utm_source=...
)の違いによって、ブラウザやCDNが別々のリソースとして扱ってしまう問題がありました。これにより、キャッシュが効かず、無駄なネットワークリクエストが発生していました。
この問題を解決するのが、新しいレスポンスヘッダーNo-Vary-Search
です。
本記事では、このNo-Vary-Search
ヘッダーを使って、URLのクエリ差分をブラウザに正しく伝え、キャッシュの命中率を劇的に向上させるための設定方法と注意点を解説します。
この記事でわかること
No-Vary-Search
がキャッシュの仕組みにどう影響するか- 無視すべきクエリと、考慮すべきクエリを明確にする正しい方法
- Node.jsやNginxでの具体的な設定例
Speculation Rules
やCDNとの連携における現実的な運用方針
1. No-Vary-Search
とは:キャッシュキーをシンプルにする魔法のヘッダー
No-Vary-Search
は、URLのクエリパラメータをブラウザがキャッシュキーとして使用する際のルールを定義するレスポンスヘッダーです。
このヘッダーを付与することで、サーバーは「このURLのクエリが〇〇であっても、〇〇でなくても、コンテンツは同一です」とブラウザに宣言できます。ブラウザは、この宣言に基づいて、すでにキャッシュされている同一コンテンツを再利用するため、ネットワークリクエストを省略できるのです。
No-Vary-Search
はまだIETFのドラフト段階の実験的な仕様ですが、Chromium系のブラウザではすでに利用可能です。未対応のブラウザでは、このヘッダーは単に無視されるため、パフォーマンスが低下することはありません。
No-Vary-Search
とVary
ヘッダーの比較
No-Vary-Search |
Vary |
|
---|---|---|
対象 | URLのクエリパラメータのみ | リクエストヘッダー(Cookie やUser-Agent など) |
役割 | 特定のクエリを無視するルールを宣言 | レスポンスが変化するヘッダーを宣言 |
2. 正しい設計指針:何を無視し、何を考慮するか
No-Vary-Search
の最も重要な設計ポイントは、どのクエリがコンテンツに影響を与えないかを明確にすることです。ここでは、正確なヘッダーの構文と設計指針を解説します。
ルール定義の基本構文
キーワード | 説明 | 設定例 |
---|---|---|
params |
指定したクエリパラメータだけを無視し、それ以外をキャッシュキーに含めます。 | params=("utm_source" "ref") |
params, except |
すべてのクエリを無視しますが、except で指定したクエリはキャッシュキーに含めます。 |
params, except=("id" "lang") |
key-order |
クエリの順序が違っても、同一コンテンツとして扱います。 | key-order |
【設計のヒント】
- 無視してよいクエリの例(
params
):utm_source
,ref
,gclid
,fbclid
などの計測系パラメータ。_
などのキャッシュ無効化用タイムスタンプ。
- 考慮すべきクエリの例(
params, except
):id
,q
(検索クエリ),lang
など、サーバーが返すコンテンツそのものが変わるパラメータ。
3. 実装例:No-Vary-Search
ヘッダーの付与
Node.js (Express)
Expressではres.set()
を使います。Structured Fieldsの構文に則り、値はスペース区切りで、文字列は二重引用符で囲む必要があります。
ケースA: 計測系だけ無視(その他は考慮)
app.get('/articles', (req, res) => {
// utm_source, ref, gclid, fbclidは無視し、その他のクエリはキャッシュキーに含める
res.set('No-Vary-Search', 'params=("utm_source" "ref" "gclid" "fbclid"), key-order');
// ...
res.send(renderArticlePage(req.query.id));
});
ケースB: 基本すべて無視、id
だけは別キャッシュ
app.get('/products', (req, res) => {
// id以外のクエリはすべて無視する
res.set('No-Vary-Search', 'params, except=("id")');
// ...
res.send(renderProductPage(req.query.id));
});
Nginx
Nginxでは、add_header
ディレクティブを使ってレスポンスにヘッダーを追加します。
server {
listen 80;
server_name example.com;
location /articles {
add_header No-Vary-Search 'params=("utm_source" "ref"), key-order' always;
# ...
proxy_pass http://localhost:3000;
}
}
【運用Tips】Nginxでの二重付与を避ける
もしバックエンドアプリケーション側でNo-Vary-Search
ヘッダーを付与している場合、Nginxのadd_header
を使うとヘッダーが重複します。このような場合は、proxy_hide_header No-Vary-Search;
で上流のヘッダーを消してから、Nginxで付与するのが安全です。
4. どのキャッシュに効くか?対応状況と連携
ブラウザのキャッシュ
No-Vary-Search
は、主にChromium系ブラウザのナビゲーションキャッシュに影響を与えます。
バージョン | 影響範囲 |
---|---|
Chrome 121–126 | prefetch 用のメモリキャッシュに適用されます。 |
Chrome 127以降 | prerender にも対応が拡張されました。 |
No-Vary-Search
はCache-Control: no-store
が指定されているレスポンスには影響を与えないため、Cache-Control
の設定も考慮して導入を検討しましょう。
CDNとの連携(現実的な運用方針)
多くのCDNはNo-Vary-Search
ヘッダーを直接解釈しません。そのため、「ブラウザ側はNo-Vary-Search
で制御し、CDN側は独自のキャッシュ設定で制御する」という二段構えの運用が現実的です。
CDNのダッシュボードや設定で、「特定のクエリをキャッシュキーから除外する」ルールを設定することで、CDNのキャッシュとブラウザのキャッシュの挙動を一致させることができます。
5. デバッグと落とし穴を避ける実践ノウハウ
デバッグ方法
- Chrome DevTools -> Networkタブ: キャッシュが効いているか確認するには、リロード時の
Size
列にfrom memory cache
やfrom disk cache
と表示されるかを確認します。レスポンスヘッダーにNo-Vary-Search
が正しく付与されているかも確認しましょう。 - クエリの正規化: ブラウザはクエリの比較に際して、
a=b
とa=%62
、a+b
とa%20b
を等価として扱います。デバッグ時には、これらの正規化ルールを考慮に入れると混乱を防げます。 - Application -> Speculative loads:
Speculation Rules
と連携している場合、このパネルでprefetch
やprerender
されたリソースが、クエリの違うURLで再利用されている状況を確認できます。
Sec-Purpose
の使い分け
prerender
やprefetch
による投機的読み込みのリクエストには、Sec-Purpose
ヘッダーが付与されます。
prefetch
のみ:Sec-Purpose: prefetch
prerender
時:Sec-Purpose: prefetch;prerender
サーバー側でこのヘッダーを検知し、ログを分離したり、不要なリソースへの投機的アクセスを制御したりすると、より安全に運用できます。
よくある落とし穴
- サーバーの嘘:
No-Vary-Search
の最大のリスクは、サーバーが「同一コンテンツ」と宣言したにも関わらず、実際は異なるコンテンツを返すことです。A/Bテストなどでクエリによってサーバーが返すHTMLが変わる場合は、導入は避けるべきです。 - ヘッダー構文の無効な組み合わせ:
params=("a"), except=("x")
のような組み合わせは仕様上無効です。この場合、ブラウザはNo-Vary-Search
を無視するため、ヘッダー効果が適用されず、既定のクエリ差分に戻ります。 - ヘッダー値の一貫性: 同一パスに対して異なる
No-Vary-Search
ヘッダーを返すと、ブラウザがキャッシュの再利用を諦めるケースがあります。304 Not Modified
など、どのステータスコードでも同じヘッダーを返すように設定しましょう。 - Service Workerとの関係:
No-Vary-Search
はブラウザ実装のキャッシュに効く仕組みです。Service WorkerのCache Storageは、アプリ側がキー設計を管理する別レイヤーなので、同様の正規化(例: UTM除去)は自前で行う必要があることを覚えておきましょう。
まとめ:No-Vary-Search
導入チェックリスト
要するに、No-Vary-Searchは「無視するクエリを宣言する」ヘッダーです。計測系はparams
で無視し、コンテンツを分けたい軸はparams, except
で残します。必要に応じてkey-order
を付けてCDNのキャッシュキー設定と揃え、同一パスでは常に同じ値を返します。導入は限定パスから段階的に行い、DevTools(Speculative loads)で再利用を確認し、Sec-Purpose
でログを分離します。no-store
やService WorkerのCache Storageは対象外である点と、A/Bテストや認証依存ページには使わない点だけ押さえておけば、キャッシュ命中率を安全に底上げできます。
- コンテンツに影響を与えない計測系クエリを
params
で定義する。 - コンテンツを変える重要なクエリがある場合は
params, except
で指定する。 - 導入は特定パスから段階的に適用し、副作用がないかを検証する。
- CDNを利用している場合、CDN側のキャッシュキー設定を
No-Vary-Search
の挙動と一致させる。 -
Speculation Rules
と組み合わせる場合は、expects_no_vary_search
ヒントを適切に設定する。
この記事が、あなたのウェブサイトのキャッシュ効率改善の一助となれば幸いです。ご覧いただきありがとうございました。