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

npmixnpmix
47
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
"use client";

import { useEffect, useState } from "react";
import Image, { ImageProps } from "next/image";
import { cn } from "@/lib/utils";

export default function BlurImage(props: ImageProps) {
  const [loading, setLoading] = useState(true);
  const [src, setSrc] = useState(props.src);

  useEffect(() => setSrc(props.src), [props.src]);

  return (
    <Image
      {...props}
      src={src}
      alt={props.alt}
      className={cn(
        "rounded-lg object-cover object-center",
        props.className,
        loading ? "blur-[2px]" : "blur-0"
      )}
      onLoad={() => setLoading(false)}
      onError={() => {
        setSrc(`https://avatar.vercel.sh/${props.alt}`);
      }}
    />
  );
}

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
import BlurImage from "@/components/ui-components/BlurImage";

export function ProfilePage() {
  return (
    <main>
      <div className="flex items-center gap-4">
        <BlurImage
          src="/profile-pic.jpg"
          alt="User profile"
          width={100}
          height={100}
          className="border-2 border-gray-200"
        />
        <div>
          <h1>John Doe</h1>
          <p>Software Engineer</p>
        </div>
      </div>
    </main>
  );
}

✅ 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.