キャッシュバスティングの基礎
ブラウザキャッシュを意図的に無効化するキャッシュバスティングの仕組みと、Spring Frameworkにおける実装パターン
TL;DR
- キャッシュバスティングとは、ブラウザに「このファイルは古い」と伝え、強制的に最新版を取得させる手法
- ファイル名やURLにバージョン情報を付加することで実現する(
app.js?v=2やapp.a1b2c3.jsなど) - Spring Framework では
ResourceHandlerRegistryやResourceUrlEncodingFilterを使って自動化できる - CDNやリバースプロキシのキャッシュにも同様の考え方が適用される
目的
Webアプリケーションでは、CSS・JavaScript・画像などの静的ファイルをブラウザがキャッシュする。これによりページの読み込み速度が向上するが、ファイルを更新してもキャッシュが残っていると古いファイルが使われ続けるという問題が発生する。
キャッシュバスティング(Cache Busting)は、ファイルの内容が変わったことをブラウザに伝え、強制的にサーバーから最新ファイルを取得させるための技術的な工夫である。
ブラウザキャッシュの仕組み
ブラウザは Cache-Control や ETag などのHTTPヘッダーをもとにキャッシュを制御する。
| ヘッダー | 役割 |
|---|---|
Cache-Control: max-age=31536000 | 1年間キャッシュを保持する |
ETag: "abc123" | ファイルの識別子。変化したかどうかの判定に使う |
Last-Modified | 最終更新日時。条件付きリクエストに使う |
max-age が長く設定されているとブラウザはサーバーに問い合わせすらしない。そのためETagや最終更新日時による「変化の検知」を待たずに、URLそのものを変えることで確実に新しいファイルを取得させるのがキャッシュバスティングの発想である。
キャッシュバスティングの手法
1. クエリストリングによるバージョン付加
URLにバージョン番号やビルド番号をクエリパラメータとして付加する。
<link rel="stylesheet" href="/css/style.css?v=1.2.3">
<script src="/js/app.js?v=20260616"></script>メリット:ファイル名を変えずに済むため実装がシンプル。
デメリット:一部のCDN・プロキシがクエリストリングを無視してキャッシュする場合がある。
2. ファイル名へのコンテンツハッシュの埋め込み
ファイルの内容から生成したハッシュ値をファイル名に含める。WebpackやViteなどのバンドラーがこの方式を採用している。
app.a1b2c3d4.js ← ファイルの内容が変わればハッシュが変わる
style.9f8e7d.cssHTMLからの参照もビルド時に自動で書き換えられる。
<!-- ビルド後のHTML -->
<script src="/js/app.a1b2c3d4.js"></script>メリット:ファイルの内容が変わったときだけURLが変わるため、変更のないファイルは長期キャッシュを維持できる。CDNとの相性も良い。
デメリット:ビルドパイプラインの整備が必要。
3. パスベースのバージョニング
ディレクトリにバージョンを含める。
/static/v3/css/style.css
/assets/2026-06/app.jsSpring Framework でのキャッシュバスティング
Spring MVC の ResourceHandler
Spring MVC には静的リソースを配信するための ResourceHandlerRegistry が用意されており、キャッシュバスティングを組み込みでサポートしている。
ContentVersionStrategy(コンテンツハッシュ方式)
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(true)
.addResolver(new VersionResourceResolver()
.addContentVersionStrategy("/**"));
}
}この設定により、/static/js/app.js へのリクエストは内部的にコンテンツハッシュを付加した形で処理される。
FixedVersionStrategy(固定バージョン方式)
.addResolver(new VersionResourceResolver()
.addFixedVersionStrategy("v1.2.3", "/**"));アプリケーションのバージョンを環境変数などから注入する場合に使う。
ResourceUrlEncodingFilter
Thymeleafなどのテンプレートエンジンから静的リソースのURLを参照する場合、ResourceUrlEncodingFilter を登録することでテンプレート内のURLが自動的にバージョン付きURLに変換される。
@Bean
public FilterRegistrationBean<ResourceUrlEncodingFilter> resourceUrlEncodingFilter() {
FilterRegistrationBean<ResourceUrlEncodingFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new ResourceUrlEncodingFilter());
return bean;
}Thymeleafテンプレート内での記述:
<!-- 記述 -->
<link rel="stylesheet" th:href="@{/static/css/style.css}">
<!-- レンダリング後(ハッシュが自動付加される) -->
<link rel="stylesheet" href="/static/css/style.a1b2c3d4.css">Spring Boot での設定
Spring Boot を使う場合、application.properties でシンプルに設定できる。
# リソースチェーンを有効化
spring.web.resources.chain.enabled=true
# コンテンツベースのバージョン戦略を有効化
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
# キャッシュ期間(秒)
spring.web.resources.cache.period=31536000または application.yml で:
spring:
web:
resources:
chain:
enabled: true
strategy:
content:
enabled: true
paths: "/**"
cache:
period: 31536000Spring Security との組み合わせ
Spring Security を使用している場合、バージョン付き静的リソースのパスがセキュリティフィルターに引っかかることがある。静的リソースのパスを明示的に許可する設定が必要になる場合がある。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/static/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}よくある落とし穴
ハードコードした ?v=1 を忘れる
クエリストリング方式で手動管理していると、デプロイ後にバージョンを更新し忘れるミスが起きやすい。CIパイプラインでビルド番号やコミットハッシュを自動で埋め込む仕組みを用意しておくと安全。
# Mavenのフィルタリングを使う例
app.version=@project.version@CDNがクエリストリングを無視する
CloudFrontなどのCDNはデフォルトでクエリストリングをキャッシュキーから除外することがある。クエリストリング方式を採用する場合はCDNの設定を確認し、クエリストリングをキャッシュキーに含める設定を行う。
ファイル名ハッシュとHTMLのキャッシュ
JavaScriptやCSSにコンテンツハッシュを付けても、それを参照するHTMLファイル自体がキャッシュされていると意味がない。HTMLは Cache-Control: no-cache や max-age=0 に設定し、常にサーバーから取得するようにする。
registry.addResourceHandler("/*.html")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.noCache());まとめ:手法の比較
| 手法 | CDNとの相性 | 自動化 | 導入コスト |
|---|---|---|---|
| クエリストリング | △(設定次第) | 手動またはビルド変数 | 低 |
| コンテンツハッシュ(ファイル名) | ◎ | バンドラーまたはSpring自動化 | 中 |
| パスバージョニング | ○ | ビルド変数 | 中 |
Springアプリケーションで静的ファイルをサーバーから直接配信する場合は、VersionResourceResolver と ResourceUrlEncodingFilter の組み合わせが最も透過的で管理しやすい。フロントエンドにVite・Webpackを使う場合はビルドツール側でコンテンツハッシュを付与し、SpringはAPIサーバーとして静的ファイルを返さない構成も一般的である。