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

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:
"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:
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 😉