ルートキャッシュ

このページ

Astro のルートキャッシュは、オンデマンドページのレンダリング済みレスポンスを保存するため、キャッシュヒット時はレンダリングが省略されます。EmDash は、ページがレンダリングするコンテンツ用のキャッシュタグを提供し、コンテンツが変更されたときにそれらのタグを無効化します。ページを数日間キャッシュしたままにしても、公開した内容は通常数秒以内に反映されます。

オブジェクトキャッシュはクエリ結果を保存するもので、すべてのページのレンダリングは依然として行われます。キャッシュプロバイダーを設定すると、匿名ユーザー向けのレンダリングはオブジェクトキャッシュを使わずにデータベースから読み取るため、無効化されたページを再度埋めるレンダリングが古いスナップショットから行われることはありません(キャッシュされるものを参照)。その上で、匿名の訪問者にはルートキャッシュが応答し、オブジェクトキャッシュは主にサインイン済みの編集者向けレンダリングを高速化します。

キャッシュプロバイダーを選ぶ

ルートキャッシュにはキャッシュプロバイダーが必要です。デプロイ先に合ったものを使用してください。

デプロイ先プロバイダーインポート元
Node.jsmemoryCache()astro/config
CloudflarecacheCloudflare()@astrojs/cloudflare/cache

memoryCache() は、サーバープロセスのメモリにエントリを保持します。cacheCloudflare() は CDN プロバイダーで、レスポンスを Cloudflare Workers Cache に保存し、キャッシュヒット時には Worker が実行されません。Astro の Netlify と Vercel アダプターにも CDN プロバイダーが付属しています。アダプターのキャッシュプロバイダーを参照してください。EmDash は、プロバイダーに依存しない Astro の cache.invalidate() API を通じて無効化するため、そのタグはどのプロバイダーでも機能します。プロバイダーによって異なる動作については、プロバイダーごとの違いで説明します。astro dev はキャッシュを一切行わないため、キャッシュの動作は本番ビルドでテストしてください。

ルートキャッシュを設定する

プロバイダーを登録し、どのルートをキャッシュ可能にするか、その期間、どのタグを付けるかを宣言します。この例では Cloudflare プロバイダーを使用しています。Node では、astro/config から memoryCache をインポートして cache: { provider: memoryCache() } を使用してください。

import { defineConfig } from "astro/config";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
	// ...既存の output、アダプター、インテグレーション
	cache: {
		provider: cacheCloudflare(),
	},
	routeRules: {
		"/": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/blog": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/blog/[slug]": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/sitemap.xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/sitemap-[collection].xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
	},
});
  • maxAge は、ルートが再びレンダリングされるまでキャッシュがエントリを提供する期間です。EmDash は公開時に古いエントリを無効化します(後述)が、無効化が失敗することもあるため、maxAge はページが古いままでいられる期間の上限にもなります。これに加えて、swr が再検証する間に古いレスポンスが 1 回返されます。長い値を選ぶ前に、プロバイダーごとの違いを確認してください。
  • swr を指定すると、キャッシュはバックグラウンドで再検証しながら古いエントリを提供し続けるため、ページが一度キャッシュされれば、訪問者がレンダリングを待つことはほとんどありません。
  • tags は、無効化のためにエントリへ名前を付けます。各ルートには、そのルートがレンダリングするコレクションのタグをコレクション名で付けてください。EmDash の組み込みの無効化はコレクション名を使用するため、一致するタグは自動的に無効化されます。キャッシュされるすべてのルートに広範な追加タグ(ここでは all。コレクション名ではないため、EmDash が自動で無効化することはありません)を付けておくと、緊急用のスイッチとして機能します。たとえば、ご自身で保護したエンドポイント内で context.cache.invalidate({ tags: ["all"] }) を呼び出してこのタグを無効化すれば、すべてを一度にクリアできます。

