Insight EdgeのLead Engineerの日下です。
弊社ではちょっとしたWebアプリを作るときに、AWSを用いたサーバーレスアーキテクチャで
フロントエンド | CloudFront + S3 + SPA(React等) |
バックエンド | API Gateway + Lambda |
という構成をしばしば使います。
今回は、この構成においてありがちなキャッシュによるバージョン不整合の対策について紹介します。
SPAにおけるキャッシュ問題
上記の構成は安価かつスケーラブルにSPAを運用できることが魅力ですが、 フロントエンドの静的ファイルに対してブラウザのキャッシュやCloudFrontのエッジキャッシュが働いてしまい、 アプリの更新がうまく反映されなかったり、フロントエンドとバックエンドのバージョン不整合の原因になることがあります。
jsやcssファイルにはハッシュ値が付与されるものの、呼び出し元であるindex.htmlにキャッシュが効いてしまうとjsやcssも古いファイルが参照されるため、
それによってキャッシュされたものが呼び出されたり、リンク切れが発生したりしてしまいます。
デプロイのたびにCloudFrontのキャッシュを削除(Invalidation)してもブラウザのキャッシュには対策できないため
ユーザー側でフルリロードしなければ最新化されずバージョン不整合などの原因になります。
アプリの処理としてバージョン整合性チェックを実装する方法もありますが、今回はお手軽な方法として、 index.htmlファイルにCache-Controlを設定してキャッシュを無効化する方法を紹介します。
S3のオブジェクトメタデータにキャッシュ動作を設定する
CloudFront + S3 で配信するファイルは、S3のオブジェクトメタデータにCache-Control等を設定することでHTTPヘッダに反映できます。
マネジメントコンソールで確認・設定する場合、index.htmlのファイルの詳細画面にてプロパティタブのメタデータの項目に設定します。
デプロイ時にAPIで設定
自動デプロイに組み込むためにAWS CLIでもやってみます。
CLIにはオブジェクトメタデータのみを編集するコマンドが無いため、以下のように aws s3api copy-object
を使用してメタデータを編集します。
その際、Content-Typeなど他のメタデータも明示的に指定しないと失われてしまうため、--content-type
オプション等で指定しています。
# 既存のオブジェクトにメタデータを設定 aws s3api copy-object --bucket your-web-contents-bucket --key index.html --copy-source your-web-contents-bucket/index.html --metadata-directive REPLACE --cache-control "no-cache, no-store" --content-type "text/html"
このコマンドは既存オブジェクトのメタデータを編集できますが、
aws s3 sync
等でファイルをアップロードした後にメタデータ編集を実行する手順だと、そのタイムラグの間にキャッシュ設定未反映のファイルがユーザに配信されてしまう可能性があります。
アップロードと同時にキャッシュ設定を反映させるには、先にindex.html以外を aws s3 sync
で更新し、
最後にindex.htmlを aws s3 cp
コマンドでメタデータ設定とともにアップロードすると良いでしょう。
# index.html以外をアップロード aws s3 sync ./build s3://your-web-contents-bucket --exclude index.html --delete # index.htmlをメタデータ設定とともにアップロード aws s3 cp ./build/index.html s3://your-web-contents-bucket/index.html --metadata-directive REPLACE --cache-control "no-cache, no-store" --content-type "text/html"
CloudFront経由で配信されたindex.htmlのヘッダをブラウザの開発者ツールで確認してみます。
ちゃんと設定されていますね。