LQIP vs BlurHash Placeholders: Choosing an Image Placeholder
This comparison sits within Image Decoding & Placeholders, part of Image & Media Optimization. Placeholders fill the space an image will occupy while it loads. Their job is perceptual — make the page look composed rather than full of holes — and structural — reserve space so nothing shifts when the image arrives. They do not make the real image load faster, and badly chosen placeholders can make pages slower.
There are four common techniques. A dominant colour is a single background colour extracted from the image. LQIP (low-quality image placeholder) is a tiny version of the image — often 20–40 pixels wide — inlined as a data URI and blurred with CSS. BlurHash encodes a blurred representation into a 20–30 character string, decoded to pixels by JavaScript. ThumbHash is a newer variant with better colour and alpha fidelity in a similar size. Each trades bytes, JavaScript and fidelity differently.
Rapid Diagnosis
- Measure placeholder bytes in HTML. Search the HTML for
data:image; sum sizes. Hundreds of LQIPs can add tens of kilobytes. - Check placeholder JavaScript. BlurHash/ThumbHash decoders and the canvas work to render them run on the main thread; check whether they run at startup.
- Check LCP attribution. If LCP fires early on a blurry preview and then again on the real image, the placeholder is acting as an LCP candidate.
- Check CLS. Placeholders without the image's aspect ratio shift when replaced.
Root Cause Analysis
1. LQIP inflating HTML. Inlining a data URI for every grid image makes the HTML — which carries the LCP text and critical CSS — larger and slower.
2. Runtime decoders at startup. Decoding dozens of BlurHashes to canvas during load adds main-thread work right when INP and TBT matter.
3. Placeholders as LCP candidates. A visible <img> LQIP large enough to be the biggest element is recorded as an LCP candidate; LCP looks fast in the lab, but when the real image replaces it, a later LCP entry wins.
4. Unsized placeholders. Placeholder and image with different boxes produce layout shift.
Step-by-Step: Implementing Placeholders Well
1. Default to aspect ratio plus dominant colour
<div class="card-media" style="--ph:#c9b8a4">
<img src="/img/p42-400.avif" width="400" height="300" loading="lazy" decoding="async" alt="Leather boot">
</div>
.card-media { aspect-ratio: 4 / 3; background: var(--ph); }
.card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* trade-off: a flat colour conveys tone but not composition; it is the right
default for grids where many images load quickly. */
2. Use LQIP selectively, as a CSS background
For a handful of large images (the hero, a feature image), a tiny blurred preview improves perceived quality. Put it on the container as a background so it cannot be an <img> LCP candidate.
.hero-media { aspect-ratio: 16 / 9; background: center / cover no-repeat url("data:image/webp;base64,UklGRj4AAABXRUJQVlA4…"); }
.hero-media img { opacity: 1; transition: opacity 200ms; }
/* trade-off: CSS background-image placeholders can themselves be LCP candidates
if large and visible; a blurred low-content background is usually smaller in
effective area or replaced quickly, but check LCP attribution after adding it. */
3. Decode BlurHash/ThumbHash at build time, not runtime
If your data source provides BlurHash strings, decode them to a tiny image or a few gradient stops on the server or at build time, and ship CSS — not a decoder.
// build step: turn a ThumbHash into a data URL once.
import { thumbHashToDataURL } from 'thumbhash';
const placeholder = thumbHashToDataURL(Buffer.from(product.thumbhash, 'base64'));
// trade-off: the data URL is larger than the hash string (a few hundred bytes),
// but it needs no client JavaScript and renders on first paint.
4. Fade in the real image without layout change
Swap or fade the real image over the placeholder within the same box; never change dimensions.
Verification
Check HTML size before and after, the absence of placeholder decoders in startup traces, LCP attribution (the final LCP should be the real image, not a preview), and CLS (zero shift on image arrival, verified with Layout Shift Regions).
Worked Example: A Real Estate Listings Page
A listings page rendered 60 cards with BlurHash placeholders decoded in the browser by a 7KB library onto 60 small canvases at startup. Startup scripting included 110ms of decoding on mid-tier phones, and some cards shifted when photos loaded because canvases had a fixed height while photos varied. The team replaced runtime decoding with build-time dominant colours, kept a single LQIP for the featured listing, and gave every card aspect-ratio: 3 / 2 with object-fit: cover. TBT fell by 120ms, CLS on the page dropped from 0.07 to 0.01, and users perceived the page as equally polished in a quick internal survey.
Placeholders and the LCP Metric
Placeholders interact with LCP in ways worth testing. An LQIP rendered as an <img> — or even as a large visible background — can be recorded as an LCP candidate when it paints. If the real image then paints in the same box, it produces a new LCP entry at its later time, and the final LCP is the real image, so the placeholder does not "fake" the metric. But if the user interacts before the real image paints, LCP stops at the placeholder, recording a misleadingly early value. For stable, honest measurement, keep placeholders as low-detail backgrounds on the container and make sure the real image is optimised as the true LCP element.
Common Mistakes
- LQIP on every image. Inflates HTML for minimal perceived benefit in dense grids.
- Runtime decoders at load. Main-thread cost when it matters most.
- Placeholders as
<img>LCP candidates. Confuses LCP measurement. - Mismatched aspect ratios. Layout shift when the real image arrives.
Edge Cases
Transparent images. Dominant-colour placeholders look wrong for logos and cut-outs on transparent backgrounds; use no placeholder or ThumbHash (which encodes alpha).
Dark mode. Light-toned placeholders flash on dark themes; derive placeholders from the image itself, not a fixed grey.
Image CDNs. Some CDNs generate dominant colours and tiny previews via URL parameters, removing build steps.
Low-bandwidth users. Placeholders are seen longer on slow networks; that is where LQIP's composition preview adds the most value.
FAQ
Do placeholders improve Core Web Vitals?
They improve CLS when they reserve space correctly. They do not improve LCP for the real image. Their main benefit is perceived performance.
Is BlurHash worth it over LQIP?
BlurHash strings are tiny in data payloads, which matters for APIs and mobile apps. For web pages, decoding them in the browser costs JavaScript; build-time conversion gives you the visual without the runtime cost.
How small should an LQIP be?
Around 16–40 pixels wide, heavily compressed (WebP or AVIF), typically 200–800 bytes, displayed blurred. Larger previews add bytes without much perceptual gain.
Should placeholders fade out?
A short opacity transition of the real image over the placeholder looks smooth and costs little (opacity is composited). Avoid animating size or blur radius, which repaint.
Do frameworks provide placeholders?
Yes — Next.js placeholder="blur", Nuxt Image placeholder, Astro and Gatsby image plugins. Check whether they inline data URIs per image and whether that HTML cost is acceptable for your page.
What about skeleton shimmers for images?
An animated shimmer is a placeholder too. Animate it with transforms, stop it under reduced motion, and keep it in a sized container; for images, a static dominant colour is often better.
Related
- Fixing CLS from skeleton screens that resize — sizing placeholders correctly.
- Using decoding=async and the decode() API — how the real image replaces the placeholder.
- Responsive images with Nuxt Image and Astro — framework placeholder options.