mcpField
N
netlify-caching

netlify-caching

github.com/netlifyクラウド・DevOps473.1k 回インストール6 日前に更新
リポジトリ

netlify-caching について

netlify-caching は、Netlify 開発者向けに動的レスポンスや個別コンポーネントのキャッシュ設定を説明するスキルです。CDN ヘッダー、キーの変化、無効化、Cache API、デバッグを扱います。

主な機能

  1. 01CDN キャッシュヘッダーの設定
  2. 02リクエスト別のキャッシュキー設定
  3. 03タグによるキャッシュ削除
  4. 04Cache API による読み書き
  5. 05状態確認と再検証

活用シーン

  1. 01Functions の動的レスポンスのキャッシュ
  2. 02地域、Cookie、クエリ別のコンテンツ配信
  3. 03更新後のタグ付きキャッシュ削除

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/netlify/context-and-tools --skill netlify-caching

インストール後、Claude Code で/netlify-cachingを入力して読み込まれたか確認します。

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

Netlify のキャッシュ

まず確認したい Cache-Control ヘッダー

動的レスポンス(Functions、Edge Functions、プロキシ)は、デフォルトではキャッシュされません。明示的に有効にする必要があります。レスポンスに Netlify-CDN-Cache-Control を設定してください。

import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  return new Response("Hello world", {
    headers: {
      'Netlify-CDN-Cache-Control': 'public, durable, max-age=60, stale-while-revalidate=120'
    }
  });
};

ヘッダーの選択(より具体的なものが優先されます。CDN-Cache-Control/Cache-Control は常に下流に渡されます):

  • Netlify-CDN-Cache-Control — Netlify CDN のみ。こちらを使ってください。
  • CDN-Cache-Control — 対応するすべての CDN。
  • Cache-Control — 任意の CDN またはブラウザー。

避けるべき従来の方法: On-demand Builders はこれらのヘッダーや Netlify-Vary に対応していません。TTL パターンを使用し、URL パスのみをキーとします。新しいコードでは ODB を使わないでください。

注意点(最初にお読みください)

  • キャッシュされるのは GET のみです。 POST/PUT などはヘッダーにかかわらずキャッシュされません。キャッシュ可能なデータは GET ルートで公開してください(入力は URL またはクエリー文字列に含めます)。
  • netlify dev では CDN キャッシュはエミュレートされません。 毎回ローカルでキャッシュミスが発生するのは想定どおりです。デプロイ済み URL(Deploy Preview または本番環境)で Cache-Status ヘッダーを使ってキャッシュを確認してください。
  • Netlify-Vary: query=... を指定しない場合、クエリー文字列全体がキャッシュキーになります。 それぞれ異なるクエリー文字列(utm_*、fbclid など)は別々のキャッシュエントリーになります。レスポンスに影響するパラメーターのみを列挙してください。
  • 静的アセットは最長で 1 年間、新しい状態として扱われます。 それより短い max-age は無視されます。静的アセットが更新されるのは、新しいデプロイまたは手動でのパージ時のみです。
  • いずれかのページで basic-auth を使用すると、サイト全体でキャッシュが無効になります。
  • durable はサーバーレス環境のみで使用できます。 Edge Function のレスポンスには効果がありません。
  • 機密性の高いコンテンツを自動無効化の対象外にしないでください。デプロイやファイアウォールの変更後も、公開キャッシュに残る可能性があります。

ディレクティブ

  • public はキャッシュ対象、private はブラウザーのみ(Netlify の共有キャッシュには保存されません)、no-store はキャッシュしません。
  • s-maxage=N は Netlify の共有キャッシュでの秒数です(共有キャッシュでは max-age より優先されます)。
  • max-age=N は任意のキャッシュでの秒数です。
  • stale-while-revalidate=N は、期限切れ後の N 秒間、バックグラウンドで再検証しながら古いデータを配信します。
  • durable(サーバーレス環境のみ)は、Netlify の durable キャッシュに保存し、他のエッジノードで再利用できるようにします。これにより、関数が再度呼び出されるのを防ぎます。

ヘッダーが設定されていない場合のデフォルト値 — 静的:Netlify-CDN-Cache-Control: public, s-maxage=31536000, must-revalidate、動的:Cache-Control: public, max-age=0, must-revalidate。

キャッシュキーのバリエーション — Netlify-Vary

レスポンスにカンマ区切りの指示を指定します。値のリストはパイプ区切りです。