クエリヘルパーは cacheHint も返します。これをページ内で Astro.cache.set() に渡せます。そのタグは、特定のエントリ(getEmDashEntry)、コレクション(getEmDashCollection)、またはページ周辺のリソース(getMenuWithCacheHint、getSiteSettingsWithCacheHint、getTaxonomyTermsWithCacheHint、getWidgetAreaWithCacheHint)を指します。これらは EmDash が書き込み時に無効化するタグと同じものなので、ヒントを渡すページは、レンダリングしているエントリやページ周辺のリソースが書き込まれたときに、ルートルールに名前がないものも含めて無効化されます。

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
if (!slug) return Astro.rewrite("/404");

const { entry, cacheHint } = await getEmDashEntry("posts", slug);
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

Astro.cache.set() はルートルールとマージされ、タグは複数回の呼び出しにわたって蓄積されます。キャッシュヒントはタグを持ちますが maxAge は持たないため、ページをどれだけキャッシュするかはルートルールが決めます。

すべての公開ルートに明示的なルールを設定してください。ルールはリクエストパスに一致するため、公開ルートは個別に列挙します。サイトルートの /[...slug] のようなレストパラメーターは、/_emdash/ 配下の管理画面や API ルートにも一致してしまいます。

公開時の無効化は組み込み済み

公開中のコンテンツが変更されるたびに、EmDash のコンテンツ API ルートはルートキャッシュを無効化します。エントリの公開、非公開化、削除、ゴミ箱からの復元、および公開中のエントリに反映される変更の保存によって、タグ [collection, id] が無効化されます。エントリの作成や複製では何も無効化されません。新しいエントリは下書きだからです。したがって、(上記のように)コレクション名でタグ付けされたルートルールは、maxAge が長くても、通常は公開から数秒以内に更新されます。

サイトの周辺要素も対象です。サイト設定を保存すると emdash:settings が無効化され、メニューの編集では emdash:menu:{name} が、タクソノミーとウィジェットエリアの書き込みでは、対応する emdash:taxonomy:{name} / emdash:widget-area:{name} タグが無効化されます。ページが getMenuWithCacheHint()、getSiteSettingsWithCacheHint()、getTaxonomyTermsWithCacheHint()、または getWidgetAreaWithCacheHint() の cacheHint を使用していれば、これらのタグは自動的に付与されます。

メニューのタグが対象とするのはメニューの編集のみです。メニューがリンクしているエントリの公開、非公開化、ゴミ箱への移動、復元や、リンク先のエントリまたはタクソノミー用語のスラッグの変更では、メニューのタグは無効化されません。メニューを表示しているページがそうした変更を反映するのは、そのページのタグにそのエントリ、そのコレクション、またはその用語のタクソノミーも含まれている場合で、そうでなければページの有効期限が切れたときです。

リダイレクトを作成、編集、削除すると、そのリダイレクト元パスのキャッシュ済みページが(末尾スラッシュありとなしの両方で)無効化され、次のリクエストからリダイレクトが適用されます。

過去のリビジョンの復元や、バイライン、メディアライブラリ、コメントの変更では、何も無効化されません。それらの影響を受けるキャッシュ済みページは、有効期限が切れるか、それをレンダリングするエントリが再度保存されたときに更新されます。スケジュール公開が公開済みエントリのタグを無効化するのは Cloudflare のみです。プロバイダーごとの違いを参照してください。

これが、タグの規則が重要である理由でもあります。EmDash が無効化するのは、コレクション名、エントリ ID、および emdash:* の周辺タグです。ルートルールで別のタグ名を使うと、組み込みの無効化は一致せず、古いエントリは maxAge が切れるまで残ります。

レスポンスをキャッシュに入れない

ルートルールはリクエストパスに一致するため、長い TTL でキャッシュしたくないレスポンスにも適用されます。EmDash は、サインイン済みユーザー向けにレンダリングされたレスポンス、Cache-Control: private または no-store 付きで送信されたレスポンス、そして 404 レスポンスを、自動的にルートキャッシュの対象外にします。その他のエラーレスポンスや独自のミドルウェアによるリダイレクトはテンプレートの役割で、リクエストごとに Astro.cache.set(false) で除外します。

