Working with images in Next.js is a common task that can significantly impact your application's performance and user experience. The built-in next/image component offers powerful optimization features, but understanding how to use them effectively can be challenging. In this guide, we'll explore image priorities and placeholders in Next.js using a practical scenario: a gallery with 4 images where the first one is large and needs special handling.
The priority attribute in Next.js Image component is a performance optimization that affects how the image loads.
import Image from 'next/image';
const HeroSection = () => {
return (
<Image
src="/hero-image.jpg"
alt="Hero Image"
width={1200}
height={600}
priority
/>
);
};When you add priority to an Image component:
- The image is preloaded with high priority, starting to load as soon as possible
- The image is automatically marked as
loading="eager"instead of the defaultloading="lazy" - It removes the lazy loading behavior that normally defers loading until the image is near the viewport
Priority is particularly useful for:
- Above-the-fold images that will be visible in the initial viewport
- Large hero images
- Any critical images that shouldn't wait for lazy loading
For our gallery example with 4 images, you might only want to set priority on the first, largest image:
<div className="gallery">
{/* Only the first/main image gets priority */}
<Image
src="/main-image.jpg"
alt="Main Image"
width={800}
height={500}
priority
/>
{/* Other images load normally */}
<Image src="/image2.jpg" alt="Image 2" width={400} height={300} />
<Image src="/image3.jpg" alt="Image 3" width={400} height={300} />
<Image src="/image4.jpg" alt="Image 4" width={400} height={300} />
</div>Note: For images already in the initial viewport, adding
priorityisn't strictly necessary since browsers won't lazy-load them anyway. However, it can still be beneficial for the largest/most important image as it gives the browser a hint about resource prioritization.
While images load (especially large ones), it's important to show placeholders to improve perceived performance. Let's explore the options Next.js provides.
The simplest option is the empty placeholder - this is the default if you don't specify anything:
<Image
src="/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
placeholder="empty" // This is the default, so you can omit it
/>This simply shows empty space (the background color of the container) where the image will be until it loads. While simple, it's not very visually appealing.
Next.js allows you to provide a tiny, blurred version of your image as a placeholder using the blurDataURL prop:
<Image
src="/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
placeholder="blur"
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII="
/>But what exactly is a blurDataURL?
A blurDataURL is a base64-encoded image string that starts with data:image/.... It's typically a very small (1x1 pixel in the example above) image that represents the color or a blurred version of the full image. The browser can render this tiny image and stretch it to fill the space until the full image loads.
For our gallery with 4 images, we can use a simple color placeholder for all images:
import Image from 'next/image';
// Simple color placeholder - same for all images
const placeholderImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
const Gallery = () => {
return (
<div className="gallery">
{/* First image loads with priority */}
<Image
src="https://your-cloud-url.com/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
priority
placeholder="blur"
blurDataURL={placeholderImg}
/>
{/* All other images use the same placeholder approach */}
<Image
src="https://your-cloud-url.com/image2.jpg"
alt="Image 2"
width={400}
height={300}
placeholder="blur"
blurDataURL={placeholderImg}
/>
<Image
src="https://your-cloud-url.com/image3.jpg"
alt="Image 3"
width={400}
height={300}
placeholder="blur"
blurDataURL={placeholderImg}
/>
<Image
src="https://your-cloud-url.com/image4.jpg"
alt="Image 4"
width={400}
height={300}
placeholder="blur"
blurDataURL={placeholderImg}
/>
</div>
);
};This approach is simple and effective - it uses the same color placeholder for all images, but still prioritizes loading the first large image.
If you want to use a custom image or icon as your placeholder, you can convert it to a data URI and use it as the blurDataURL:
import Image from 'next/image';
const Gallery = () => {
// Your converted SVG/image as data URI
const iconPlaceholder = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik01IDhoMTR2MTNINXptNS01djJoNHYtMmgtNHptLTUtMWgydjRoLTJ6bTItMmgydjJoLTJ6bTctMmg0djJoLTR6bTEwIDFoLTJ2NGgyeiIvPjwvc3ZnPg==";
// This represents an image icon
return (
<div className="gallery">
{/* First image with priority */}
<Image
src="https://your-cloud-url.com/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
priority
placeholder="blur"
blurDataURL={iconPlaceholder}
/>
{/* Other images with the same icon placeholder */}
<Image
src="https://your-cloud-url.com/image2.jpg"
alt="Image 2"
width={400}
height={300}
placeholder="blur"
blurDataURL={iconPlaceholder}
/>
{/* Add more images... */}
</div>
);
};To get your own image as a data URI:
- Use an online converter like Base64 Image Encoder
- Upload your SVG or small placeholder image
- Get the data URI (starting with
data:image/...) - Use that string as your
blurDataURL
This approach is perfect for using your own custom placeholders with minimal code.
For more complex requirements, here are some advanced approaches:
If you want to create a reusable component that shows a custom placeholder:
import Image, { ImageProps } from 'next/image';
import { useState } from 'react';
// Simple image component with icon placeholder
interface ImageWithPlaceholderProps extends ImageProps {
placeholderSrc: string; // Path to your placeholder image/SVG
}
const ImageWithPlaceholder = ({
placeholderSrc,
alt,
...props
}: ImageWithPlaceholderProps) => {
const [isLoaded, setIsLoaded] = useState(false);
return (
<div style={{ position: 'relative' }}>
{!isLoaded && (
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f5f5f5' }}>
<img src={placeholderSrc} alt={`Placeholder for ${alt}`} style={{ width: '50px', height: '50px' }} />
</div>
)}
<Image
{...props}
alt={alt}
onLoadingComplete={() => setIsLoaded(true)}
style={{ opacity: isLoaded ? 1 : 0, transition: 'opacity 0.3s ease', ...props.style }}
/>
</div>
);
};
// Usage
const Gallery = () => {
return (
<div className="gallery">
<ImageWithPlaceholder
src="https://your-cloud-url.com/large-image.jpg"
placeholderSrc="/image-placeholder-icon.svg" // Your SVG in public folder
alt="Featured Large Image"
width={1200}
height={800}
priority
/>
<ImageWithPlaceholder
src="https://your-cloud-url.com/image2.jpg"
placeholderSrc="/image-placeholder-icon.svg"
alt="Image 2"
width={400}
height={300}
/>
{/* Add more images... */}
</div>
);
};This component is more flexible, as it allows you to use any image from your public folder as a placeholder.
For even more control over the placeholder appearance and behavior:
import { useState } from 'react';
import Image from 'next/image';
const Gallery = () => {
const [imageLoaded, setImageLoaded] = useState(false);
return (
<div className="gallery">
{/* Container with relative positioning */}
<div style={{ position: 'relative', width: '100%', height: 'auto' }}>
{/* Custom placeholder shown when image is loading */}
{!imageLoaded && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div className="loading-spinner">Loading...</div>
</div>
)}
{/* The actual image */}
<Image
src="https://your-cloud-url.com/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
priority
onLoadingComplete={() => setImageLoaded(true)}
/>
</div>
{/* Other images */}
<Image src="https://your-cloud-url.com/image2.jpg" alt="Image 2" width={400} height={300} />
<Image src="https://your-cloud-url.com/image3.jpg" alt="Image 3" width={400} height={300} />
<Image src="https://your-cloud-url.com/image4.jpg" alt="Image 4" width={400} height={300} />
</div>
);
};This approach allows you to show custom loading indicators, spinners, or any other content while the image loads.
For the most sophisticated placeholder experience, especially with remote images, you can use the plaiceholder or blurhash libraries to generate proper blur hashes:
// Server-side component or API route
import { getPlaiceholder } from 'plaiceholder';
// Generate blur data URL for remote image
const getBlurData = async (imageUrl: string) => {
try {
const { base64 } = await getPlaiceholder(imageUrl);
return base64;
} catch (error) {
console.error('Error generating placeholder:', error);
return null;
}
};
// Then in your page component (Server Component)
async function GalleryPage() {
// Get blur placeholders for images
const mainImagePlaceholder = await getBlurData('https://your-cloud-url.com/large-image.jpg');
return (
<div className="gallery">
<Image
src="https://your-cloud-url.com/large-image.jpg"
alt="Featured Large Image"
width={1200}
height={800}
priority
placeholder="blur"
blurDataURL={mainImagePlaceholder || undefined}
/>
{/* Other images... */}
</div>
);
}This approach generates high-quality, color-matched blur placeholders that look like blurred versions of the actual images. While more complex to set up, it provides the most professional-looking placeholders.
For our gallery with 4 images where the first one is large and needs priority loading, I recommend using one of these approaches:
- Simplest approach: Use
priorityon the first image and a simple colorblurDataURLfor all images - Custom icon approach: Use the data URI method to show your custom icon as a placeholder
- Professional approach: For production apps, consider generating proper blur hashes for the best visual experience
The right choice depends on your specific requirements, design aesthetics, and the importance of the images to your user experience.
By effectively using these priority and placeholder techniques, you can create a much better user experience, especially when dealing with larger images that might take time to load.