Netlify-Vary: query=item_id|page, country=es+de|us, cookie=ab_test|is_logged_in
  • query=a|b は対象のパラメーターを指定し、query のみの場合はすべてのパラメーターを対象にします。キーでは大文字と小文字が区別されます。パラメーターの順序は関係ありません。
  • header=Device-Type|App-Version — カスタムヘッダーおよびほとんどの標準ヘッダー。
  • language=en|es+pt — + でグループ化します。品質値を考慮して Accept-Language と照合されます。
  • country=us|es+pt — GeoIP を使用します。ISO 3166-1 の 2 文字コードを指定し、+ でグループ化します。
  • cookie=ab_test|is_logged_in — Cookie ヘッダー全体ではなく、対象のキーを指定します。

次のヘッダーではバリエーションを指定できません: Accept*、Cache-Control、Connection、Content-Length、Cookie、Host、If-*、Range、Referer、Upgrade、User-Agent。言語/Cookie/形式には、Vary: Accept-Language/Vary: Cookie または対応する Netlify-Vary の指示を使用してください。

一貫性のルール: URL へのすべてのレスポンスで、同じ Netlify-Vary を返す必要があります。最初にキャッシュされたレスポンスの指示が優先され、それ以降の指示は無視されます。Netlify-Vary と標準の Vary は両方とも尊重されます(形式/エンコーディングには Vary を使用し、Cloudflare などの上流 CDN に指示を渡す場合にも使用してください)。

キャッシュタグとオプトアウト

タグを付けて、タグ指定のパージができるようにします。

Netlify-Cache-Tag: tag1,tag2,tag3
  • Netlify-Cache-Tag(Netlify CDN)は、下流に渡される Cache-Tag より優先されます。一部のプロバイダーは Cache-Tag を削除するため、そうしたプロバイダー経由でプロキシする場合は両方を設定してください。
  • 制約:大文字と小文字を区別しません。UTF-8 のみ。タグあたり最大1024文字、レスポンスあたり最大500タグです。

Netlify-Cache-ID(カンマ区切り)を使うと、レスポンスを自動的なアトミックデプロイの無効化対象から除外できます(パージ用のキャッシュタグとして自動登録されます。IDの上限は別途500個です)。

Netlify-Cache-ID: cms-proxy,product,image

オプトアウトした後は、関連する変更(例:Netlify-Cache-ID が付いたリダイレクト/プロキシや関数の変更)の後に、オンデマンドでパージしてください。

オンデマンドの無効化(パージ)

デプロイ済み関数から purgeCache を使ってパージします(サイト ID は自動的に渡されます)。

import { purgeCache } from "@netlify/functions";

export default async () => {
  await purgeCache(); // no args = purge everything for the site
  return new Response("Purged!", { status: 202 });
};

タグを指定してパージし、必要に応じてデプロイ/サブドメインを対象に指定します。

import { purgeCache } from "@netlify/functions";

export default async (req: Request) => {
  const cacheTag = new URL(req.url).searchParams.get("tag");
  if (!cacheTag) return;
  await purgeCache({
    tags: [cacheTag],
    deployAlias: "deploy-preview-11",
    domain: "early-access.company.com",
  });
  return new Response("Purged!", { status: 202 });
};

環境から自動的に取得される認証情報が使えるのは、デプロイ済み関数内のみです。 CI、ローカルスクリプト、またはビルドから実行する場合は、token(環境変数から読み取った個人アクセストークン。決してハードコードしないでください)と siteID を渡してください。

Lambda互換関数では、従来の module.exports.handler = async (event, context) => {…} シグネチャを使用し、context.clientContext.custom.purge_api_token を渡す必要があります。

import { purgeCache } from "@netlify/functions";

module.exports.handler = async (event, context) => {
  const token = context.clientContext.custom.purge_api_token;
  await purgeCache({ tags: ["tag1", "tag2"], token });
  return { body: "Purged!", statusCode: 202 };
};

直接 API を呼び出す場合(関数外から)は、Authorization: Bearer <personal_access_token> と Content-Type: application/json を指定して POST https://api.netlify.com/api/v1/purge を実行します。

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <personal_access_token>" \
  --data '{"site_slug": "mysitename", "cache_tags": ["news"], "deploy_alias": "deploy-preview-11", "domain": "early-access.company.com"}' \
  'https://api.netlify.com/api/v1/purge'
  • サイト指定のパージ:site_id または site_slug。タグ指定のパージ:cache_tags とサイト。cache_tags を省略するとサイト全体がパージされます。cache_tags が空のリストの場合は、何もパージされません。
  • 識別子の対応関係:UI(Project configuration > General > Project details)では、Project ID = site_id、Project name = site_slug です。https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api#get-site を参照してください。
  • レート制限: 各タグまたはサイトをパージできるのは5秒あたり2回までです。超過すると 429 が返されます。

