Webサイトの高速化技術の古参にして強力な手段がキャッシュの設定です。
基本中の基本ではあるものの、その仕組みについては実はよくわかっていないことも多かったり。
そこでキャッシュの仕組みや設定について基本的なことをまとめてみました。
実は奥が深いな、キャッシュの世界。
ブラウザキャッシュの仕組み
キャッシュとは、一時的に保管しておく複製ファイルのことですね。
Webの高速化において非常に重要な仕組みです。
Webサイトは画像などのリッチメディアを多用しているため、サーバー負荷やロードタイムを節約できるキャッシュの効果は絶大です。
キャッシュ利用の仕組みは次の図のような流れです。
初めてアクセスする場合は、ページの表示に必要なリソースをダウンロードするとともにキャッシュしておきます。
2回目以降のアクセスでは、キャッシュしたリソースを再利用することでダウンロードを省略し、ページの高速表示を図ります。
ブラウザのキャッシュが期限切れの場合でも、そのキャッシュがまだ有効かどうか、リソースの検証をサーバーにリクエストします。
キャシュがまだ有効な場合はそのままキャッシュを利用し、有効でない場合に新たにファイルをダウンロードします。
どのくらいの期間キャッシュを保持するのかはブラウザに依存します。
そこで、サイト側からブラウザに対してキャッシュの保存期間を指定することで、できるだけキャッシュを有効に活用するように働きかけます。
キャッシュを制御するHTTPヘッダー
キャッシュの制御は、通信を行う際のHTTPヘッダー情報(メッセージヘッダー)によって指示されます。
ブラウザからサーバーに送る情報をリクエストヘッダー、サーバーからブラウザに送る情報をレスポンスヘッダーと言いますが、これらの情報はブラウザのデベロッパー向けツールを利用すれば確認できます。
HTTPヘッダーの中で、キャッシュに関連して使われる情報は次の4つです。
- Cache-Control
- Expires
- Last-Modified
- ETag
Cache-Control
Cache-Control: public, max-age=86400
HTTP/1.1から使えるHTTPヘッダーで、キャッシュの有効期限を秒数で指定します。
リソースを所得してからの秒数で設定するため、サーバーとクライアントの時間設定がずれていても、キャッシュ期間のずれは生じません。
Cache-Controlの指示を受け取ったブラウザは、次回以降のアクセスがキャッシュの有効期間内であれば、ブラウザはHTTPリクエストをせずにキャッシュを利用します。
Cache-Control は Expires ヘッダーよりも優先される指定で、その他キャッシュの処理条件を指定できます。
Expires
Expires: Fri, 29 Jun 2018 11:17:13 GMT
Expiresは、キャッシュの有効期限を日時で指定します。
サーバーとクライアントの時間設定がずれていると、キャッシュ期間にずれが生じます。
Cache-Control同様、次回以降のアクセスがキャッシュの有効期間内であれば、ブラウザはHTTPリクエストをせずにキャッシュを利用します。
Cache-Controlの max-age
と両方記載がある場合は、Cache-Control の設定が優先されます。
Last-Modified
Last-Modified: Wed, 27 Dec 2017 08:39:12 GMT
Last-Modifiedはリソースの最終更新日を表します。
キャッシュリソースとサーバーリソースの情報を照合する際に使います。
ブラウザがキャッシュしているファイルの最終更新日をサーバーに知らせ、サーバー側のファイルの最終更新日と一致していればキャッシュを使い、不一致なら新たにファイルをダウンロードします。
このような検証作業を含んだHTTP通信を条件付きHTTPリクエスト (条件付きGETリクエスト) と言います。
ETag
ETag: "175614e56b6c00"
ETag (エンティティタグ) は、各リソースに付与される識別子で、 HTTP/1.1から定義されています。
最終更新日に限らず、同一URLのリソースに何かしらの変化があると ETag の値は変化するので、より細かい条件でキャッシュの有効性を確認できます。
Last-Modified と ETag の両方がある場合は、ETag の検証結果が優先されます。
Web高速化の基本は強制的なキャッシュ
キャッシュ制御を行うHTTPヘッダーは4つありましたが、ブラウザの処理は2通りに分けられます。
強制的にキャッシュを使う場合と、有効性を検証した上でキャッシュを使う場合の2通りです。
前者を「強いキャッシュ」、後者を「弱いキャッシュ」と呼ぶことがあります。
Cache-Control と Expires による指定は、強いキャッシュ制御です。
セットされている期間内であれば、ブラウザは原則としてサーバーに問い合わせることなくキャッシュを利用するからですね。
対して、Last-modified と ETag を利用したキャッシュの活用は弱いキャッシュです。
ブラウザとサーバー間で行ってくれる通信を利用したものです。
よって、ページ高速化のためのキャッシュ設定の基本は Cache-Control か Expires を指定することです。
キャッシュの設定方法
キャッシュは、どのリソースの種類に対しどれくらいの期間キャッシュを有効にするか、を設定します。
キャッシュを設定すべきなのは、画像・CSS・JavaScript などのあまり変更を加えないファイルです。
Page Speed Insights のアドバイスでは、最小で1週間、最大で1年の期間で設定することを推奨しているので、それに従えばよいでしょう。
ページ(HTMLやPHPなど)については、キャッシュを設定しないでかまいません。
頻繁に更新されないサイトや動的に変化しないページであれば、短めの期間のキャッシュを設定しておくこともできます。サイトによって判断すればよいでしょう。
Cache-Control の設定方法
ここでは Apacheサーバー .htaccessファイルへの記述を例としています。
サーバー制御ができてしまう重要なファイルなので、実際の操作は知識のある方が行いましょう。
ファイルの拡張子に応じて Cache-Control をセットします。FilesMatch
ディレクティブで正規表現を使って必要な拡張子を記述します。
ディレクティブは「コマンド」や「指示」と言った意味合いで、Apacheのドキュメントでよく使われる言葉です。
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|gif|jpe?g|png|webp|svg|ico)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
<FilesMatch "\.(eot|ttf|woff|woff2)$">
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
<FilesMatch "\.(html|php)$">
Header set Cache-Control "private, no-cache, max-age=1800"
</FilesMatch>
</IfModule>
<FilesMatch "\.(拡張子A|拡張子B|拡張子C)$">
と拡張子を |
で区切って ()
内に複数記述します。
Header set Cache-Control
の後の ""
内に max-age=秒数
で有効期間を指定します。
ファイル形式によって Cache-Control の値を変えたい場合は、FilesMatch
ディレクティブを繰り返せばOKです。
上記の例では、画像・CSS・JavaScript のキャッシュ期間を30日 (max-age=2592000
) としてまとめて記述しています。
Webフォントのキャッシュを追加し、期間を1年 (max-age=31536000
) に設定しました。
上記のサンプルでは、ページに対するキャッシュを 30分 (max-age=1800
) と設定していますが、反対に、キャッシュさせたくなければ max-age=0
とすることもできます。
さらに no-cache
を指定し、キャッシュの処理条件も指定しています。
no-cacheは、コンテンツの変更の有無を必ず確認させる指示なので、古いままのコンテンツを読み込んでしまうのを防止します。
ディレクティブには以下のようなものがあります。
ディレクティブ | 意味 |
---|---|
public | 全てのユーザーに対する共有キャッシュ |
private | 特定のユーザーに対するキャッシュ(中間キャッシュ不可) |
no-cache | 検証せずにキャッシュを使用しない(名前が紛らわしいがキャッシュはする) |
no-store | いかなるキャッシュもしない |
must-revalidate | 有効期間後のキャッシュをオリジンサーバーへ検証せずに使用しない |
Expiresの設定方法
Expiresでの設定は、HTTP/1.0 対応のみのかなり古いブラウザでもキャッシュを効かせることができるので、Google Page Speed Insights では Expires による指定を推奨しています。
Cache-Controlでも問題ないでしょうが、Expiresを指定してもかまいません。
Expiresの設定には mod_expires モジュールを利用します。
mod_expires は Cache-Control の max-age も同時に設定してくれます。
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 30 minutes"
ExpiresByType text/css "access plus 14 days"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
ExpiresByType font/eot "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
ExpiresActive On
でモジュールを有効化します。
ExpiresDefault
ディレクティブは全てのリソースがキャッシュ対象となるので使わずに、ExpiresByType
ディレクティブでファイルタイプごとにキャッシュを設定します。
ExpiresByType [MIMEタイプ] "[base] plus [num] [type]"
という書式で記述します。
[base]はキャッシュ有効期間の設定基準を次の3つのいずれかで記述します。
値 | 意味 |
---|---|
access | アクセスしてからの時間 |
now | 今からの時間 (実質 access と同じ) |
modification | 最終更新からの時間 |
[num]は数字で、[type] に以下の時間の単位が入ります。
値 | 意味 |
---|---|
years | 年 |
months | 月 |
weeks | 週 |
days | 日 |
hours | 時 |
minutes | 分 |
seconds | 秒 |
ETagの削除
ETagはLast-Modifiedよりも柔軟にファイルの差異を検出することができると言いましたが、キャッシュを上手く働かせるには逆に不都合となる場合もあります。
例えば、複数台のサーバーに分散してWebサイトを稼働させていると、同一リソースでもサーバーによってETagが異なることがあります。
そうすると、実質キャッシュが使えるリソースであっても無効になるということが起こります。
この場合、ETag (If-None-Match
) よりも 最終更新日 (If-Modified-Since
) で判別した方がキャッシュを有効活用できるので、ETagをHTTPヘッダーから削除すれば制御できます。
ETagの削除FileETag None
またはHeader unset ETag
反対に、ETag が意味を持つケースとして、同一URLで複数言語のコンテンツを返すサイトなどがあります。
最終更新日は同じでもコンテンツが違うことが想定できるので、ETag による識別が利用できます。
上級向けの設定ではありますが、運用環境によっては慎重に検討するとよいでしょう。
ソースファイルで言うと299行目、
キャッシュを制御するHTTPヘッダーの中のCache-Controlセクションで使われている例文の中の
Cache-Control
の文字が
Cache-Cotrol
になってましたよ!
ご指摘ありがとうございます!訂正しました!