背景
- Railsの
config/environments/production.rbを見ていたconfig.public_file_server.headersという設定があった - キャッシュ制御の設定っぽいけど、なぜこの値なのか、どういう仕組みで安全なのかがわからなかったので調べた
public_file_serverとは
Railsが public/ ディレクトリにある静的ファイル(CSS, JS, 画像など)を配信するための仕組み
関連する設定は2つある
# config/environments/production.rb
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
enabled — 静的ファイル配信の有効/無効
本番環境では通常、NginxやCDNが静的ファイルの配信を担当する。そのためRails自身が配信する必要はなく、デフォルトでは無効になっている
ただしHerokuのようなPaaS環境ではNginxを挟まずPumaが直接リクエストを受けるため、Rails自身が静的ファイルを配信する必要がある。環境変数 RAILS_SERVE_STATIC_FILES で切り替えられるようになっているのはそのため
headers — 配信時のHTTPレスポンスヘッダー
静的ファイルを配信するときに付与するHTTPレスポンスヘッダーを設定できる
Cache-Controlヘッダーの中身
cache-control: public, max-age=31536000
この設定には2つの指示が含まれている
public
「ブラウザだけでなく、CDNやプロキシサーバーもキャッシュしてOK」という意味
対義語は private で、これはブラウザだけがキャッシュできる(CDNやプロキシは不可)。ユーザー固有のデータ(マイページのHTMLなど)には private を使う。CSSやJSのような全ユーザー共通のファイルは public で問題ない
max-age=31536000
「31536000秒(= 1年間)はサーバーに再リクエストせず、キャッシュを使ってOK」という意味
つまりブラウザは一度ダウンロードしたファイルを1年間はローカルキャッシュから返す。サーバーへのリクエスト自体が発生しないので、表示速度の向上とサーバー負荷の軽減の両方が得られる
なぜ1年もキャッシュして大丈夫なのか
「ファイルを更新したのに古いキャッシュが使われ続けるのでは?」と思うかもしれないが、Railsのアセットパイプラインがこの問題を解決している
ダイジェスト付きファイル名
Railsはアセットをコンパイルするとき、ファイルの中身からハッシュ値(ダイジェスト)を計算し、ファイル名に付与する
application-a1b2c3d4e5f6.css
application-a1b2c3d4e5f6.js
CSSやJSの中身を1文字でも変更すると、ダイジェストが変わり、ファイル名も変わる
# 修正前
application-a1b2c3d4e5f6.css
# 修正後(中身が変わったのでダイジェストも変わる)
application-f7g8h9i0j1k2.css
HTMLから参照されるファイル名も新しいものに切り替わるので、ブラウザは「見たことのない新しいファイル」として取得しにいく。古いファイルのキャッシュは参照されなくなるだけなので、キャッシュが1年残っていても問題にならない
この仕組みがあるから長期キャッシュが安全
まとめると、こういう流れになる
1. ブラウザが application-a1b2c3d4e5f6.css をリクエスト
2. サーバーが「1年間キャッシュしてOK」と返す
3. ブラウザが1年間キャッシュする
4. 開発者がCSSを修正してデプロイ
5. HTMLが参照するファイル名が application-f7g8h9i0j1k2.css に変わる
6. ブラウザは新しいファイル名でリクエスト → 新しいファイルを取得
7. 古い application-a1b2c3d4e5f6.css のキャッシュは使われなくなる
ファイル名が変わるから、キャッシュの無効化を気にする必要がない。これがRailsのアセットパイプラインとCache-Controlヘッダーの組み合わせが上手くいく理由
まとめ
-
config.public_file_serverはRailsがpublic/の静的ファイルを配信する仕組み -
enabledは本番ではNginx/CDNが担当するので通常は無効、Herokuなどでは有効にする -
headersでcache-control: public, max-age=1年を設定し、ブラウザキャッシュを活用する - 1年という長期キャッシュが安全なのは、アセットパイプラインのダイジェスト付きファイル名のおかげ
- ファイル内容が変わればファイル名も変わるので、古いキャッシュが使われるリスクはない
参考
- Rails Guides — Asset Pipeline
- RFC 7234 — HTTP Caching(Cache-Controlヘッダーの仕様)
- Rails API — ActionDispatch::Static