SPAにおけるindex.htmlとアセットファイルの配布方法およびキャッシュ戦略の分離

はじめに

この記事は、アソビュー Advent Calendar 2025の8日目(表面)です。
こんにちは、アソビューでSREを担当している森です。

私はもともとアプリケーション開発を中心に業務をしていましたが、SRE チームに異動してまだ一年も経っていません。
インフラ領域の経験が浅い中で、SPA配信基盤の大きな改善に挑戦することになり、学びの多い取り組みとなりましたので棚卸しも兼ねてここで共有したいと思います。

アソビューでは一部のアプリケーションをSPA(Single Page Application)で構成しています。
最近その構成を改修しましたので本記事では、 SPAにおいて index.html とアセットファイル(JavaScript や CSS)を分離して配布・キャッシュする戦略 を採用することで、キャッシュ不整合や管理コスト増加といった課題をどのように解決したのかを技術的観点から解説します。

SPAにおける配布とキャッシュの基本

index.html

  • アプリケーションのエントリーポイント
  • キャッシュは行わない、または短い TTL を設定することが一般的

アセットファイル(.js、.cssなど)

  • アプリケーションロジックやスタイルを含む
  • ファイル名にハッシュを含め、長い TTL でキャッシュする

これらは性質が異なるため、配布レイヤーとキャッシュ戦略を分離することで、安全かつ効率的な運用が可能になります。

旧インフラ構成

旧構成では、以下のような多段CloudFront構成を採用していました。

ユーザーのブラウザ  
  ↓  
CloudFront(前段):index.html以外をキャッシュ  
  ↓  
CloudFront(後段):全てをキャッシュ  
  ↓  
S3 (最新バージョンのみ保持)  

設計思想

ユーザーからのアクセスをできる限りキャッシュで処理し、S3 への負荷を最小化することが目的でした。
弊社サービスの都合上前段・後段で別々にキャッシュすることで、S3 へのアクセスを抑制しようとしていましたが、この構成は以下の問題を抱えていました。

問題点1:キャッシュの不整合

前段と後段の CloudFront でキャッシュ対象が異なり、更新タイミングも一致しないため、以下のような不整合が発生していました。

  • index.html は新しいバージョンを読み込むが、アセットファイルは古いキャッシュのまま
  • 更新が両方の CloudFront に即時反映されない
    • 前段のCloudFrontはリリースに伴い.jsファイルのバージョンが更新されることでキャッシュパスが切り替わりますが、リリースタイミングでのキャッシュクリアを実施していなかった
  • .js ファイルと HTML のバージョンずれによりページでスクリプトエラーが発生し真っ白になる

問題点2:ロールバック時のバージョン不整合

S3 には 最新バージョンしかアップロードされておらず、ロールバック時に次の問題が発生していました。

  • CloudFront の設定で.jsの取得にてエラーが発生するとデフォルトでindex.htmlが返されていた
  • ユーザーのindex.htmlのバージョンとアクセスタイミングによっては.jsファイルが見つからず、CloudFrontのエラー時設定に従いロールバック後バージョンの.jsのキャッシュパスにindex.htmlが登録されてしまうケースがあった
  • ロールバック後バージョンのindex.html が参照する.jsが欠損、ユーザーがindex.htmlからアクセスすると正常に画面表示されなかった

問題点3:管理コストの増加

CloudFront が二重化されていることで、

  • キャッシュ更新の保証が難しい
  • 管理コストが増える
  • デプロイ後の即時反映が困難

といった問題がありました。

新インフラ構成

これらの課題を解消するため、以下の新構成を導入しました。

ユーザーのブラウザ  
  ↓  
CloudFront:全てをキャッシュ  
  ↓  
├─ nginx:index.htmlを提供  
└─ S3:アセットファイルを全バージョン保持  

設計のポイント:配布方法とキャッシュ戦略の分離

index.htmlの配布:Nginxから提供

  • CloudFrontで短い TTL を設定
  • 更新を即時反映できる
  • ロールバック時もバージョン管理が容易

アセットファイル(S3 から配布)

  • 全バージョンを保持
  • ファイル名にハッシュを含め、長期キャッシュが可能
  • ロールバック時でも参照ファイルが欠損しない

キャッシュ戦略の分離による効果

index.htmlとアセットファイルで異なるキャッシュ戦略を適用できるようになりました

  • index.html:短TTL → すぐ更新される
  • アセット:長TTL → 高いキャッシュ効率

この戦略により、両者の性質を最大限活かしつつ不整合を回避できる構成となりました。

新構成への移行で注意した点

無停止での移行

移行中も旧構成と新構成が並行稼働できるよう、切り替え手順を詳細に整理して進めました。
結果として、一度の切り替えでスムーズに移行できました。

デプロイ時の安全性確保

デプロイ中・デプロイ後・ロールバック時など、あらゆるタイミングで

  • index.html が参照するアセットのバージョンが常に存在すること
  • 古いタブのユーザーも正しいバージョンへ誘導されること

を保証するため、SRE チーム全体で繰り返し検証を行いました。

新構成移行後の成果

キャッシュ構成の簡素化

  • キャッシュ更新の整合性が保証される
  • 更新反映の追跡が容易になる

ロールバック時の安全性向上

  • S3 が全バージョンを保持するため、ロールバック時の整合性問題が解消
  • CloudFront のキャッシュが一段になったことで、挙動がシンプルに
  • エントリーポイント(index.html)だけをロールバックすれば安全に切り戻しが出来る様になった

上記改善の効果によって

  • キャッシュ管理の安全性向上:不整合がゼロに
  • 管理コスト削減:構成がシンプルになり運用しやすくなった
  • リリースの安定性向上:バージョンずれによるスクリプトエラーが解消

を実現することが出来ました!

SRE に異動して一年も経っていない状態からの挑戦でしたが、 チームと協力し安定稼働を保証出来るまで新構成案を改善し続けた経験は大きな学びとなりました。

学びと今後の改善

今回の構成移行作業を通じて、特に以下の点を学びました。

  • キャッシュ構成は複雑化すると更新保証が難しくなる
  • index.html とアセットを分離すると SPA の運用が圧倒的に安定する

今後は、IaC の標準化などより運用効率を高める改善にも取り組んでいきたいと考えています。

最後に

アソビューでは新しい挑戦を歓迎しており、
私のようにインフラ未経験からでも重要なシステム構築に関わることができます。

興味がある方は、ぜひ一度カジュアル面談にお越しください。

www.asoview.co.jp