Building a Smart BlurImage Component in Next.js 15 App Router

AndryAndry Dina
13
Next.js Image Preloading

Stop shipping blurry or broken images in Next.js

We've all been there:

  • Images flash as blurry during load.
  • Broken URLs silently fail.
  • Fallback handling is a mess.
  • UX takes a hit.

In this quick guide, i'll share a dead-simple reusable BlurImage component i've been using in all my Next.js 14+ App Router projects. It handles:

✅ Blur-up placeholder while loading
✅ Fallback avatar if the image fails
✅ Fully typed, drop-in for any <Image /> component


The problem with image loading in production

When dealing with dynamic content (user profiles, external images, CMS sources), you can’t always trust the image URL.

Sometimes:

  • The image takes time to load (slow networks)
  • The image fails (broken links, expired URLs)
  • Users see either a broken icon or nothing

The native <Image /> component in Next.js is great for optimization — but you still need a tiny UX layer on top.


The solution: BlurImage component

Let’s build a very simple wrapper around Next.js <Image /> using only App Router compatible code:

typescript
1"use client";
2
3import { useEffect, useState } from "react";
4import Image, { ImageProps } from "next/image";
5import { cn } from "@/lib/utils";
6
7export default function BlurImage(props: ImageProps) {
8  const [loading, setLoading] = useState(true);
9  const [src, setSrc] = useState(props.src);
10
11  useEffect(() => setSrc(props.src), [props.src]);
12
13  return (
14    <Image
15      {...props}
16      src={src}
17      alt={props.alt}
18      className={cn(
19        "rounded-lg object-cover object-center",
20        props.className,
21        loading ? "blur-[2px]" : "blur-0"
22      )}
23      onLoad={() => setLoading(false)}
24      onError={() => {
25        setSrc(`https://avatar.vercel.sh/${props.alt}`);
26      }}
27    />
28  );
29}

What’s happening here?

  • loading state controls whether the blur effect is active.
  • If the image loads, we simply turn off the blur.
  • If loading fails, we swap in a fallback avatar from vercel.sh.
  • Fully reusable for any image source.

"Note: cn() here is a utility to combine Tailwind classes — feel free to replace it with your own className merge function."

Why not use native placeholder="blur"?

Good question.

Next.js built-in blur placeholders work if you provide the blurDataURL. But for dynamic external URLs, you may not always have one available. This component gives you a consistent experience even when you can't precompute placeholders.

Usage example

Let’s say you’re rendering user avatars:

tsx
1import BlurImage from "@/components/ui-components/BlurImage";
2
3export function ProfilePage() {
4  return (
5    <main>
6      <div className="flex items-center gap-4">
7        <BlurImage
8          src="/profile-pic.jpg"
9          alt="User profile"
10          width={100}
11          height={100}
12          className="border-2 border-gray-200"
13        />
14        <div>
15          <h1>John Doe</h1>
16          <p>Software Engineer</p>
17        </div>
18      </div>
19    </main>
20  );
21}

✅ If user.profileImageUrl is valid: clean, sharp image after load ✅ If it fails: fallback avatar using user name ✅ Always smooth: initial blur transition

Small, simple, powerful

Sometimes it's these tiny components that:

  • Save you hours of debugging broken image
  • Give your app that "polished" feel
  • Improve perceived performance with just a few lines of code

If you found this useful, follow the blog or join my newsletter for more practical Next.js patterns like this one. Also — feel free to share this with your dev friends who keep shipping broken images 😉

Similar articles

Never miss an update

Subscribe to receive news and special offers.

By subscribing you agree to our Privacy Policy.