Astro.cache.set(false) の効果は、次に Astro.cache.set() が呼ばれるまでです。レイアウトはページのフロントマターの後にレンダリングされるため、キャッシュヒントを設定するレイアウトは、除外したページのキャッシュを再び有効にしてしまいます。除外の指定をレイアウトに(たとえば prop として)渡し、それが設定されている場合はレイアウト内の Astro.cache.set() 呼び出しをスキップしてください。

プレビューと編集リンクは自動的に処理される

EmDash のミドルウェアは、次のレスポンスを自らルートキャッシュから除外するため、テンプレートコードは不要です。?_preview= トークン付きのリクエスト(有効かどうかを問いません。これらの URL はトークンごとに異なり、そうしないとトークンの期限切れ後もキャッシュされた下書きにアクセスできてしまうためです)、およびエディターツールバーが挿入されたレスポンスです。toolbar: "client" を使用している場合は、?_edit URL にも同じ扱いが適用されます。有効なプレビューレスポンスとツールバーが挿入されたレスポンスには、ブラウザーや下流の CDN 向けにさらに Cache-Control: private, no-store が付与されます。

エラーレスポンス

EmDash はすべての 404 をルートキャッシュに入れないため、存在しなかったエントリを後で公開すると、すぐに表示されます。キャッシュ対象ルートでレンダリングされた 500 などのその他のエラーレスポンスは、ルールの TTL を引き継ぎます。それらは Astro.cache.set(false) で除外してください。

ミドルウェアでのリダイレクト

リダイレクトで処理を打ち切るミドルウェア(たとえばホスト名の正規化)は、context.cache?.set(false) で除外する必要があります。そうしないと、そのリダイレクトがルートルールの TTL でキャッシュされ、ホスト名ごとにキャッシュエントリを分けない CDN プロバイダーでは、正規ホスト名へのリクエストにも返されて、即座にリダイレクトループが発生する可能性があります。オプショナルチェーンを使用してください。どのルートにも一致しない URL の 404 ページなど、一部のレスポンスでは context.cache が undefined になります。

編集者とキャッシュ済みページ

ルートキャッシュは URL ごとに 1 つの共有バリアントを保存し、そのキャッシュキーは Cookie を無視するため、キャッシュヒットが編集者のセッションを反映することはありません。デフォルトのサーバーレンダリングされるツールバーでは、公開サイトを閲覧中の編集者には通常、ツールバーなしのキャッシュ済み匿名バリアントが返され、emdash-edit-mode Cookie はキャッシュ済み URL には効果がありません。サインイン済みの編集者向けにレンダリングされたページが共有キャッシュに保存されることはなく、次の匿名の訪問者がそれを埋めます。

ルートキャッシュを使うサイトでは、toolbar: "client" を設定してください。すると小さなスクリプトが、キャッシュ済みページ上でサインイン済みの編集者に編集ピルを表示し、編集ビューは常に最新の状態でレンダリングされる ?_edit URL から読み込まれます。プレビューリンクは、どちらのモードでもキャッシュをバイパスします。

プロバイダーごとの違い

memoryCache() を使う Node.js

  • 各サーバープロセスはそれぞれ独自のキャッシュを持ち、無効化は書き込みを処理したプロセスのみをクリアします。複数のプロセスやインスタンスを実行している場合、他のものは maxAge が切れるまでキャッシュ済みのページを提供し続けます。
  • 再起動するとキャッシュは空になります。
  • memoryCache() は maxAge を持つレスポンスのみを保存するため、ルールのないルートは、ページがキャッシュヒント付きで Astro.cache.set() を呼び出していても、キャッシュされません。
  • Node では、スケジュール公開はルートキャッシュを無効化しません。スケジュールされたエントリを一覧表示するページは、キャッシュされたコピーの有効期限が切れてから表示されるため、スケジュールされたコンテンツを一覧表示するルートでは maxAge を短く保ってください。

