Added LQIP (Low Quality Image Placeholders) to the about page using astro-lqip. Base64-encoded blurred previews now load instantly before the full images, eliminating layout shift and giving a smoother perceived load.
What LQIP does
LQIP (Low Quality Image Placeholder) shows a tiny, blurred version of an image while the full resolution loads. The placeholder is base64-encoded directly in the HTML, so it renders instantly without any network request. When the real image loads, a CSS transition fades it in smoothly.
Why astro-lqip
I went with astro-lqip because it integrates cleanly with Astro’s <Picture> component. Drop-in replacement, same API, just add lqip="base64" to any image. The library handles placeholder generation at build time and injects the CSS needed for blur-to-sharp transitions.
Implementation
The initial setup was straightforward. Swap Astro’s Picture for the library’s version, add the lqip prop, and you get blurred placeholders. I wrapped it in a custom LqipPicture component to set project defaults:
<Picture lqipSize={16} {...props} />Problems I hit
1s transition felt clunky. The default transition duration was too slow for fast connections. Images would load in 200ms but then take another second to fade in. Dropped it to 300ms.
Blur “pop-in” on slow connections. On throttled 3G, the blur itself would appear late. The image container would show empty, then suddenly the blur would pop in, then fade to the real image. Looked janky.
The root cause: astro-lqip’s CSS was loading as an external stylesheet. On slow connections, there’s a race between the HTML (with the base64 placeholder) and the CSS (with the blur filter and transition rules). HTML wins, placeholder renders without blur, then CSS arrives and applies the filter mid-load.
Layout issues with lazy-loaded images. The placeholder wrapper wasn’t filling its container properly. Images would float around instead of respecting the thumbnail’s aspect-ratio box.
Fixes
For the CSS race condition, I moved the critical LQIP styles inline in <head>. Render-blocking CSS means the blur is always ready before the HTML paints. No more pop-in.
/* Inlined in head - render-blocking */
[data-astro-lqip] {
filter: blur(20px);
transition: filter 300ms ease;
}For layout, added a rule to make the LQIP wrapper fill its container:
.press-thumbnail [data-astro-lqip] {
display: block;
width: 100%;
height: 100%;
}Bumped lqipSize from 4 to 16 pixels. At 4px, placeholders were just abstract color blobs. At 16px, you can actually recognize the shape of the image. Adds maybe 150 bytes per image, which is nothing.
The insight
Library CSS isn’t render-blocking by default, and that matters for anything that affects initial paint. The blur filter needs to be ready before the placeholder renders, or you get a flash of unblurred content. Sometimes you need to inline the critical bits.