Definition
This is the pattern to use when you want to lay down a small, blurry preview before a large image finishes loading — to reduce the "empty gap" feeling — but you also want to avoid slowing down the first response by generating that preview on the server. The first visitor is served quickly without a preview, and the generated preview is quietly populated into the cache after the response is sent, so it's used from the second visit onward.
More precisely, LQIP (Low Quality Image Placeholder) is a UX pattern that shows a small blurred / base64 (an image inlined as a text string) image before the real image. SSR (Server-Side Rendering, where the server builds the HTML ahead of time and sends it) stale-while-populate (a strategy that uses the value immediately if present, and otherwise fills it in after the response) does only a synchronous peek of the cache at response time (it just checks whether it already exists, without generating anything if it doesn't); on a miss (not in cache), it warms (pre-heats the cache) after the response with after() (a Next.js function that runs after the response is sent), so the placeholder is attached from the next visit onward.
Why it's needed
When a hero or first-card image loads late, even if the layout is reserved, the empty-gap feeling lingers. Generating the blur synchronously on every SSR inflates TTFB (Time To First Byte, the time from request until the first byte arrives) because of the upstream (source image server) fetch. peek + warm serves the first visit fast without a placeholder, then fills in the blur from subsequent visits.
How it works
Relationship with LCP (Largest Contentful Paint, the metric for when the largest content element on screen is painted) (important):
- Chrome excludes low-entropy (low information content, i.e. simply blurry and stretched) images from LCP candidates.
- An
<Image>withwidth/heightalready handles CLS (Cumulative Layout Shift, how much elements get pushed around and the screen jitters during loading), so blur does not further reduce CLS. - Only Speed Index (a metric for how quickly the screen fills in visually) may improve slightly — if the goal is a Lighthouse score, lightening the real image comes first.
Practical application
- Apply conditional
placeholder="blur"/blurDataURLto only one LCP candidate. - warm must never throw — so a background failure doesn't break the user's response.
Trade-offs
| Choice | Benefit | Cost |
|---|---|---|
| stale-while-populate | 0 added TTFB | no blur on first visit |
| synchronous LQIP every SSR | blur from first visit | TTFB / upstream load |
| 16px webp base64 | a few hundred bytes inline | must accept no LCP impact |
When not to use it
- Investing resources in LQIP purely to improve LCP milliseconds — the effect is negligible.
- Synchronous warm on every card — worsens TTFB.
Common mistakes
- Concluding "failure" because LCP didn't drop after adding blur — the low-entropy exclusion is normal.
- Upstream fetch in SSR without
after()— increases TTFB. - Confusing the CLS expectation of a dimensionless
<img>with next/image.
Related concepts
- server-image-proxy-transcoding-cache — real image bytes / LCP
- cls-skeleton-layout-reservation — skeleton / layout reservation
- largest-contentful-paint — LCP candidate rules