by MintJams

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

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-SearchVaryヘッダーの比較

No-Vary-Search Vary
対象 URLのクエリパラメータのみ リクエストヘッダーCookieUser-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-SearchCache-Control: no-storeが指定されているレスポンスには影響を与えないため、Cache-Controlの設定も考慮して導入を検討しましょう。

CDNとの連携(現実的な運用方針)

多くのCDNはNo-Vary-Searchヘッダーを直接解釈しません。そのため、「ブラウザ側はNo-Vary-Searchで制御し、CDN側は独自のキャッシュ設定で制御する」という二段構えの運用が現実的です。

CDNのダッシュボードや設定で、「特定のクエリをキャッシュキーから除外する」ルールを設定することで、CDNのキャッシュとブラウザのキャッシュの挙動を一致させることができます。


5. デバッグと落とし穴を避ける実践ノウハウ

デバッグ方法

  • Chrome DevTools -> Networkタブ: キャッシュが効いているか確認するには、リロード時のSize列にfrom memory cachefrom disk cacheと表示されるかを確認します。レスポンスヘッダーにNo-Vary-Searchが正しく付与されているかも確認しましょう。
  • クエリの正規化: ブラウザはクエリの比較に際して、a=ba=%62a+ba%20bを等価として扱います。デバッグ時には、これらの正規化ルールを考慮に入れると混乱を防げます。
  • Application -> Speculative loads: Speculation Rulesと連携している場合、このパネルでprefetchprerenderされたリソースが、クエリの違うURLで再利用されている状況を確認できます。

Sec-Purposeの使い分け

prerenderprefetchによる投機的読み込みのリクエストには、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-storeService WorkerのCache Storageは対象外である点と、A/Bテストや認証依存ページには使わない点だけ押さえておけば、キャッシュ命中率を安全に底上げできます。

  • コンテンツに影響を与えない計測系クエリparamsで定義する。
  • コンテンツを変える重要なクエリがある場合はparams, exceptで指定する。
  • 導入は特定パスから段階的に適用し、副作用がないかを検証する。
  • CDNを利用している場合、CDN側のキャッシュキー設定No-Vary-Searchの挙動と一致させる。
  • Speculation Rulesと組み合わせる場合は、expects_no_vary_searchヒントを適切に設定する。

この記事が、あなたのウェブサイトのキャッシュ効率改善の一助となれば幸いです。ご覧いただきありがとうございました。