大規模なプルリクエストをマージし、新しいサイトデザインが本番環境にデプロイされました。ページをリロードすると、CSSは完璧に適用されています。しかし、ブラウザのタブには、2023年から使っている古いピクセル化されたロゴが頑なに表示されたままです。デプロイ後にFaviconが更新されない問題で頭を抱えているのは、あなただけではありません。

Faviconのような静的アセットは、非常に厄介です。ブラウザ、プロキシ、そしてCDNなどのエッジネットワークは、帯域幅を節約するためにこれらを永久にキャッシュしようとします。

「ブラウザのキャッシュをクリアする」という基本的なアドバイスはここでは割愛します。ローカルブラウザ特有の問題については、ローカルFaviconキャッシュの強制クリアガイドをご覧ください。今回はデプロイメント側に焦点を当て、新しくデプロイされたサイトを訪れるすべてのユーザーに対して、強制的にアイコンを更新させる方法を解説します。

応急処置:クエリストリングによるキャッシュバスティング

急いでいて、とにかく新しいアイコンをすぐに表示させたい場合は、クエリストリング(クエリ文字列)を使用します。これは古くからある手法ですが、確実に動作します。

メインのHTMLテンプレート(index.html_document.tsxなど)を開き、FaviconのURLにバージョンパラメータを追加します。

<!-- 変更前: -->
<link rel='icon' href='/favicon.svg'>

<!-- 変更後: -->
<link rel='icon' href='/favicon.svg?v=2'>

?v=2(またはタイムスタンプやGitのコミットハッシュ)を追加することで、ブラウザに「これは全く新しいファイルだ」と錯覚させます。これによりローカルキャッシュをバイパスし、新たなネットワークリクエストを強制します。これを試しても表示されない場合は、パスの設定に根本的な問題がある可能性があります。Faviconトラブルシューティングガイドを確認してください。

なぜ本番環境でFaviconがスタックするのか?

クエリストリングで解決したなら素晴らしいですが、そもそもなぜこのようなことが起きるのでしょうか?モダンなWebアプリをデプロイする際、ファイルは複数の強力なキャッシュレイヤーを通過します。

1. CDNエッジキャッシュ

Vercel、Netlify、AWS CloudFrontなどでホスティングしている場合、静的ファイルはグローバルなエッジノードに分散されます。これらのネットワークはファイル名(favicon.ico)を見て、メモリから直接配信します。

デプロイメントパイプラインで静的ファイルのCDNキャッシュを明示的に無効化(Invalidation)しない限り、エッジノードはTTL(Time-To-Live)が切れるまで古いアイコンを配信し続けます。これには24〜48時間かかることもあります。

2. ビルドツールのハッシュ化漏れ

Vite、Webpack、Next.jsなどのモダンなバンドラーは優秀です。ビルドのたびにCSSやJSファイルに一意のハッシュ(例:main.a8b4c.js)を付与し、ユーザーが常に最新のコードを受け取れるようにします。

しかし、Faviconは通常public/static/ディレクトリに置かれます。ビルドツールはこれらのファイルをハッシュ化せずに、そのまま出力フォルダにコピーすることが多いのです。ファイル名が全く同じままなので、ブラウザは再ダウンロードする理由を見出せません。

3. Service Workerの罠(PWA)

サイトがプログレッシブWebアプリ(PWA)の場合、Service Workerがネットワークリクエストを傍受します。Service Workerはアプリシェルやアイコンを強力にキャッシュすることで知られています。

もしsw.js/favicon.pngをキャッシュしており、デプロイ時に更新メカニズムがトリガーされなかった場合、CDNをバイパスしたとしても、Cache Storage APIから無期限に古いアイコンが配信され続けます。

プロが実践するFaviconキャッシュ問題の予防策

クエリストリングは優れた応急処置ですが、一部の厳格な企業プロキシはキャッシュする前にクエリパラメータを削除してしまうことがあります。最も確実な方法は、ファイル名自体のバージョニングです。

戦略1:実際のファイル名を変更する

クエリストリングに頼るのではなく、大規模なリブランディングの際はFaviconのファイル名そのものを変更します。

GitHubが動的な状態アイコンをどう扱っているか見てみましょう。未読通知がある場合、GitHubはキャッシュされたファイルを上書きしようとはしません。href属性を全く別のファイルパス(青いドット付きのSVGなど)に切り替えます。実際のパスを変更することこそが、すべてのキャッシュレイヤーを即座にバイパスする唯一の100%確実な方法です。

戦略2:適切なCache-Controlヘッダーの設定

サーバーの設定(Nginx、Apache、Nodeサーバーなど)を制御できる場合は、Faviconに対して特定のCache-Controlヘッダーを設定すべきです。

# NginxでのFavicon設定例
location ~* \.(ico|png|svg)$ {
    expires 1d;
    add_header Cache-Control 'public, max-age=86400, must-revalidate';
}

max-ageを短く(1年ではなく1日など)設定することで、古いアイコンがキャッシュされたとしても、数ヶ月にわたって新しいデプロイを妨害するような事態を防ぐことができます。

まとめ

デプロイ後にFaviconが更新されない問題に対処することは、Web開発者にとって一種の通過儀礼です。ブラウザからCDN、Service Workerに至るまでのキャッシュ階層はWebを高速化するために設計されていますが、静的アセットの更新を非常に困難なものにしています。

次回のブランド変更時には、この頭痛の種を未然に防ぎましょう。Mzu favicondlを使用して鮮明でモダンなアイコンセットを生成し、物理ファイル名にバージョン番号を含め、ユーザーが即座に新しいブランドを目にすることができるという確信を持ってデプロイしてください。