定義
大きな画像が表示され切る前に、小さくぼやけたプレビューを先に敷いて「空白」の印象を減らしたいが、そのプレビューをサーバーで生成することで初回レスポンスが遅くなるのは避けたい、というときに使うパターンである。初回訪問者にはプレビューなしでとにかく速く返し、生成したプレビューはレスポンス送信後にこっそりキャッシュへ埋めておき、2回目の訪問から使う。
正確には、LQIP(Low Quality Image Placeholder、低画質画像プレースホルダー) は本画像の前に小さなblur(ぼかし処理)/base64(画像をテキスト文字列としてインライン埋め込みした形)画像を見せるUXパターンである。SSR(Server-Side Rendering、サーバーがHTMLをあらかじめ生成して送る方式)stale-while-populate(あれば即座に使い、なければレスポンス後に埋めておく戦略)は、レスポンス時点でキャッシュを同期peek(キャッシュにすでにあるかだけを覗き、なくても新規生成はしない)するだけで、miss(キャッシュにない)なら**after()(レスポンス送信後に実行されるNext.jsの関数)でレスポンス後にwarm(キャッシュを事前に温めておく)** し、次回訪問からplaceholderを付ける。
なぜ必要か
ヒーロー・最初のカード画像が遅れて表示されると、レイアウトは確保されていても空白感が残る。blurをSSRごとに同期生成すると、upstream(元画像サーバー)のfetchがTTFB(Time To First Byte、リクエスト後に最初のバイトが届くまでの時間)を大きくする。peek + warmは初回訪問はplaceholderなしで速く、以降の訪問からblurを埋める。
動作原理
LCP(Largest Contentful Paint、画面上で最も大きいコンテンツが描画される時点の指標)との関係(重要):
- Chromeはlow-entropy(情報量が少ない、つまり単にぼかして引き伸ばしただけの)画像をLCP候補から除外する。
width/heightのある<Image>はCLS(Cumulative Layout Shift、ロード中に要素が押されて画面が揺れる度合い)をすでに抑えているため、blurがCLSをさらに減らすことはない。- Speed Index(画面が視覚的にどれだけ速く埋まるかを測る指標)だけが微改善しうる — Lighthouseスコアが目的なら本画像の軽量化が優先。
実務適用
- LCP候補1枚にのみ条件付きで
placeholder="blur"/blurDataURL。 - warmはnever throw — バックグラウンドの失敗がユーザーへのレスポンスを壊さないように。
トレードオフ
| 選択 | 利点 | コスト |
|---|---|---|
| stale-while-populate | TTFB追加0 | 初回訪問はblurなし |
| 同期LQIP every SSR | 初回訪問からblur | TTFB・upstream負荷 |
| 16px webp base64 | 数百バイトをインライン | LCP無影響の認識が必要 |
使ってはいけない場合
- LCPのms改善だけを目的にLQIPへリソースを投入する — 効果はほぼない。
- すべてのカードに同期warm — TTFBが悪化する。
よくある間違い
- blur追加後にLCPが下がらず「失敗」と判断する — low-entropy除外は正常。
after()なしでSSRでupstream fetch — TTFBが増加する。- dimensionなしの
<img>のCLS期待とnext/imageを混同する。
関連概念
- server-image-proxy-transcoding-cache — 本画像バイト・LCP
- cls-skeleton-layout-reservation — スケルトン・レイアウト予約
- largest-contentful-paint — LCP候補ルール