// blog/design/
Back to Blog
Design · Published August 25, 2026 · 9 min read · By Toine

Image Placeholder Blur: Faster Perceived Loading

Image Placeholder Blur: Faster Perceived Loading

When images load slowly, users see a blank space, a broken icon, or a sudden pop-in that shifts the whole layout. None of those feel good. Blur-up placeholders fix all three by showing a tiny blurred preview that fades into the full-resolution image when it loads.

The trick works because a heavily blurred image keeps the overall colour and dominant shapes. Your brain fills in the rest. The result feels much faster than empty space, even when actual load time is identical.

Medium popularised this approach in 2015 and it became standard for image-heavy sites. Modern implementations are tiny (20 to 200 bytes per placeholder), easy to automate, and improve perceived performance without affecting real load speed.

* * *

BlurHash: The Most Popular Implementation

BlurHash is an algorithm that encodes an image into a compact string of 20 to 30 characters. This string can be decoded in the browser to render a blurred placeholder at any resolution.

A typical BlurHash looks like: LEHV6nWB2yk8pyo0adR*.7kCMdnj

This 28-character string encodes enough information to render a recognizable blur of the original image. Store it in your database alongside the image URL, and render it instantly while the full image loads.

How to generate a BlurHash:

Server-side during image upload: `javascript import { encode } from 'blurhash'; import sharp from 'sharp';

const { data, info } = await sharp('photo.jpg') .raw() .ensureAlpha() .resize(32, 32, { fit: 'inside' }) .toBuffer({ resolveWithObject: true });

const hash = encode( new Uint8ClampedArray(data), info.width, info.height, 4, 3 // component counts ); `

How to render in the browser: `javascript import { decode } from 'blurhash';

const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32); const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32; const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(32, 32); imageData.data.set(pixels); ctx.putImageData(imageData, 0, 0); `

Optimize your full-resolution images with the Image Compressor so the transition from blur to sharp happens faster.

Progressive image loading stages from blur to sharp on a photo gallery
Progressive image loading stages from blur to sharp on a photo gallery
* * *

LQIP: Low Quality Image Placeholder

LQIP takes a different approach. Instead of an encoded hash, it uses an actual image file scaled down to a tiny size (typically 20 to 40 pixels wide) and heavily compressed. The resulting file is 200 to 800 bytes.

The browser loads this tiny image, scales it up to fill the container (which naturally blurs it due to interpolation), and then swaps in the full image when ready.

Advantages over BlurHash: - No client-side decoding JavaScript required - Can be inlined as a base64 data URI directly in HTML - Preserves more color accuracy than BlurHash - Works in email and contexts without JavaScript

Disadvantages: - Slightly larger than BlurHash (200 to 800 bytes vs 20 to 30 bytes) - Requires generating and storing an actual image file - Less consistent visual quality at extreme sizes

Generate LQIP with sharp: `javascript const placeholder = await sharp('photo.jpg') .resize(20) .jpeg({ quality: 20 }) .toBuffer();

const dataUri = data:image/jpeg;base64,${placeholder.toString('base64')}; `

The data URI can be embedded directly in the src attribute of an tag or used as a CSS background-image. When the full image loads, swap the src attribute.

Resize your images to the exact dimensions needed for your layout with the Image Resizer. Serving correctly sized images reduces loading time and makes the blur-to-sharp transition smoother.

Key takeaway

LQIP takes a different approach.

* * *

CSS-Only Blur Techniques

If you do not want to generate placeholder images at all, CSS offers simpler alternatives.

Dominant color background: `css .image-container { background-color: #3a7d44; / extracted from image / aspect-ratio: 16/9; } `

This is the simplest approach. Extract the dominant color during upload and set it as the container's background. The image loads on top of a matching color rather than white space.

CSS blur filter on thumbnail: `css .placeholder { filter: blur(20px); transform: scale(1.1); / hide blur edges / transition: opacity 0.3s ease; } .placeholder.loaded { opacity: 0; } `

Gradient placeholder: `css .image-container { background: linear-gradient(135deg, #667eea, #764ba2); } `

