Blur-Up Placeholders with LQIP
On a fast connection, images appear almost with the page. On a slow one, a static site with lazy-loaded images shows a column of empty rectangles that fill in, one by one, as the reader scrolls. The rectangles are correctly sized — the layout does not shift — but they look broken. Low-quality image placeholders (LQIP) fill each rectangle immediately with a tiny, blurred version of the image, so the page looks complete from the first frame and images sharpen in place as they arrive.
This guide generates placeholders at build time in Astro and Eleventy, compares three kinds — blurred micro-images, dominant colours and CSS gradients — on size and look, inlines them without inflating the HTML, fades in the real image without layout work, and looks honestly at what placeholders do and do not do for Core Web Vitals. It is part of Image Optimization Pipelines in Astro. The site is a photography-heavy travel blog built with Astro, with an average of 14 images per post.
Prerequisites
- An image pipeline that already produces sized, modern-format images:
astro:assetsor@11ty/eleventy-img. sharpavailable at build time.- Images with
widthandheightattributes, so placeholders fill boxes that are already reserved — see Reserving Space for Images and Embeds to Stop Layout Shift.
Three Kinds of Placeholder
| Kind | How | Size per image | Look |
|---|---|---|---|
| Blurred micro-image | 20 px wide WebP, base64, CSS blur | 350–700 bytes | recognisable shapes and colours |
| Dominant colour | one hex colour as background | ~20 bytes | flat colour block |
| Gradient (ThumbHash / BlurHash decoded to CSS) | 4–9 colours as a CSS gradient | 100–250 bytes | soft colour regions |
The travel blog used micro-images for photos in the article body and dominant colours for thumbnails in lists, where 30 blurred micro-images per page added noticeably to the HTML.
Step 1: Generate Placeholders at Build Time
In Astro, a small helper runs sharp on the source image during the build and returns a data URI and the dominant colour. Results are cached by source path and file hash so unchanged images are not reprocessed, as described in Caching Processed Images Between Builds:
// src/lib/placeholder.ts
import sharp from 'sharp';
export async function placeholder(path: string) {
const img = sharp(path).rotate();
const { dominant } = await img.stats();
const tiny = await img.clone().resize(20, null, { fit: 'inside' }).webp({ quality: 40 }).toBuffer();
return {
color: `rgb(${dominant.r} ${dominant.g} ${dominant.b})`,
dataUri: `data:image/webp;base64,${tiny.toString('base64')}`
};
}
In Eleventy, the same function is called from the image shortcode that already runs eleventy-img, and the result is written into the same <img> markup.
Twenty pixels wide was the sweet spot: at 16 pixels, placeholders of busy street scenes turned into mud; at 32, the size doubled with little visible gain once blurred.
Step 2: Render Without Hurting the Real Image
The placeholder goes on the image element itself as a background, so no extra elements or layout are involved:
---
const { src, alt, width, height, loading = 'lazy' } = Astro.props;
const ph = await placeholder(src.fsPath);
const img = await getImage({ src, width: 1200, format: 'avif' });
---
<img src={img.src} srcset={img.srcSet.attribute} sizes="(min-width: 50em) 46rem, 100vw"
width={width} height={height} alt={alt} loading={loading} decoding="async"
style={`background: ${ph.color} url(${ph.dataUri}) center / cover no-repeat;`}
class="lqip" />
img.lqip { background-blend-mode: normal; filter: none; }
img.lqip:not([data-loaded]) { image-rendering: auto; }
Because the placeholder is the <img> element's own background, the real image paints over it the moment it decodes — no swap, no JavaScript and no layout change. The browser scales the 20-pixel image up smoothly, which blurs it enough on its own; an extra CSS filter: blur() would apply to the real image too and needs a wrapper to avoid, so the site skipped it.
A fade-in on load is possible with a class added in a load listener, but it delays the moment the real image is fully visible and needs JavaScript for every image. The site tried a 200 ms fade and removed it after readers on fast connections described images as "sluggish".
Step 3: Treat the LCP Image Differently
A placeholder does not help LCP: the metric waits for the real image. Worse, a large base64 placeholder in the HTML head of the page makes the HTML bigger and can delay the start of the image request by a few milliseconds. For the LCP image, the site uses only the dominant colour (20 bytes), loading="eager" and fetchpriority="high", and puts the effort into the real image's size and format, following Lazy-Loading Images Without Hurting LCP. Placeholders are for everything below it.
Step 4: Keep the HTML Budget in Check
Fourteen blurred placeholders per post added about 7.3 KB to the HTML before compression and 5.8 KB after — base64 data compresses poorly. That was acceptable for article pages but not for list pages with 30 thumbnails, where dominant colours were used instead (0.6 KB total). A simple rule in the image component chooses: blurred placeholder for images wider than 400 CSS pixels in the article body, dominant colour for thumbnails and the LCP image.
What Readers Noticed
Placeholders are a perceived-performance change, so the useful measurements are about perception and behaviour rather than Core Web Vitals. In a small moderated test with eight readers on a throttled phone, seven preferred the version with blurred placeholders and described the other as "loading" or "broken" while scrolling. In analytics, scroll depth on long posts rose from 62% to 67% median, and the share of readers who reached the last image rose from 41% to 46%. LCP and CLS did not change measurably, which was the expected result: the images were already sized and prioritized correctly.
Dark Mode and Transparent Images
Two details catch sites with a dark theme. Dominant-colour backgrounds computed from bright photos are fine in both themes, but placeholders for images with transparency — logos, diagrams exported as PNG, product shots on transparent backgrounds — should not get a background at all, or the placeholder colour shows through the finished image. The helper checks sharp's hasAlpha metadata and skips the placeholder for transparent images. And screenshots of light-themed interfaces produce near-white placeholders that flash brightly on a dark page; for screenshots the site uses the page's own muted surface colour instead of the image's dominant colour, which reads as "loading" rather than as a white flash.
Measured Impact
| Measure | No placeholders | Micro-images + dominant colours |
|---|---|---|
| LCP p75 mobile (field) | 1.72 s | 1.73 s |
| CLS p75 | 0.01 | 0.01 |
| HTML per article, compressed | 18.2 KB | 24.0 KB |
| Median scroll depth on long posts | 62% | 67% |
| Readers reaching the last image | 41% | 46% |
| Build time added (warm cache) | — | 0.8 s |
Pitfalls & Rollback
- Expecting better LCP. Placeholders do not change it; optimize the real image for that.
- Base64 placeholders on every thumbnail. List pages bloat; use dominant colours there.
- CSS blur on the image element. It blurs the real image too; rely on upscaling or use a wrapper.
- Placeholders without reserved dimensions. A placeholder in a box of the wrong size still shifts when the real image arrives.
- Rollback: the placeholder is one style attribute in the image component; removing it restores plain images.
Conclusion
Blur-up placeholders make a static site with many lazy images look complete while it loads, for a few hundred bytes per image, generated once at build time. Use tiny blurred images for large photos in the body, dominant colours for thumbnails and the LCP image, paint them as the image's own background so nothing moves, and do not expect them to change LCP. Readers on slow connections will notice the difference even if Lighthouse does not.
FAQ
What is an LQIP?
A low-quality image placeholder, a very small version of an image, typically 16 to 32 pixels wide, shown blurred and stretched while the real image loads. It gives readers an impression of the image immediately and makes the transition feel smoother.
Do blurred placeholders improve LCP?
No. LCP measures when the real image renders, and a placeholder does not count. Placeholders improve perceived loading and can prevent a blank space, but a large inline placeholder in the HTML can slightly delay LCP by making the HTML bigger.
How big should a placeholder be?
Small enough to inline cheaply, typically under 1 KB. A 20-pixel-wide WebP or a CSS gradient derived from the image's dominant colours usually lands between 200 and 700 bytes, which is negligible even with dozens of images on a page.
Should the LCP image have a blurred placeholder?
Usually a dominant-colour background is enough for the LCP image. The effort is better spent making the real image load fast with priority hints and the right size. Blur-up placeholders are most useful for images further down the page that load lazily.
Related
- Parent: Image Optimization Pipelines in Astro — the full pipeline.
- Lazy-Loading Images Without Hurting LCP — which images to lazy-load.
- Art Direction with the Picture Element — different crops per viewport.
- Caching Processed Images Between Builds — keeping placeholder generation cheap.
- Building an Image CDN Pipeline for Static Sites — placeholders with an image CDN.