Brotli vs Gzip 圧縮比較|nginx静的・動的設定とCDN実践ガイド
- テキストアセットの転送量削減では BrotliがGzipより15〜25%小さいのが2026年時点の一般的な計測値だ。
- ただし動的Brotli高レベルはCPUとTTFBを悪化させる。
- ビルド時に
.br/.gzを生成し、nginxはbrotli_static on;gzip_static on;で配信するのが最も安全で速い。 - CDNがエッジ圧縮を担う場合も、オリジン静的圧縮とHTTP/2・HTTP/3の多重化・CRP最適化を組み合わせて初めてLCPが動く。
はじめに — 圧縮はまだ効くのか
2026年のフロントエンドはバンドル分割・Tree Shaking・ESM配信が当たり前になり、単一の巨大 app.js は減った。それでも典型的なSPA初回ロードでは HTML + CSS + JS合計 400〜900KB(圧縮前) が残り、モバイル回線(下行 5〜20Mbps、RTT 80〜150ms)では転送バイトがそのままLCPに効く。
クリティカルレンダリングパス最適化でレンダーブロッキングを削っても、ネットワーク上のバイト数が大きければWaterfallは長いままだ。HTTP/2・HTTP/3が接続効率を改善しても、圧縮は「1リクエストあたりのペイロード」を直接小さくする。両者は競合せず、積み重ねる。
本記事では次を実務レベルで扱う。
- BrotliとGzipのアルゴリズム差とブラウザ対応
- 静的圧縮(precompressed) と 動的圧縮(on-the-fly) のnginx設定
- compression levelのCPU・サイズトレードオフ
- CDN(Cloudflare等)との役割分担
- 2026年ベンチマーク数値と検証コマンド
BrotliとGzip — 技術的な違い
| 項目 | Gzip | Brotli |
|---|---|---|
| 標準化 | RFC 1952(1996) | RFC 7932(2016) |
| 辞書 | 固定ハフマン、32KBウィンドウ | コンテキストモデリング + 静的辞書(約120KB) |
| 典型圧縮率(テキスト) | ベースライン | 同条件で 15〜25% 小さい ことが多い |
| エンコードCPU | 比較的軽い | レベルが上がると急増 |
| デコードCPU | 軽い | 軽い(gzip同等〜やや速い) |
| ブラウザ対応 | ほぼ100% | Chrome/Firefox/Safari/Edge 全対応(2020年以降) |
| nginx標準 | 同梱 | 別モジュール(ngx_brotli) |
Brotliの強みは 事前定義された120KB超の静的辞書 にある。HTML・CSS・JSに頻出するトークン(function, class, div, https:// など)が辞書に入っているため、短いファイルほど相対的な削減率が大きくなる。Gzipは汎用DEFLATEで、辞書なしでも安定するが、テキストに対してはBrotliに劣る。
Content-Encoding のネゴシエーション
ブラウザはリクエストヘッダーで対応コーデックを宣言する。
GET /assets/app.js HTTP/2
Host: example.com
Accept-Encoding: br, gzip, deflate, zstd
サーバーはレスポンスで実際のエンコーディングを返す。
HTTP/2 200 OK
Content-Type: application/javascript
Content-Encoding: br
Content-Length: 84231
Vary: Accept-Encoding
Cache-Control: public, max-age=31536000, immutable
Vary: Accept-Encoding はCDNキャッシュで必須だ。これがないと、gzipでキャッシュされたレスポンスがbr対応クライアントに誤配信される。圧縮とキャッシュはセットで設計する。
2025〜2026年にかけて一部CDN・originで zstd 圧縮が実験導入されている。Brotliより高速にエンコードでき、圧縮率はBrotliに近い場合がある。ただしブラウザネイティブ対応は限定的で、2026年6月時点では Brotli + Gzip のデュアルスタック が本番の無難な選択肢だ。nginxのzstdモジュールはngx_http_zstd_module等で別途ビルドが必要。
2026年ベンチマーク — 数値で見る削減率
以下は同一ビルド成果物(Vite + React 19、本番 minify 済み)を 2026年6月 に計測した代表値だ。環境は AMD EPYC 7R13(4 vCPU)、nginx 1.26 + ngx_brotli 1.0.0。再現性のためファイル単体で計測している。
単一ファイル比較(bytes)
| ファイル | 生サイズ | gzip -6 | brotli -6 | brotli -11 | Brotli-6 vs gzip-6 |
|---|---|---|---|---|---|
index.html | 4,812 | 1,643 | 1,291 | 1,198 | -21.4% |
app.[hash].js | 287,420 | 89,104 | 72,318 | 68,901 | -18.8% |
vendor.[hash].js | 412,880 | 128,550 | 101,204 | 96,882 | -21.3% |
app.[hash].css | 48,220 | 9,882 | 7,104 | 6,550 | -28.1% |
sprite.svg | 22,100 | 6,440 | 4,892 | 4,501 | -24.0% |
| 合計 | 775,432 | 235,619 | 186,809 | 177,032 | -20.7% |
モバイル4G相当(下行 10Mbps、RTT 100ms)で転送時間だけを概算すると、gzip合計 235KB ≈ 188ms、brotli-6合計 187KB ≈ 150ms — 約38ms短縮。LCP要素がJS/CSSに依存するSPAでは、CRP最適化と合わせて数百ms改善に寄与しうる。
動的圧縮のTTFBペナルティ(同一4 vCPU)
| 方式 | 287KB JS 初回圧縮 | TTFB増分(p50) |
|---|---|---|
| 非圧縮配信 | 0 ms | 0 ms(基準) |
| gzip 動的 level 6 | 12 ms | +12 ms |
| brotli 動的 level 6 | 48 ms | +48 ms |
| brotli 動的 level 11 | 890 ms | +890 ms |
静的 .br 配信(事前圧縮) | 0 ms(配信のみ) | +1 ms(ディスクI/O) |
動的brotli-11は 1リクエストあほぼ1秒のCPU を食い、並行リクエストでスループットが崩れる。静的precompressedならエンコードコストはCIに閉じ込められる。
静的圧縮 vs 動的圧縮 — 使い分け
| 方式 | いつ圧縮する | CPU負荷の場所 | 向いているリソース |
|---|---|---|---|
| 静的(precompressed) | CI/CDビルド時 | デプロイパイプライン | JS/CSS/HTML/JSON/SVG(ハッシュ付きファイル名) |
| 動的(on-the-fly) | リクエスト毎 | nginxワーカーCPU | SSR HTML、API JSON、動的生成テキスト |
| CDNエッジ圧縮 | キャッシュミス時 | CDN PoP | オリジン非圧縮・小規模サイト |
鉄則: ファイル名にコンテンツハッシュがあるアセット(app.a1b2c3.js)はビルド時に .br と .gz を生成し、nginxは brotli_static / gzip_static で配信する。SSRのHTMLや /api/ JSONだけ動的圧縮に残す。
ビルドパイプライン例(npm scripts)
{
"scripts": {
"build": "vite build",
"compress": "node scripts/compress-assets.mjs",
"build:prod": "npm run build && npm run compress"
}
}
// scripts/compress-assets.mjs
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { gzipSync } from 'node:zlib';
import { brotliCompressSync, constants } from 'node:zlib';
const DIST = './dist';
const TEXT_EXT = new Set(['.html', '.js', '.css', '.svg', '.json', '.txt', '.xml']);
async function walk(dir) {
const entries = await readdir(dir, { withFileTypes: true });
for (const ent of entries) {
const full = join(dir, ent.name);
if (ent.isDirectory()) await walk(full);
else if (TEXT_EXT.has(ent.name.slice(ent.name.lastIndexOf('.')))) {
const buf = await readFile(full);
await writeFile(full + '.gz', gzipSync(buf, { level: 6 }));
await writeFile(
full + '.br',
brotliCompressSync(buf, {
params: { [constants.BROTLI_PARAM_QUALITY]: 11 },
})
);
console.log('compressed:', full);
}
}
}
await walk(DIST);
Viteなら vite-plugin-compression を使う手もある。いずれにせよ デプロイ成果物に .br / .gz が並ぶ 状態を作る。
nginx設定 ① — 基本のGzip(動的)
標準nginxでそのまま使える最小構成。まずはこれで現状把握し、のちに静的圧縮へ移行する。
# /etc/nginx/conf.d/gzip-basic.conf
# 動的Gzipのみ — 検証・小規模サイト向け
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_disable "msie6";
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/atom+xml
image/svg+xml
font/ttf
font/otf
application/vnd.ms-fontobject;
# HTMLはデフォルトで gzip_types に含まれないため明示
gzip_types text/html;
gzip_min_length 256 は小さすぎるレスポンスの圧縮を避ける。256バイト未満はヘッダー分だけ肥大化することがある。gzip_vary on はCDN利用時に必須。
nginx設定 ② — Brotli動的圧縮(ngx_brotli)
ngx_brotliモジュールをロードしたうえでの動的Brotli設定。本番ではレベル4〜6に抑える。
# /etc/nginx/conf.d/brotli-dynamic.conf
# ngx_brotli モジュール必須(load_module または --add-module ビルド)
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
brotli on;
brotli_comp_level 5;
brotli_min_length 256;
brotli_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/atom+xml
image/svg+xml
font/ttf
font/otf
application/vnd.ms-fontobject
text/html;
Debian/Ubuntuでカスタムビルドした場合、modules-enabled にシンボリックリンクを置く。Dockerなら多段ビルドでnginx+brotliイメージを作る。
FROM debian:bookworm-slim AS builder
RUN apt-get update && apt-get install -y build-essential libpcre3-dev zlib1g-dev libssl-dev git
RUN git clone --recursive https://github.com/google/ngx_brotli.git /tmp/ngx_brotli
RUN curl -O http://nginx.org/download/nginx-1.26.2.tar.gz && tar xf nginx-1.26.2.tar.gz
WORKDIR /nginx-1.26.2
RUN ./configure \
--with-compat \
--add-dynamic-module=/tmp/ngx_brotli \
&& make modules
FROM nginx:1.26-bookworm
COPY --from=builder /nginx-1.26.2/objs/ngx_http_brotli_filter_module.so /etc/nginx/modules/
COPY --from=builder /nginx-1.26.2/objs/ngx_http_brotli_static_module.so /etc/nginx/modules/
nginx設定 ③ — 静的圧縮(本番推奨)
app.js と同階層に app.js.br / app.js.gz があるとき、クライアントの Accept-Encoding に応じて自動選択する。エンコードCPUゼロが最大の利点。
# /etc/nginx/conf.d/compression-static.conf
# 事前圧縮ファイル .br / .gz を優先配信
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
gzip_static on;
brotli_static on;
# 静的ファイルに .br/.gz が無い場合のフォールバック(動的)
gzip on;
gzip_comp_level 5;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/css application/javascript application/json image/svg+xml text/html;
brotli on;
brotli_comp_level 4;
brotli_min_length 1024;
brotli_types text/css application/javascript application/json image/svg+xml text/html;
server {
listen 443 ssl http2;
server_name cdn-origin.example.com;
root /var/www/app/dist;
index index.html;
location /assets/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
# brotli_static / gzip_static が .br / .gz を自動選択
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}
brotli_static on は Accept-Encoding: br のとき file.br を探し、あれば Content-Encoding: br で返す。無ければ通常ファイルまたは動的brotliにフォールバックする。
nginx設定 ④ — 静的アセットと動的APIの分離
大規模構成では location ごとに圧縮方針を分ける。/assets/ は静的のみ、/api/ は動的JSON、/ はSSR。
# /etc/nginx/sites-available/app-production.conf
# 静的 precompressed + API動的圧縮のハイブリッド
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
map $http_accept_encoding $css_js_encoding {
default "";
"~*br" ".br";
"~*gzip" ".gz";
"~*gzip.*br" ".br";
}
upstream app_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/app/dist;
# --- 静的アセット: 事前圧縮のみ、動的圧縮オフ ---
location ^~ /assets/ {
gzip off;
brotli off;
gzip_static on;
brotli_static on;
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Vary "Accept-Encoding" always;
access_log off;
try_files $uri =404;
}
# --- API: 動的JSON圧縮 ---
location ^~ /api/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
gzip on;
gzip_comp_level 5;
gzip_types application/json;
gzip_vary on;
brotli on;
brotli_comp_level 4;
brotli_types application/json;
add_header Vary "Accept-Encoding" always;
}
# --- SSR / HTML: 中程度の動的圧縮 ---
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
gzip on;
gzip_comp_level 6;
gzip_types text/html text/css application/javascript;
gzip_vary on;
brotli on;
brotli_comp_level 5;
brotli_types text/html text/css application/javascript;
add_header Vary "Accept-Encoding" always;
}
}
/assets/ で gzip off; brotli off; としているのは、誤って動的圧縮に落ちた場合のCPU浪費を防ぐため。.br が無いファイルは生のまま返り、デプロイチェックで検知する運用が望ましい。
nginx設定 ⑤ — CDNオリジン向け(Cloudflare連携)
CloudflareはデフォルトでエッジBrotli/Gzipを行う。オリジンで二重圧縮しないよう注意する。オリジンがすでに Content-Encoding: br で返す場合、CFは再圧縮しないが、キャッシュキーと Vary の整合が重要。
# /etc/nginx/sites-available/origin-behind-cloudflare.conf
# Cloudflare オリジン: 静的 br/gz 配信 + 実IP復元
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
# Cloudflare IPレンジからの接続のみ許可(本番推奨)
# set_real_ip_from は CF 公開IPリストを include
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
gzip_static on;
brotli_static on;
gzip_vary on;
server {
listen 443 ssl http2;
server_name origin.example.com;
root /var/www/releases/current;
# オリジンは圧縮済みバイトを返す — CFはそのままキャッシュ
location / {
brotli off;
gzip off;
gzip_static on;
brotli_static on;
add_header Cache-Control "public, max-age=0, s-maxage=86400" always;
add_header Vary "Accept-Encoding" always;
add_header X-Origin-Compression "static-br-gz" always;
try_files $uri $uri/ /index.html;
}
location /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
}
Cloudflareダッシュボード側では Speed → Optimization → Brotli: ON、Auto Minify はオフ(ビルド済みと競合するため)。s-maxage でエッジTTLを制御し、オリジンは max-age=0 でブラウザ直キャッシュを短くするパターンが多い。
nginx設定 ⑥ — 圧縮効果のログ計測
どのコーデックで何バイト返したかをログに残し、週次で集計する。
# /etc/nginx/conf.d/compression-logging.conf
log_format compression '$remote_addr - [$time_local] '
'"$request" $status $body_bytes_sent '
'enc=$sent_http_content_encoding '
'ct=$sent_http_content_type '
'rt=$request_time '
'upstream=$upstream_addr';
server {
listen 443 ssl http2;
server_name example.com;
access_log /var/log/nginx/access.log compression;
# 以下 server ブロックの設定は他ファイルと merge される想定
}
enc=br の割合が90%未満なら、古いクライアント・ボット・設定ミスを疑う。enc=- が多いのは画像やwoff2など非圧縮typesで正常な場合もある。
compression level — トレードオフの深掘り
Gzipレベル(1〜9)
| レベル | エンコード速度 | サイズ(287KB JS) | 推奨用途 |
|---|---|---|---|
| 1 | 最速 | 98,200 bytes | 超高トラフィック動的 |
| 4 | 速い | 90,800 bytes | 動的API |
| 6 | 標準 | 89,104 bytes | 動的汎用デフォルト |
| 9 | 遅い | 87,950 bytes | 静的生成のみ |
レベル6→9の差は 約1.3% しか縮まないのにCPUは2〜3倍。動的では6で十分。
Brotliレベル(0〜11)
| レベル | エンコード速度 | サイズ(287KB JS) | 推奨用途 |
|---|---|---|---|
| 4 | 速い | 76,100 bytes | 高負荷動的 |
| 5〜6 | 中 | 72,318 bytes | 動的上限 |
| 9 | 遅い | 70,102 bytes | CI静的生成 |
| 11 | 最遅 | 68,901 bytes | ビルド時 .br |
動的は 5以下、静的 .br は 11 が一般的。レベル11は1ファイルあたり数百msかかっても、数千ファイルを並列圧縮すればCI 2〜5分で収まる。
| シナリオ | 推奨設定 |
|---|---|
| ハッシュ付きJS/CSS | ビルド時 brotli-11 + gzip-6 → brotli_static / gzip_static |
| SSR HTML | 動的 brotli-5 / gzip-6、またはキャッシュ+事前生成 |
| REST JSON API | 動的 brotli-4 / gzip-5、100KB超はページネーション検討 |
| WebSocket | 圧縮なし(permessage-deflateは別途検討) |
| 既に圧縮済み | woff2 / webp / avif / mp4 は types から除外 |
CDNとの役割分担 — 3パターン
パターンA: CDNエッジ圧縮のみ(小規模・レガシー)
- オリジン: 非圧縮
- CDN: 初回ミス時に圧縮しキャッシュ
- メリット: オリジン設定が単純
- デメリット: オリジン↔CDN間の転送が太い、ミス時TTFB悪化
パターンB: オリジン静的圧縮 + CDNパススルー(推奨)
- オリジン:
.br/.gzをContent-Encoding付きで返す - CDN: 圧縮済みをキャッシュ、再圧縮しない
- メリット: エンドツーエンドで最小バイト、ミス時も速い
- デメリット: ビルドパイプラインに圧縮ステップが必要
パターンC: オリジン動的 + CDN再圧縮(非推奨)
- オリジンがgzip、CDNがbrotliに変換など
- CPU二重消費、キャッシュキー複雑化。避ける。
HTTP/2・HTTP/3の多重化と組み合わせると、圧縮後の小さなフレームが多数並列で飛ぶ。転送バイト削減は 単一コネクションの占有時間短縮 に直結する。
検証手順 — curlとDevTools
Accept-Encoding ごとのサイズ比較
# 非圧縮(Accept-Encoding なし)
curl -sI -H "Accept-Encoding: identity" https://example.com/assets/app.js | grep -i content-length
# gzip
curl -sI -H "Accept-Encoding: gzip" https://example.com/assets/app.js | grep -E 'content-encoding|content-length'
# brotli
curl -sI -H "Accept-Encoding: br" https://example.com/assets/app.js | grep -E 'content-encoding|content-length'
# 実バイト数計測
curl -s -H "Accept-Encoding: br" https://example.com/assets/app.js | wc -c
デコード後の整合性
curl -s -H "Accept-Encoding: br" https://example.com/assets/app.js | brotli -d | shasum -a 256
curl -s https://example.com/assets/app.js | shasum -a 256
# ハッシュが一致すれば静的/動的どちらでも中身は同一
Lighthouse / WebPageTest
- WebPageTest — Compare Viewで「圧縮OFF(identity)」は再現困難なため、Before/Afterで設定変更前後を比較
- Chrome DevTools Network — Size列の「transferred」vs「resource」で圧縮率を確認
- Lighthouse — 「Enable text compression」監査が赤なら動的圧縮漏れ
転送量20%削減しても、LCP要素が未最適化の画像1枚(500KB)なら効果は薄い。圧縮は テキストアセット に効く。CRP最適化でLCP要素の特定・preload、画像はWebP/AVIFへ。プロトコル・圧縮・レンダリングの順でボトルネックを切り分ける。
よくある障害と対処
| 症状 | 原因 | 対処 |
|---|---|---|
| ブラウザで文字化け | 二重圧縮、壊れた .br | curl --compressed で検証、CIでハッシュ比較 |
| CDNでgzip/brが交互に混在 | Vary: Accept-Encoding 欠落 | nginx gzip_vary on / add_header Vary |
.br があるのにgzipが返る | brotli_static 未ロード | load_module と brotli_static on 確認 |
| CPU 100% | 動的brotli-11を全レスポンスに適用 | 静的化またはレベル引き下げ |
| ETagが効かない | 圧縮版と非圧縮版でETag不一致 | gzip_static は元ファイルのmtime/etagを使用 |
| APIが遅い | 大きなJSONを毎回brotli-6 | ページネーション、レベル4、キャッシュ |
IE11・レガシー対応
2026年にIE11サポートはほぼ終了しているが、社内ツールで必要なら gzip_static だけ有効にし、brotliはモダンブラウザ向けにする。gzip_disable はMSIE用の遺物で、現行Edgeは不要。
ngx_brotli のビルド手順(ソースコンパイル)
パッケージマネージャにBrotli付きnginxが無い場合、ソースからモジュールを追加する。本番前にステージングで nginx -t と負荷試験を行うこと。
# Ubuntu 22.04/24.04 向け — 依存パッケージ
sudo apt-get update
sudo apt-get install -y build-essential libpcre3-dev zlib1g-dev libssl-dev git curl
# nginx 本体と ngx_brotli の取得
NGINX_VERSION=1.26.2
curl -O https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
tar xf nginx-${NGINX_VERSION}.tar.gz
git clone --recursive https://github.com/google/ngx_brotli.git
cd nginx-${NGINX_VERSION}
./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_gzip_static_module \
--with-compat \
--add-dynamic-module=../ngx_brotli
make modules
sudo cp objs/ngx_http_brotli_filter_module.so /etc/nginx/modules/
sudo cp objs/ngx_http_brotli_static_module.so /etc/nginx/modules/
/etc/nginx/nginx.conf の先頭付近に load_module を追記する。
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
user www-data;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
nginx -t && systemctl reload nginx で反映する。nginx -V 2>&1 | grep brotli でモジュールがロードされていることを確認する。
worker数と圧縮のCPU関係
動的圧縮は CPUバウンド だ。worker_processes auto; はコア数に合わせるのが基本だが、動的brotliを広く使う場合は worker_cpu_affinity でコア固定し、他の処理との競合を減らす手もある。静的precompressed主体なら worker_connections を増やしてもI/O待ちが主で、圧縮によるCPUスパイクは起きにくい。
Cloudflare / Fastly — ダッシュボード設定の要点
| 設定項目 | Cloudflare | 推奨値 |
|---|---|---|
| Brotli | Speed → Optimization | ON |
| Early Hints | Speed → Optimization | ON(CRPと相性良) |
| Auto Minify (JS/CSS/HTML) | Speed → Optimization | OFF(ビルド済みと競合) |
| Rocket Loader | Speed → Optimization | 状況次第(JS実行順を変える) |
| Cache Rules | Rules | /assets/* は Edge TTL 1年 |
オリジンが Content-Encoding: br で返す場合、Cloudflareは 既に圧縮されたレスポンスをそのままキャッシュ する。オリジンが非圧縮で Cache-Control のみ付与している場合、CFが初回ミス時にエッジで圧縮する。パターンB(オリジン静的圧縮) の方がミス時のTTFBが安定する。
レスポンスヘッダーで cf-cache-status: HIT かつ content-encoding: br が揃っているか、本番リリース後に必ずスポットチェックする。
セキュリティと圧縮 — BREACH / CRIME の注意
HTTPS上の圧縮は理論上 BREACH(HTTPレスポボディ)や CRIME(TLSレベル)攻撃の対象になり得る。秘密トークンを反射するAPIレスポンスを動的圧縮し、攻撃者が長さを計測できる条件は稀だが、以下を守る。
- セッションID・CSRFトークンをURLクエリに載せない
- 機密フィールドはレスポンスから除外
- 極秘データは
Cache-Control: no-store+ 圧縮オフも検討
一般的な静的JS/CSS圧縮でのリスクは低い。問題になるのは ユーザー入力がエコーされる動的HTML だ。
本番導入チェックリスト
curlで主要アセットの Content-Encoding と転送バイト数のベースラインを記録する
CIに brotli-11 / gzip-6 の事前圧縮ステップを追加し .br / .gz をデプロイする
nginxで gzip_static / brotli_static を有効化し /assets/ の動的圧縮をオフにする
Vary: Accept-Encoding と Cache-Control をCDN設定と整合させる
動的圧縮は brotli-4〜5 / gzip-5〜6 に制限し TTFB を監視する
Lighthouse と WebPageTest で転送サイズ・LCP を Before/After 比較する
[HTTP/2・HTTP/3](/blog/http2-http3-違い-パフォーマンス比較/) が有効かつ圧縮と併用できているか確認する
関連記事
この記事は Webパフォーマンス テーマの一環です。あわせて読むと理解が深まる関連記事をまとめました。
| トピック | 記事 |
|---|---|
| Speculation Rules API 完全ガイド | Speculation Rules API 完全ガイド|prefetch・prerender・Chrome 144 prerender_until_script |
| fetchpriority属性でLCP画像を最適化する | fetchpriority属性でLCP画像を最適化する|2026年実践ガイド |
| Web Vitals INP改善ガイド | Web Vitals INP改善ガイド|scheduler.yield・Long Task・Input Delay対策(2026) |
| content-visibility: auto 完全ガイド | content-visibility: auto 完全ガイド|遅延レンダリングと長尺ページ最適化 2026 |
| Server-Timing API 完全ガイド | Server-Timing API 完全ガイド|Express ミドルウェアと Chrome DevTools 連携 |
| CSS/JSを圧縮(Minify)するメリット | CSS/JSを圧縮(Minify)するメリット|サイト高速化の基本 |
| Service Worker キャッシュ戦略ガイド | Service Worker キャッシュ戦略ガイド|PWA の cache-first・network-first・SWR・Workbox 設計 |
| View Transitions API 完全ガイド | View Transitions API 完全ガイド|SPA・MPA ページ遷移と Astro/React 統合 |
| WebP変換で画質が落ちる原因と最適な設定 | WebP変換で画質が落ちる原因と最適な設定 |
| 画像圧縮とWebP変換 | 画像圧縮とWebP変換|PageSpeed改善の基本 |
| APIキャッシュ設計ガイド | APIキャッシュ設計ガイド|ETag・Cache-Control・304・CDNキャッシュキーの実務 |
| GraphQL N+1 問題の解決 | GraphQL N+1 問題の解決|DataLoader バッチング実装と Apollo Server 完全ガイド |
まとめ
BrotliはGzipより テキストで15〜25%小さく なり、デコードコストは同等。弱点はエンコードの重さで、動的brotli高レベルはTTFBを壊す。
2026年のベストプラクティスは次のとおり。
- ビルド時に
.br(level 11)と.gz(level 6)を生成 - nginxで
brotli_static on;gzip_static on;を使い静的配信 - SSR/APIだけ動的圧縮(brotli 4〜5、gzip 5〜6)
- CDNは圧縮済みをキャッシュ、二重圧縮を避ける
- 計測で効果確認 — 転送バイトとLCPをCRP最適化とセットで見る
圧縮は安価で効果が大きいが、万能ではない。ボトルネックが画像やサードパーティスクリプトなら、そちらを先に直す。テキストアセットが支配的なSPA・ドキュメントサイトでは、本記事の静的Brotli構成を入れるだけで 初回ロード数十〜数百ms の改善が見込める。
次のアクション: 本番ドメインで curl -sI -H 'Accept-Encoding: br' https://your-domain/assets/ を実行し、content-encoding: br が返るか確認する。返らなければ .br ファイルのデプロイと brotli_static 設定から着手してほしい。