A gradient approximating the image's color range provides a more interesting placeholder than a solid color. Some tools can automatically extract a gradient from an image.

Skeleton loading with animation: `css .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } `

Skeleton loading does not attempt to preview the image content. Instead, it signals that something is loading, which is better than a blank space.

Minify your placeholder CSS with the CSS Minifier to keep the overhead minimal.

* * *

Framework-Specific Implementations

Most modern frameworks have built-in support for image placeholders.

Next.js: the next/image component supports blur placeholders natively: `jsx import Image from 'next/image';

Hero image `

For local images imported directly, Next.js generates the blur placeholder automatically at build time. For remote images, you need to provide the blurDataURL.

Gatsby: gatsby-plugin-image handles blur-up placeholders with its BLURRED layout option. It generates the placeholder during the build process.

Nuxt: the @nuxt/image module supports placeholder prop with automatic LQIP generation.

Astro: the built-in Image component does not generate blur placeholders automatically. Use the BlurHash approach with a custom component.

Plain HTML/JavaScript: use the Intersection Observer API to lazy-load images and swap placeholder data URIs for full URLs when the image enters the viewport: `javascript const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.add('loaded'); observer.unobserve(img); } }); });

document.querySelectorAll('img[data-src]').forEach(img => { observer.observe(img); }); `

Code snippet showing BlurHash implementation alongside rendered preview
Code snippet showing BlurHash implementation alongside rendered preview
* * *

Measuring the Impact on Core Web Vitals

Blur placeholders primarily affect two Core Web Vitals.

Cumulative Layout Shift (CLS): without a placeholder, an image loading causes the content below it to jump. With a placeholder that has the correct aspect ratio, the space is already reserved. CLS improvement is often the biggest measurable benefit. Set explicit width and height attributes on your images (or use aspect-ratio in CSS) alongside the placeholder.

Largest Contentful Paint (LCP): if the hero image is the LCP element, the blur placeholder does not change the LCP time because LCP measures when the actual content becomes visible, not the placeholder. However, the perceived experience is dramatically better because users see a blurred preview instead of nothing.

To measure the actual impact:

  1. Run Lighthouse before implementing placeholders (baseline)
  2. Implement placeholders with proper aspect ratios
  3. Run Lighthouse again and compare CLS and LCP scores
  4. Check field data in Chrome UX Report or Google Search Console for real-user metrics

Common mistake: implementing blur placeholders without setting aspect ratios on the images. The placeholder itself can cause layout shift if the container changes size when the full image loads. Always set dimensions.

Another mistake: inlining large base64 placeholders (over 2 KB) in the HTML. This bloats the initial HTML document and can slow down the First Contentful Paint. Keep inline placeholders under 500 bytes. For larger placeholders, load them as separate tiny image files.

* * *

FAQ

Which approach should I use: BlurHash, LQIP, or CSS?

For image-heavy sites with a build pipeline, LQIP is the most practical because it works everywhere without client-side JavaScript. For dynamic images (user uploads), BlurHash is better because the compact string is easy to store in a database. For simple sites without many images, CSS dominant-color backgrounds are sufficient and require the least effort.

Do blur placeholders affect SEO?

No. Search engines index the alt text and the final image URL, not the placeholder. As long as your tags have proper alt attributes and the final src points to the correct image, placeholders have no SEO impact. The CLS improvement from proper placeholders can indirectly help SEO through better Core Web Vitals scores.

How do I generate blur placeholders for thousands of existing images?

Write a batch script using sharp (Node.js) or Pillow (Python) that processes all images in your content directory. Generate either BlurHash strings or LQIP data URIs and store them in a JSON file or database. Most teams run this as a one-time migration and then generate placeholders automatically for new uploads.

Can blur placeholders work with responsive images?

Yes. The placeholder covers the container area regardless of which responsive image size ultimately loads. Since the placeholder is a blurred approximation, the same placeholder works for all breakpoints. The aspect ratio should match the image's aspect ratio, not a specific pixel dimension.

Key takeaway

### Which approach should I use: BlurHash, LQIP, or CSS.