独立系サイトのキャッシュ戦略:ページキャッシュ、オブジェクトキャッシュ、ブラウザキャッシュの設定
1. 3層キャッシュのそれぞれの役割
独立系サイトのキャッシュは3層に分けられ、各層が異なる問題を解決します。一緒にしないでください。ページキャッシュはHTMLのレンダリング結果全体を静的ファイルとして保存し、次に同じURLへアクセスがあったときはファイルをそのまま返します。PHPもデータベースも実行する必要はありません。オブジェクトキャッシュはデータベースクエリの結果をメモリ(RedisやMemcachedなど)に保存し、次に同じクエリがあればメモリから直接取得します。MySQLへ再び問い合わせる必要はありません。ブラウザキャッシュはHTTPレスポンスヘッダーで訪問者のブラウザに「このファイルはローカルに保存しておいてください」と伝え、次回のアクセス時はローカルディスクから直接読み込みます。
3層のパフォーマンス効果には大きな差があります。ページキャッシュの効果が最大です。PHPの実行とデータベースクエリをまるごとスキップし、LCPを4秒以上から1秒以内に短縮できます。オブジェクトキャッシュの効果は中程度です。データベースクエリの時間を省くだけなので、ページキャッシュがすでにヒットしていれば、オブジェクトキャッシュはそもそも発動しません。ブラウザキャッシュの効果はリピーター向けです。初回アクセスでは必ずダウンロードが必要ですが、2回目以降はローカルから読み込むため、再訪問時の速度がはっきり向上します。
| キャッシュ層 | 保存するもの | 保存場所 | パフォーマンス効果 | 代表的なツール |
|---|---|---|---|---|
| ページキャッシュ | HTMLページ全体 | サーバーのディスクまたはメモリ | 最大、LCPが60%から80%低下 | WP Rocket、LiteSpeed Cache |
| オブジェクトキャッシュ | データベースクエリの結果、PHPオブジェクト | RedisまたはMemcached | 中程度、DBクエリ時間が50%低下 | Redis、Memcached |
| ブラウザキャッシュ | CSS/JS/画像 | 訪問者のブラウザ内 | リピーター、2回目のアクセスが速い | Cache-Controlレスポンスヘッダー |
| CDNキャッシュ | 静的リソース+一部のHTML | 世界各地のエッジノード | 地域間の遅延が50%以上低下 | Cloudflare、Akamai |
多くの人はまずRedisやMemcachedをいじりたがりますが、B2Bの自社サイトならページキャッシュを正しく設定するだけで性能問題の80%は解決します。オブジェクトキャッシュは加点要素で、月間UVが5万を超えたときやデータベースクエリが遅くなってきたときに導入するのが適切です。CDNと3層キャッシュの関係はCDNによるグローバル高速化とノード選定、性能指標の合格ラインはCore Web Vitalsの最適化。
二、ページキャッシュ:HTMLをどう保存すれば失敗しないか
ページキャッシュは最も価値のある層であり、最も設定を誤りやすい層でもあります。設定ミスの典型的な症状は、訪問者が問い合わせを送信した後に表示されるのが前の訪問者の送信内容だったり、ログイン後なのに未ログイン状態のページが表示されたりすることです。原因は、動的コンテンツを含むHTMLまで静的ファイルとして保存してしまうことにあります。
正しい方法は、URLとユーザー状態で2種類に分けることです。公開静的ページ(トップページ、製品ページ、ブログ記事ページ)はページキャッシュの対象にし、動的ページ(問い合わせフォーム送信後、wp-admin管理画面、ログイン後のページ、ショッピングカート)は必ずキャッシュをスキップさせます。WordPressのキャッシュプラグイン(WP Rocket、LiteSpeed Cache)はどちらも「URLの除外」と「Cookieによるキャッシュスキップ」のルールに対応しています。
| ページの種類 | キャッシュの可否 | キャッシュ期間 | 除外ルール |
|---|---|---|---|
| トップページ、製品一覧ページ | キャッシュ | 1時間から4時間 | ログインユーザーを除外 |
| ブログ記事ページ | キャッシュ | 12時間から1日 | コンテンツ更新時に手動で更新 |
| 問い合わせフォームページ | キャッシュしない | 設定なし | 常にオリジンへ |
| フォーム送信完了ページ | キャッシュしない | 設定なし | session cookie付き |
| wp-admin管理画面 | キャッシュしない | 設定なし | 管理者cookieを除外 |
| 検索結果ページ | 短時間キャッシュまたはキャッシュなし | 5分 | 検索語ごとのキャッシュを避ける |
シナリオ例:産業機械メーカーの公式サイトの製品詳細ページで、URLが /products/cnc-machine-a/ だとします。このページの内容は数か月に一度しか変わらないので、1日キャッシュしても全く問題ありません。しかし問い合わせフォームの「送信完了」ページのURLは /inquiry-thank-you/ で、このページは訪問者ごとに自分が送信した確認情報が表示されるべきなので、絶対にキャッシュしてはいけません。WP Rocketでは、/inquiry-thank-you/ と /wp-admin/* をNever Cache URL(s)リストに追加します。
キャッシュの無効化もページキャッシュの鍵です。ブログ記事を1本修正したとき、どうやって本番環境にすぐ反映させるか。方法は2つあります。1つは公開時に手動で全キャッシュをPurgeする方法、もう1つは「URL単位の更新」でその記事だけを消す方法です。WP Rocketは記事更新時に関連キャッシュを自動で更新しますが、多くのカスタムページでは自動でトリガーされないため、公開後にPurgeをクリックする習慣をつける必要があります。环形山は越境サイトにおいてキャッシュの無効化と公開フローを連動させています。詳しくはcraterの技術スタックと4つの自社開発プラグイン。
三、オブジェクトキャッシュ:データベースクエリの結果をどう再利用するか
ページキャッシュがヒットしているときは、PHPもデータベースも動かないので、オブジェクトキャッシュも当然使われません。しかしページキャッシュがミスしたとき、たとえば新しい記事を公開した直後や、キャッシュすべきでない動的ページにアクセスしたときは、PHPがデータベースに問い合わせます。WordPressのページは平均12〜20回データベースに問い合わせ、そのたびにディスクIOが発生します。遅いクエリが積み重なると、ページの表示に1秒以上かかることもあります。
オブジェクトキャッシュは、こうしたクエリ結果をRedisやMemcachedに保存し、次に同じクエリが来たらメモリから直接読み取ります。Redisを1台のサーバーにインストールし、WordPress側はRedis Object Cacheプラグインで接続するだけで、コードを書き換える必要はありません。正しく設定すれば、データベースのクエリ時間を50%以上削減でき、ページキャッシュのミス時の応答時間も1.5秒から0.5秒以内に短縮できます。
| 指標 | オブジェクトキャッシュなし | Redisオブジェクトキャッシュあり | 改善幅 |
|---|---|---|---|
| ページあたりのデータベースクエリ回数 | 12〜20回/ページ | 2〜5回/ページ | 70%〜80%削減 |
| 1回のクエリにかかる時間 | 5〜20ミリ秒(ディスクIO) | 0.1〜1ミリ秒(メモリ) | 10〜50倍高速 |
| ページキャッシュのミス時の応答時間 | 1〜1.5秒 | 0.3〜0.5秒 | 50%以上削減 |
| MySQLのCPU使用率 | ピーク時80%以上 | ピーク時30%〜40% | 半分以上削減 |
オブジェクトキャッシュは必ず導入すべきものではありません。月間UVが1万以下のB2Bサイトならデータベースの負荷はそれほど大きくなく、ページキャッシュがすでに95%のリクエストを処理しているため、Redisを導入しても効果はあまり見込めません。では、いつ導入すべきか。判断の目安は2つあります。1つ目は、MySQLのスロークエリログで1秒を超えるクエリが1日に100件以上あること。2つ目は、管理画面で記事を編集して保存したあと、フロント側に反映されるまで数分かかること(オブジェクトキャッシュが正しく無効化されていない状態)です。データベースのスロークエリとインデックスの最適化については独立サイトのデータベーススロークエリとインデックス最適化。
四、ブラウザキャッシュ:静的リソースのキャッシュヘッダーの設定方法
ブラウザキャッシュは3層の中で最も設定が簡単な層です。サーバー側のプログラムは一切関係なく、Webサーバー(NginxまたはApache)にレスポンスヘッダーを数行追加するだけです。仕組みはこうです。サーバーがCSS、JS、画像を返すときに、HTTPレスポンスヘッダーにCache-Control: max-age=2592000を追加し、ブラウザに「このファイルは30日間ローカルに保存してください」と伝えます。30日以内に再アクセスしてもサーバーへのリクエストは発生しません。
ただし、ここには古典的な矛盾があります。ブラウザキャッシュの期間を長く設定するとユーザーのアクセスは速くなりますが、CSSやJSを変更してもユーザーのローカルには古いバージョンが残り、表示が崩れてしまいます。解決策はキャッシュ期間を短くすることではなく、ファイル名にバージョン番号やハッシュを付けることです。たとえば style.css?v=2 や style.abc123.css のようにします。ファイルの中身が変わればURLも変わるので、ブラウザは自動的に再ダウンロードします。
| リソース種別 | Cache-Controlの推奨設定 | max-age | 更新方法 |
|---|---|---|---|
| 画像(jpg/png/webp) | public, max-age=31536000 | 1年 | ファイル名にハッシュを付け、画像を差し替えたら名前も変更 |
| フォントファイル | public, max-age=31536000 | 1年 | フォントは変更がなければそのままでOK |
| CSSスタイルシート | public, max-age=604800 | 7日 | ファイル名にバージョン番号?ver=2 |
| JavaScript | public, max-age=604800 | 7日 | ファイル名にバージョン番号?ver=2 |
| HTMLページ | no-cache, must-revalidate | キャッシュしない | 毎回オリジンで検証 |
| favicon.ico | public, max-age=604800 | 7日 | ほとんど変更なし |
直感に反する判断:多くの人はキャッシュ期間が長いと変更がユーザーに届かないと恐れ、すべてのリソースのmax-ageを1時間に設定します。その結果、訪問者は毎回すべてのCSSとJSを再ダウンロードし、ファーストビューがかえって遅くなります。正しい方法は思い切って長く設定し(画像1年、CSS/JS 7日)、ファイルハッシュやバージョン番号で更新に対応することです。WordPressのwp_enqueue_script関数は自動でバージョン番号を付与し、テーマやプラグインの更新時に自動で変わるため、手動管理は不要です。
画像最適化とメディア資産の管理についてはメディア資産の管理と画像圧縮をご覧ください。多言語サイトの静的リソースキャッシュではhreflangとCDNキャッシュの相互作用にも注意が必要です。詳しくはHreflang多言語対応。
五、キャッシュ無効化の戦略とよくある衝突
キャッシュを設定した後、最大の問題は「どうやってキャッシュを期限切れにすべきときに期限切れにするか」です。キャッシュが適切に無効化されないとユーザーは古いコンテンツを見てしまい、逆に無効化されやすすぎると性能のメリットが失われます。独立サイトでよくあるキャッシュ無効化のシナリオは4つあります:新規記事の公開、製品情報の更新、CSS/JSの変更、問い合わせフォームの送信です。それぞれのシナリオに異なる無効化戦略があります。
| シナリオ | 無効化範囲 | 無効化方法 | 注意点 |
|---|---|---|---|
| 新規ブログ記事の公開 | トップページ+記事一覧+個別記事 | WP Rocketが自動更新 | カテゴリページとタグページも更新が必要 |
| 製品価格の更新 | 製品詳細ページ+一覧ページ | 該当URLを手動でPurge | 全体をPurgeしない、性能に影響する |
| CSS/JSの変更 | 該当する静的ファイル | バージョン番号を変更すれば自動反映 | max-ageを変更せず、ファイル名を変更 |
| 問い合わせフォームの送信 | フォームページは絶対にキャッシュしない | URL除外ルール | 完了ページもキャッシュ不可 |
| サイト全体のリニューアル | すべてのキャッシュ | ワンクリックでPurge All | アクセスの少ない時間帯に実施 |
| プラグイン/テーマの更新 | すべてのキャッシュ | 自動または手動でPurge | 更新前にバックアップ |
よくある衝突は2つあります。1つ目はキャッシュプラグインを2つインストールしている場合(例えばWP RocketとLiteSpeed Cacheを同時に導入)、両方のプラグインが.htaccessと出力バッファを変更し、ページが真っ白になったりスタイルが崩れたりします。ルールはサイト全体でキャッシュプラグインを1つだけにすることです。2つ目はページキャッシュとログイン状態の衝突です。ユーザーが管理画面にログインしても、フロントエンドは未ログインのキャッシュページのままなので、ナビゲーションバーが正しく表示されません。解決方法は、キャッシュプラグインがログインCookieを検知して自動的にキャッシュをスキップすることです。
キャッシュ戦略を公開した後は継続的に監視し、毎月キャッシュヒット率とLCPを確認します。キャッシュヒット率が突然下がった場合、通常は誰かがURL構造を変更したり、ランダムパラメータ付きのリンクを追加したことが原因です。監視とアラートのチェックリストはサイト監視とアラート、日常メンテナンス項目は公式サイト保守で毎月やる6つのことをご覧ください。バックアップと復元の演習もキャッシュ設定変更後に一度行ってください。詳しくはサイトのバックアップと復元演習。
FAQ よくある質問
WordPress独立サイトにキャッシュプラグインは必須ですか?
問い合わせフォームのページはキャッシュできますか?
Redisオブジェクトキャッシュはいつ導入すべきですか?
ブラウザキャッシュの期間はどれくらいが適切ですか?
キャッシュで内容の変更が反映されない場合はどうすればいいですか?
ページキャッシュとCDNキャッシュは競合しますか?
最後に
キャッシュの設定で初心者が陥りがちなのは両極端です。まったく設定せず毎回PHPとデータベースを動かしてファーストビューに4秒以上かけるか、逆に攻めすぎてフォームページまでキャッシュし、ユーザーに他人の送信内容を見せてしまうか。正解は「レイヤーごとに分ける」ことです。静的ページは積極的にキャッシュし、動的ページは絶対にキャッシュしない。静的アセットは長めにキャッシュしてバージョン番号を付けます。
やることは3つです。まずページキャッシュのプラグインを1つ入れ(WP RocketかLiteSpeed Cacheのどちらか)、フォームページとwp-adminを除外します。次にNginxのCache-Controlヘッダーを設定し、画像は1年、CSS/JSは7日、HTMLはキャッシュしないようにします。最後に月間UUが5万を超えたらRedisのオブジェクトキャッシュを導入します。ここまでやればLCPはほぼ1秒以内に収まり、キャッシュが内容を混線させる事故も起きません。
覚えておきたい一言。キャッシュは独立サイトのパフォーマンスを左右するてこです。ページキャッシュは時間を80%削減し、オブジェクトキャッシュはデータベースを助け、ブラウザキャッシュは再訪問を軽くします。3つの層がそれぞれ役割を担うので、フォームページだけはキャッシュに入れないでください。
