コンソールを赤く染める憎きエラー

静的アセットを新しいCDNに移行したばかりだとします。サイトの読み込みは爆速になりましたが、ブラウザのタブにはデフォルトのグレーの地球儀が表示されています。DevToolsを開くと、コンソールが真っ赤に染まっています:Access to fetch at 'https://cdn.yoursite.com/site.webmanifest' from origin 'https://yoursite.com' has been blocked by CORS policy。

これが、非常に厄介なFaviconのCORS(オリジン間リソース共有)エラーの世界です。Webマニフェスト、SVGアイコン、または動的に生成されるFaviconを別のサブドメインやサードパーティのCDNでホストしている場合、現代のブラウザはセキュリティ上の理由からデフォルトでこれらをブロックします。

開発作業に戻れるよう、今すぐこの問題を解決しましょう。

即効性のある解決策:HTML属性の追加

お急ぎの場合、問題の多くはHTMLの<link>タグに属性が欠けていることです。ブラウザは通常の画像とFetchリクエストを異なる方法で処理します。<img>タグは通常、厳格なCORSチェックを回避しますが、JSONマニフェストや動的操作用のSVGの取得は回避できません。

これを即座に修正するには、マニフェストとアイコンのリンクにcrossorigin属性を追加します。

Webマニフェストリンクの更新

<link rel='manifest' href='https://cdn.yoursite.com/site.webmanifest' crossorigin='use-credentials'>

なぜuse-credentialsなのか?PWAマニフェストの読み込みにCookieや認証が必要な場合(社内ツールなどでよくあります)、この正確な値が必要です。CDNが完全に公開されている場合は、代わりにcrossorigin='anonymous'を使用できます。

SVGアイコンリンクの更新

<link rel='icon' type='image/svg+xml' href='https://cdn.yoursite.com/icon.svg' crossorigin='anonymous'>

JavaScript経由でアイコンを注入したり、Canvas要素で使用したりする場合、このanonymousフラグはユーザー認証情報を送信せずにファイルをリクエストするようブラウザに指示し、通常はこれでブラウザのセキュリティモデルを満たします。

根本原因:なぜブラウザはFaviconをブロックするのか?

たかが16x16ピクセルの画像がなぜセキュリティロックダウンを引き起こすのか不思議に思うかもしれません。これは2つの特定の最新Web機能に起因しています。

1. Fetch APIとWebマニフェスト

site.webmanifestファイルは画像ではなく、JSONファイルです。ブラウザがrel='manifest'タグを見ると、内部でFetch APIを使用してダウンロードします。クロスオリジンのFetchリクエストは厳格なCORSポリシーの対象となります。HTMLがwww.example.comにあり、マニフェストがassets.example.comにある場合、サーバーが明示的に許可しない限り、ブラウザはそれをブロックします。

2. 汚染されたCanvas(Tainted Canvas)

動的なFavicon(JavaScriptを使用してアイコンに赤い通知ドットを追加するなど)を構築している場合、アイコンを<canvas>要素に描画し、変更を加えてデータURLとしてエクスポートしている可能性が高いです。元の画像がCORSヘッダーなしのCDNから読み込まれた場合、ブラウザはそのCanvasを「汚染された(tainted)」とマークします。Canvasが汚染されると、toDataURL()を呼び出した際に致命的なセキュリティエラーがスローされます。

CORS以前に基本的な表示の問題で悩んでいる場合は、Faviconが表示されない場合のトラブルシューティングガイドをご覧ください。

サーバー側の修正:CDNの設定

HTMLの修正は戦いの半分に過ぎません。CDNやストレージバケット(AWS S3やCloudflareなど)が正しいヘッダーを返さない場合、ブラウザは依然としてファイルを拒否します。

アセットサーバーがAccess-Control-Allow-Originヘッダーを返すように設定する必要があります。一般的な環境での設定方法は以下の通りです。

Nginxの設定

静的アセット用に独自のNginxサーバーを管理している場合は、設定にこのブロックを追加して、マニフェストと画像のクロスオリジンリクエストを許可します:

location ~* \.(webmanifest|svg|png|ico)$ {
    add_header Access-Control-Allow-Origin "*";
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
}

AWS S3 CORSの設定

S3バケットから直接アセットを提供している場合は、AWSコンソール経由で以下のJSONスニペットを使用してバケットのCORSポリシーを更新する必要があります:

[
    {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "HEAD"],
        "AllowedOrigins": ["https://www.yoursite.com"],
        "ExposeHeaders": []
    }
]

実例:GitHubの対応方法

GitHubを見てみましょう。彼らはユーザーアバターとUIアイコンをavatars.githubusercontent.comでホストしています。ネットワークリクエストを検査すると、これらすべてのアセットに対して明示的にAccess-Control-Allow-Origin: *を送信していることがわかります。

なぜでしょうか?GitHubは動的なUI更新に大きく依存しているからです。CORSヘッダーを完璧に設定していなければ、それらの画像からピクセルデータを読み取ろうとするクライアント側スクリプトがブラウザタブのJavaScript実行をクラッシュさせてしまいます。彼らはこのサーバーヘッダーとHTMLのcrossorigin='anonymous'属性を組み合わせることで、堅牢なセットアップを構築しています。

予防策と私の個人的な意見

何年もこれらの問題をデバッグしてきて、私には強い意見があります:メインのfavicon.icoはルートドメインに置いてください。

Webマニフェストや高解像度SVGなどの最新のアセットについては、上記のようにヘッダーを設定している限り、CDNでホストすることは問題ありません。

結論

FaviconのCORSエラーは頭痛の種ですが、ブラウザのセキュリティモデルを理解すれば完全に予測可能です。リンクタグにcrossorigin属性を追加し、CDNがAccess-Control-Allow-Originヘッダーを送信することを確認し、フォールバックアイコンをルートドメインに保持してください。

小規模なプロジェクトでCDNの問題を完全に回避したい場合は、Mzu favicondlを使用して完全に最適化されたローカルアイコンのセットを生成してください。クロスオリジンの問題を解決する最善の方法は、同じオリジンにとどまることである場合もあります。