プログレッシブJPEG最適化:baselineとの違い・mozjpeg/Sharpの実務
JPEGは今でも“最後の互換フォーマット”。その見え方の早さを底上げするのがprogressive
(段階描画)です。この記事では baseline との違い、品質とサブサンプリングの決め方、 Sharp+mozjpegでの一括変換レシピをまとめます。
要点(TL;DR)
- progressive は「粗い→細かい」の多段スキャン。知覚速度が上がるが、サイズはわずかに増えることがある。
- 写真=4:2:0、文字/イラスト=4:4:4。迷ったら 4:2:0 から始めて気になる箇所だけ 4:4:4 に。
- Sharp の
.jpeg({ quality, progressive: true, mozjpeg: true })
で十分。まず Q=78 で基準化。 - LCP画像は
<Image priority />
とキャッシュ設計(Cache-Controlガイド)も併せて最適化。
1. なぜ progressive を使うのか
baseline は“上から順に”一発描画、progressive は“全体を粗く→段階的に精細化”。低帯域でも全体像がすぐ見えるため、 体感は軽くなります。逆に、極小サムネやアイコンではヘッダ増分が相対的に効いてサイズが増えがちで、恩恵が薄いです。
- 向いている:ヒーロー画像・記事リード・ビジュアル主体の写真。
- 向いていない:小さなロゴ/アイコン/顔写真サムネ(15KB未満が多い領域)。
- WebP/AVIF採用でも、フォールバックJPEGは依然有用(AVIF vs WebP)。
2. 最短フロー(実務)
- 基準づくり:写真は Q=78 / 4:2:0、文字がある画像は Q=83 / 4:4:4 を基準に。
- 一括変換:下の Sharp スクリプトで progressive JPEG を出力。
- 検品:人物の目元・斜め線・文字で劣化確認(必要に応じて Q±5)。
- 配信:LCP画像は
priority
と適切なキャッシュ(SWR/immutableの使い分け)。
3. 実装レシピ(コピペOK)
3.1 Sharp で progressive JPEG を一括生成
// scripts/jpeg-progressive.ts
// 依存: npm i -D sharp tsx
// 使い方:
// $env:Q=78; $env:SUB='4:2:0'; npx tsx scripts/jpeg-progressive.ts
// ※ Windows PowerShell 前提(SUB= '4:4:4' も可)
import fs from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
const INPUT = process.env.IN ?? "input"; // 入力フォルダ
const OUTPUT = process.env.OUT ?? "output"; // 出力フォルダ
const Q = Number(process.env.Q ?? 78);
const SUB = (process.env.SUB ?? "4:2:0") as "4:2:0" | "4:4:4";
async function main() {
await fs.mkdir(OUTPUT, { recursive: true });
const files = (await fs.readdir(INPUT)).filter(f => /\.(jpe?g|png|webp)$/i.test(f));
for (const f of files) {
const src = path.join(INPUT, f);
const out = path.join(OUTPUT, path.parse(f).name + ".jpg");
await sharp(src)
.rotate() // EXIFの回転を適用
.toColourspace("srgb") // ICCを落としても色が変わらないよう sRGB 化
.jpeg({
quality: Q, // 目安: 70–82
progressive: true, // Progressive JPEG を有効化
mozjpeg: true, // mozjpeg が有効なら最適化を使用
chromaSubsampling: SUB // 4:2:0(写真)/ 4:4:4(文字・イラスト)
})
.toFile(out);
console.log("→", out);
}
}
main().catch(err => { console.error(err); process.exit(1); });
3.2 package.json と実行例(PowerShell)
// package.json(抜粋)
{
"devDependencies": { "sharp": "^0.33.0", "tsx": "^4.0.0" },
"scripts": {
"jpeg:progressive": "tsx scripts/jpeg-progressive.ts"
}
}
// 実行例(PowerShell)
// $env:Q=80; $env:SUB='4:2:0'; npm run jpeg:progressive
// # 入出力パスの変更
// $env:IN='assets/raw'; $env:OUT='public/img'; npm run jpeg:progressive
3.3 next/image の基本(LCP対策)
// app/(example)/page.tsx(LCP画像の例)
// Progressive JPEG を用意したうえで next/image で配信
import Image from "next/image";
export default function Example() {
return (
<Image
src="/img/hero.jpg" // Progressive JPEG
alt="Hero"
width={1600}
height={900}
priority // LCP対策(初回表示は優先読み込み)
sizes="(min-width: 1024px) 1200px, 100vw"
placeholder="empty" // LQIPと併用するなら blurDataURL を設定
/>
);
}
4. 応用と使いどころ
5. 公開前チェック
- 写真は Q=70–82 / 4:2:0、文字入りは Q=80–85 / 4:4:4 にできている。
- sRGB化して ICC/EXIF を落としても色ズレ/回転崩れがない(
.toColourspace("srgb").rotate()
)。 - 極小画像は baseline にしてサイズ増を回避。
- LCP画像に
priority
を付け、キャッシュは CDN設計に沿っている。
6. まとめ
Progressive JPEG は“先に見える”を作る実戦的な選択です。まずは Q=78 / 4:2:0 / progressive を基準に一括処理し、 文字や図版だけ 4:4:4 で微調整。画像そのものの最適化は /compressor、配信は Cache-Controlガイド と組み合わせて総合最適にしましょう。