Data URI のコスト
Data URI はファイルを URL の中に入れる仕組みです:data:image/png;base64,iVBORw0KGgo...。URL を書ける場所ならどこでも使えるので、アイコンを HTML の src 属性や CSS の background-image の中に置けば、ブラウザがそれを別途リクエストすることはありません。
トレードオフは次のとおりです。
- サイズ。 Base64 はペイロードを元の 4/3、つまり約 33% 大きくします(正確な式は Base64 のサイズ増加にあります)。さらに
data:image/png;base64,という接頭辞で 22 バイト増えます。 - キャッシュ。 インライン化したファイルは、それを含む文書の一部としてしかキャッシュされません。すべての HTML ページにロゴをインライン化すると、ページごとに毎回ダウンロードされます。CSS ファイルにインライン化すればその CSS と一緒にキャッシュされますが、アイコンを 1 つ変えるとスタイルシート全体のキャッシュが無効になります。
- レンダリング。 レンダリングをブロックする CSS が届くまで、ブラウザは何も描画できません。200 KB のフォントや画像をインライン化したスタイルシートは、そのバイトのダウンロードにかかる時間だけ初回描画を遅らせます。HTML では、インラインのデータがそれ以降のコンテンツをバイトストリームの後ろへ押しやります。
- 遅延読み込みが効かない。 Data URI を使った
<img>にloading="lazy"を付けても何も節約できません。バイトはすでにページと一緒に届いているからです。
その代わりに、リクエストが 1 つ減ります。HTTP/1.1 では、ブラウザがホストごとにごく少数の接続しか開かなかったため、これは大きな意味がありました。HTTP/2 や HTTP/3 では多数のリクエストが 1 つの接続を共有するので、節約は小さくなります。主にリクエストとレスポンスのヘッダー分と、ブラウザが遅れて見つけるファイルについての 1 往復分のレイテンシです。
実測:gzip と Brotli は Base64 をどう変えるか
33% という数字は圧縮していないテキストの場合です。ほとんどの HTML と CSS は gzip か Brotli で配信されるので、重要なのは圧縮後のサイズです。私たちが自分で計測しました。
方法。 サンプルファイルは Debian 13 のパッケージから取りました。16×16 の PNG アイコン(HighContrast アイコンテーマ)、48×48 の RGBA PNG(hicolor テーマ)、2880×1800 の Xfce の青い壁紙 JPEG、それを Pillow で品質 80 に縮小した 800×500 の JPEG、Roboto Slab Regular の WOFF2 フォント、2 つの SVG アイコン(479 バイトの Adwaita シンボリックアイコンと 7.6 KB の GNOME の Debian スワール)です。各ファイルとその Base64(data: 接頭辞を除いたペイロードのみ)を Node.js 24 の zlib で圧縮しました。設定は、事前圧縮した静的アセットでよく使われる gzip レベル 9 と Brotli 品質 11 です。サイズの単位はバイトです。
| ファイル | 元のサイズ | Base64 | 元 gzip | Base64 gzip | 元 Brotli | Base64 Brotli |
|---|---|---|---|---|---|---|
| PNG 16×16 | 281 | 376 | 285 | 306 | 285 | 284 |
| PNG 48×48 | 4,552 | 6,072 | 4,575 | 4,602 | 4,551 | 4,589 |
| JPEG 800×500 | 8,195 | 10,928 | 7,425 | 7,173 | 7,160 | 6,908 |
| JPEG 2880×1800 | 197,011 | 262,684 | 182,684 | 183,945 | 180,114 | 181,140 |
| WOFF2 フォント | 52,532 | 70,044 | 52,538 | 52,765 | 52,536 | 52,690 |
| SVG アイコン | 479 | 640 | 291 | 427 | 247 | 410 |
| SVG スワール | 7,610 | 10,148 | 3,123 | 4,398 | 2,582 | 3,823 |
数値からわかること:
圧縮済みのバイナリでは、増加分がほぼすべて消えます。 PNG、JPEG、WOFF2 のデータはすでに圧縮されていてランダムに近いため、再圧縮してもほとんど縮みません(どちらの PNG も gzip でわずかに大きくなりました)。ランダムなバイト列の Base64 は 64 種類の記号をそれぞれ 8 ビットで表しますが、ハフマン符号化によって 1 記号あたり約 6 ビットに戻ります。圧縮した Base64 は圧縮したバイナリの 96〜107%、元のファイルの 84〜109% に収まりました。今回の JPEG はそれ自体がわずかに圧縮できたため、Base64 版が gzip した JPEG より少し小さくなることさえありました。とはいえ、当てにできるほどの差ではありません。16×16 の小さな PNG では gzip の固定 18 バイトのヘッダーとトレーラーが大きな割合を占めますが、Brotli は枠組みが軽いのでその差が消えました。
SVG は悪化します。 SVG は繰り返しの多いテキストで、圧縮と非常に相性がよく、スワールは gzip で 7,610 バイトから 3,123 バイトに縮みました。Base64 は 3 バイトのグループを、アラインメントによって値が変わる 4 文字に組み替えるため、同じ部分文字列が 2 度目には同じに見えなくなります。gzip した Base64 の SVG は gzip した SVG より 41〜47% 大きく、Brotli では 48〜66% 大きくなりました。
URL エンコードした SVG と Base64
SVG はもともとテキストなので、Base64 はまったく必要ありません。URL エンコードした Data URI なら読みやすさも保てます。
.flag {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 0v16h2V8h3l1 2h5V2h-4L9 0z'/%3E%3C/svg%3E");
}
最低限エスケープが必要なのは %、#(URL のフラグメントの始まりになるため)、<、> と、属性を囲む引用符です。SVG 内の二重引用符を一重引用符に置き換えれば、引用符のエスケープは不要になります。ASCII 以外の文字は UTF-8 としてパーセントエンコードする必要があります。
同じ 2 つの SVG を同じ方法で計測した結果です。
| SVG | エンコード | サイズ | gzip | Brotli |
|---|---|---|---|---|
| アイコン | Base64 | 640 | 427 | 410 |
| アイコン | 最小限の URL エンコード | 493 | 293 | 253 |
| アイコン | encodeURIComponent | 723 | 326 | 279 |
| スワール | Base64 | 10,148 | 4,398 | 3,823 |
| スワール | 最小限の URL エンコード | 7,179 | 3,057 | 2,549 |
| スワール | encodeURIComponent | 11,594 | 3,319 | 2,750 |
「最小限」とは、空白をまとめ、二重引用符を一重引用符に変え、%、#、<、> だけをエスケープしたものです。圧縮後は元の SVG とほぼ同じサイズになり、スワールは空白をまとめたぶんだけむしろ少し小さくなりました。完全な encodeURIComponent は圧縮前こそ大きいものの、圧縮すれば Base64 よりはるかに小さくなります。SVG を Data URI に変換ツールは両方の形式を並べて出力します。
それでも SVG に Base64 を使う意味があるのは、ツールや API が Base64 しか受け付けない場合や、エスケープを気にしたくない文字が SVG に含まれている場合です。
Content Security Policy と data:
Content Security Policy では、'self' に data: URL は含まれません。許可したいディレクティブごとにスキームを列挙する必要があります。
Content-Security-Policy: default-src 'self'; img-src 'self' data:; font-src 'self' data:
img-src と font-src で data: を許可するのは一般的で、リスクも低めです。script-src、default-src、object-src で許可するのは話が別です。マークアップを注入できる攻撃者が外部ホストなしでスクリプトを送り込めるようになり、CSP の意義の多くが失われます。インライン画像がポリシーに引っかかる場合は、img-src だけを広げてください。
また、ブラウザはリンク、リダイレクト、スクリプトのいずれであっても、トップレベルのウィンドウを data: URL に遷移させることをブロックします。そのため Data URI はページに埋め込むことはできても、トップレベルのリンクとして開くことはできません。
インライン化する価値があるとき
たいていは価値がある:
- 小さなアイコンや UI 画像。 目安としておよそ 1〜2 KB 未満のもの、特に初回描画に必要なものです。リクエストのオーバーヘッドがサイズに対して大きな割合を占め、Base64 のコストは圧縮でほとんど消えます。
- 単一ファイルの HTML。 オフラインのレポート、エクスポートしたドキュメント、テストカバレッジのページ、ディスクから開いても動く必要がある HTML メールのプレビューなどです。
- クリティカル CSS。 どのみち HTML にインライン化する CSS なら、小さな背景画像を一緒に運べます。
- 生成されたコンテンツ。 スクリプトで PNG に描画したグラフを HTML レポートに埋め込む場合などです。
たいていは価値がない:
- 写真や数 KB を超えるもの。長いキャッシュ期間を設定した別ファイルにしたほうが有利です。
- 多くのページで共有する画像。HTML のレスポンスのたびに再ダウンロードされてしまいます。
- Web フォント。WOFF2 ファイルは通常数十 KB あり(上で計測したものは 52 KB)、CSS にインライン化するとスタイルシートがレンダリングをブロックする時間が長くなります。別ファイルのフォントならキャッシュもプリロードもできます。
- スクロールしないと見えない位置のもの。
loading="lazy"を付けた別ファイルなら、必要になるまでコストはかかりません。
メールは別の話です。data: 画像への対応はメールクライアントによってまちまちなので、受信者が実際に使っているクライアントでテストしてください。HTML メールに画像を埋め込む確実な方法は、cid: で参照するインラインの MIME 添付です。これも Base64 ですが、MIME 構造の中にあります。メールでの Base64 をご覧ください。
PNG、JPEG、WebP、SVG をドロップすると、Data URI とそのサイズが表示されます。ファイルはブラウザの外に出ません。
よくある質問
Base64 の画像は通常の画像より遅いですか?
本質的に遅いわけではありません。小さなインライン画像はリクエストを減らせますが、大きな画像は HTML や CSS を肥大化させ、個別にキャッシュできず、レンダリングを遅らせることがあります。数 KB 未満ならインライン化は有効で、それ以上では逆効果です。
gzip で Base64 の 33% の増加はなくなりますか?
PNG、JPEG、WOFF2 ではほぼ完全になくなります。私たちの計測では、gzip した Base64 はバイナリファイルとの差が数 % 以内に収まりました。SVG などのテキストではなくならず、gzip した Base64 の SVG は gzip した SVG より 41〜47% 大きくなりました。
インライン SVG には URL エンコードと Base64 のどちらがよいですか?
URL エンコードです。読みやすく、編集でき、元の SVG とほぼ同じくらいに圧縮されます。少なくとも #、%、<、> をエスケープし、属性を二重引用符で囲む場合は SVG 内で一重引用符を使ってください。
Data URI の画像が Content Security Policy でブロックされるのはなぜですか?
CSP は data: を別のスキームとして扱い、'self' には含まれません。img-src(フォントなら font-src)に data: を追加してください。script-src や default-src には追加しないでください。