cacheCloudflare() を使う Cloudflare

  • このプロバイダーは、Cloudflare-CDN-Cache-Control と Cache-Tag のレスポンスヘッダーを書き込みます。Workers Cache は両方を使用します。クライアントがそれらを目にすることはなく、ページがブラウザー向けに設定する Cache-Control ヘッダーにも影響しません。
  • キャッシュプロバイダーが設定されていると、@astrojs/cloudflare アダプターは Workers Cache を自動的に有効にします。ビルド時に、wrangler deploy が読み込む生成済みの Wrangler 設定へ "cache": { "enabled": true } が書き込まれます。
  • ルールのないルートには no-store が付与されますが、それはレンダリング中に Astro.cache.set() を呼び出すものが何もない場合に限られます。getSiteSettingsWithCacheHint() や getMenuWithCacheHint() から得られるような、maxAge のないキャッシュヒントは、有効期間のない Cloudflare-CDN-Cache-Control: public を生成するため、Workers Cache はデフォルトのキャッシュ期間(200 レスポンスでは 2 時間)にフォールバックします。レイアウトがこれらのヒントを設定している場合、そのレイアウトを使用するすべてのページは、ルールの有無にかかわらずキャッシュ可能になります。
  • デフォルトでは Worker のバージョンがキャッシュキーの一部になるため、デプロイするたびにキャッシュは空の状態から始まり、デプロイ直後の最初のリクエストは Worker でレンダリングされます。デプロイをまたいでエントリを保持するには、Wrangler 設定で "cache": { "enabled": true, "cross_version_cache": true } を設定してください(バージョン間キャッシュを参照)。その場合、テンプレートの変更は、影響を受けるエントリの有効期限が切れるか無効化されて初めて表示されます。
  • スケジュール公開では、Cron Trigger が公開したエントリのタグが無効化されます。

パージのレート制限

Cloudflare プロバイダーは、Workers Cache をパージすることで無効化します。Workers Cache のパージには、アカウントのプランにかかわらず、常に Cloudflare の無料プランのパージ制限が適用されます。アカウントごとに 1 分あたり 5 リクエスト、バーストは 25 です(パージのレート制限を参照)。ほとんどのコンテンツ書き込みは、それぞれパージリクエストを 1 回消費します。公開中のコンテンツを変更する保存(公開中のエントリに直接保存するコレクションでの自動保存を含む)、公開、非公開化、削除、復元、スケジュール設定、エントリへのタクソノミー用語の割り当てに加え、設定、メニュー、タクソノミー、ウィジェットのあらゆる編集が該当します。リダイレクトの書き込みは、リダイレクト元パスのバリアントごとにパージリクエストを 1 回消費するため、リダイレクト元が変更されると最大で 4 回になります。

レート制限されたパージは、管理画面にエラーを表示せずに失敗し、影響を受けるページは maxAge が切れるまでキャッシュされたバージョンを保持します。一括編集中や、複数の編集者が同時に作業している場合は、古いコンテンツが残る最悪のケースとして許容できる maxAge を選んでください。

ブラウザーキャッシュは別物

ルートキャッシュは EmDash が無効化するため、長い TTL を設定できます。ブラウザーキャッシュは無効化されません。EmDash のヒントが提供するのはパージタグであり、自動的な Last-Modified バリデーターや、Astro の暗黙的な Cache-Control: no-cache ヘッダーではありません。1 つのエントリの更新時刻は、設定、関連リスト、削除されたエントリをカバーしないため、それをページのバリデーターとして使うと、条件付きの 304 レスポンスを通じて古い本文が残り続ける可能性があります。

ブラウザーキャッシュは Astro.response.headers.set("Cache-Control", ...) で設定します。訪問のたびに最新かどうかを確認するには no-cache を、そうでなければ短い有効期間(日単位ではなく分単位)を使ってください。条件付きリクエストを有効にするには、レスポンスのいずれかの依存関係が変わるたびにタイムスタンプが進む独自の lastModified ヒントを渡します。

エントリの表示用の更新日には、EmDash が自動では設定しない cacheHint.lastModified ではなく、entry.data.updatedAt を使用してください。