Cache API(caches グローバル)

関数/エッジ関数から HTTP レスポンスをプログラムで読み書きできます。ヘッダーによるルートキャッシュと併用して、ルートの個別コンポーネントや任意のフェッチをキャッシュできます。

スコープのルール: caches.open() はどこでも使用できますが、match/put/delete はリクエストハンドラー内でのみ使用できます。モジュール/グローバルスコープで実行すると例外が発生します。

import type { Config, Context } from "@netlify/functions";

const cache = await caches.open("my-cache"); // ok in global scope

export default async (req: Request, context: Context) => {
  const request = new Request("https://example.com/expensive-api");
  const cached = await cache.match(request);
  if (cached) return cached;

  const fresh = await fetch(request);
  if (fresh.ok) {
    cache.put(request, fresh.clone()).catch((error) => {
      console.error("Failed to add to the cache:", error);
    });
  }
  return fresh;
};

export const config: Config = { path: "/cache-api-example" };

CacheStorage のサブセット:

  • caches.match(request) → 任意のキャッシュから Response を返します。該当するものがない場合は undefined を返します。
  • caches.open(name) → Cache を返します。名前を分けるとキャッシュが分散し、ヒット率が下がります。意味のある名前を少数使ってください。

Cache のメソッド(すべて caches.open() が必要です):

  • cache.match(request) → Response | undefined。
  • cache.put(request, response) → レスポンスを追加します。
  • cache.add(request)/cache.addAll(requests) → フェッチして保存します。
  • cache.delete(request) → true。
  • keys() は実装されていません。内容を一覧表示する方法はありません。

一貫性:読み取り/書き込みは強整合性です。削除は結果整合性です(削除したエントリーが短時間返されることがあります)。

キャッシュできないもの: 部分レスポンス(206)、Vary: *、GET 以外のメソッド。レスポンスには max-age/s-maxage が1秒以上で、public(private/no-cache/no-store ではない)を指定したキャッシュ制御ヘッダーと、2xx のステータスが必要です。満たさない場合、保存時にエラーが発生します。制御できないレスポンスには、fetchWithCache を使ってヘッダーを書き換えてください。

呼び出しあたりの上限: 参照100回、挿入/削除20回です。超過した場合、それ以降の参照では何も返されず、書き込み/削除も何も行われません。上限はリクエスト内のエッジ関数間で共有されますが、サーバーレス関数とエッジ関数の間では共有されません。キャッシュデータはリージョンごとに保存され(複製されません)、再デプロイ時および max-age/s-maxage の期限切れ時に自動で無効化されます。

@netlify/cache モジュール

インストールすると、ヘルパー、時間定数(MINUTE/HOUR/DAY)、ローカル開発用の caches エクスポートを利用できます。

npm install @netlify/cache

ローカル開発時の回避策: caches グローバルは Node.js の一部ではありません。Netlify は Functions/Edge ランタイム(本番環境と netlify dev の実行時)でこれを提供しますが、フレームワーク独自の開発サーバーを実行するとグローバルが未定義となり、エラーが発生します。その場合は、代わりにインポートしてください。

import { caches } from "@netlify/cache";
const cache = await caches.open("my-cache");

Netlify CLI 20.0.3 以降が必要です。ローカルでは何も永続化されず、検索結果は返されず、書き込みや削除による変更もありません。グローバルを使う場合と機能上の違いはありません。

cacheHeaders(settings) → ヘッダーオブジェクト
import { cacheHeaders, DAY } from "@netlify/cache";

const headers = {
  "x-custom-header": "some value",
  ...cacheHeaders({
    ttl: 2 * DAY,          // s-maxage
    swr: HOUR,             // stale-while-revalidate
    durable: true,
    tags: ["product", "sale"],
    overrideDeployRevalidation: ["tag"], // opt out of atomic-deploy invalidation
    vary: {
      cookie: ["ab_test_name", "ab_test_bucket"],
      query: ["item_id", "page"], // or true for all
      country: ["us", ["es", "pt"]], // nested = OR
      language: ["en"],
      header: ["Device-Type"],
    },
  }),
};

汎用(Netlify 以外)のヘッダーのみが必要な場合は、代わりに cdn-cache-control npm モジュールを使用してください。

fetchWithCache(resource, options?, cacheSettings?)

キャッシュ済みのレスポンスを返すか、取得して保存したうえで返す、fetch のドロップイン置き換えです。cacheSettings は、競合するレスポンスヘッダーを上書きします。swr を指定すると、バックグラウンドでの再検証が自動的に処理されます。

import { fetchWithCache, DAY } from "@netlify/cache";

const response = await fetchWithCache("https://example.com/expensive-api", {
  ttl: 2 * DAY,
  tags: ["product", "sale"],
  vary: { cookie: ["ab_test_name"], query: ["item_id", "page"] },
});
getCacheStatus(response | headers | headerString)

{ hit, caches: { durable: { hit, stale, stored, ttl }, edge: { hit, stale } } } を返します。

const { hit, edge, durable } = getCacheStatus(response);
needsRevalidation(response) → boolean

cache.match/cache.put を直接呼び出す場合にのみ必要です(fetchWithCache と swr の組み合わせでは不要です)。Cache API のレスポンスが SWR ウィンドウ内で古くなっている場合に true を返します。そのレスポンスを返した後、context.waitUntil で再検証し、新しいコピーを cache.put してください。

if (cached) {
  if (needsRevalidation(cached)) {
    context.waitUntil(
      fetch(request).then((fresh) => {
        const response = new Response(fresh.body, {
          headers: { ...Object.fromEntries(fresh.headers), ...cacheHeaders({ ttl: MINUTE, swr: HOUR }) },
        });
        return cache.put(request, response);
      })
    );
  }
  return cached;
}

耐久キャッシュ

durable(サーバーレスのみ)を追加すると、ローカルコピーのないエッジノードは関数を呼び出す前に共有耐久キャッシュを確認します。これにより、呼び出し回数が減り、キャッシュミス時のレイテンシが改善します。整合性は最終的に保証されるため、バージョンごとに複数のリージョンから関数が数回呼び出される場合があります。サイトの関数が稼働するリージョンと同じ場所に配置されます。Netlify-Vary、SWR、オンデマンド無効化に対応しています。Next.js: Next Runtime 5.5.0 以降では、耐久キャッシュが自動的に使用されます。

Cache-Status によるデバッグ

Netlify はすべてのレスポンスに Cache-Status(RFC 9211)を設定します。デプロイ済みの URL で確認してください。"Netlify Edge" または "Netlify Durable" で始まる値を探します。

  • "Netlify Edge"; fwd=miss — キャッシュに何もありません。
  • "Netlify Edge"; hit — キャッシュから配信されました。
  • "Netlify Edge"; hit; fwd=stale — 再検証中に古いデータが配信されました(SWR)。
  • ミス時に耐久キャッシュへ保存された場合:"Netlify Durable"; fwd=uri-miss; stored=true; ttl=3600。
  • 耐久キャッシュにヒットした場合:"Netlify Durable"; hit; ttl=1234。

ttl が負の場合は、有効期限が切れてからの秒数です。リクエストごとに異なるキャッシュインスタンスにヒットすることがあります。本番トラフィックがない場合や durable を指定していない場合は、ヒットするまで空のキャッシュがいくつか続くことを想定してください。同じインスタンスを温めるには、リクエストを繰り返します。

Netlify のキャッシュに関するルール

これはドキュメント上の事実ではなく、組織内の慣例です。ctx-gen によってレンダリング済みのスキルに統合され、生成されることはありません。スキルのメンテナーが管理しています。

  1. CDN がキャッシュするのは GET レスポンスのみです。ヘッダーに関係なく、POST/PUT などはキャッシュされません。キャッシュ可能なデータは GET ルートで公開してください(入力は URL またはクエリ文字列に含めてください)。
  2. Netlify-Vary: query=... がない場合、クエリ文字列全体がキャッシュキーになります。異なるクエリ文字列(utm_*、fbclid など)ごとに、別々のキャッシュエントリが作成されます。レスポンスを実際に変化させるパラメーターのみを指定してください。
  3. netlify dev は CDN キャッシュをエミュレートしません。ローカルで毎回キャッシュミスになるのは想定どおりであり、バグではありません。デプロイ済み URL(Deploy Preview または本番環境)の Cache-Status ヘッダーで、キャッシュの動作を確認してください。
  4. purgeCache() が暗黙の認証情報を利用できるのは、デプロイ済みの関数内のみです。CI、ローカルスクリプト、またはビルドから実行する場合は、token(環境変数から読み込む個人用アクセストークン。ハードコードは禁止)と siteID を渡してください。

コメント