Next.js course · Module 3: Styling and Visual Optimization
Image Component and Image Optimization in Next.js 16
In this lesson11
In Metropolis Quantum, visual communication plays a key role. Futuristic holograms, digital billboards, and immersive displays are ubiquitous in the city's landscape. However, just like in real cities, engineers must ensure the efficiency of display systems so that images load quickly and do not consume too many resources.
In the world of web applications, images are one of the biggest obstacles to achieving optimal page performance. Large, uncompressed photos can significantly slow down page loading and worsen the user experience. Fortunately, Next.js 16 offers a powerful tool for image optimization - the Image component.
Why is Image Optimization Important?
Before we dive into the details of the Image component, it is worth understanding why image optimization is so important:
- Page performance - images often make up over 50% of a web page's weight
- Core Web Vitals - metrics like LCP (Largest Contentful Paint) are directly related to image loading time
- SEO - faster pages are better positioned in search results
- User experience - nobody likes waiting for content to load
- Data consumption - for mobile users, large images can quickly use up data limits
- Accessibility - properly optimized images are more accessible for people with slower internet connections
Traditional Approach to Images in HTML
In traditional HTML, images are usually added using the <img> tag:
<img src="/images/quantum-city.jpg" alt="Panorama Metropolis Quantum" />This simple approach, however, has several drawbacks:
- No automatic optimization - images are served in their original format and size
- No responsiveness - images do not automatically adapt to different screen sizes
- No lazy loading - all images are loaded immediately, regardless of their visibility
- No prioritization - it is not easy to determine which images are critical for displaying the page
The Image Component in Next.js 16
The Image component from Next.js solves these problems and offers a number of additional benefits:
- Automatic optimization - conversion to modern formats (WebP, AVIF)
- Size adjustment - serving images in the appropriate size for the device
- Lazy loading - images are loaded only when they approach the viewport
- Avoiding layout shifts - reserving space for images before they load
- Support for multiple sources - easy specification of different images for different resolutions
- Core Web Vitals optimization - improvement of metrics such as LCP and CLS
Basic Usage of the Image Component
To start using the Image component, you first need to import it from next/image:
import Image from 'next/image';
export default function QuantumCityView() {
return (
<div className="city-view">
<h1>Panorama Metropolis Quantum</h1>
<Image
src="/images/quantum-city.jpg"
alt="Aerial panorama of Metropolis Quantum"
width={1200}
height={600}
/>
</div>
);
}In the above example, the Image component requires several attributes:
src- path to the imagealt- alternative text for accessibilitywidthandheight- width and height of the image in pixels
Static Image Imports
Next.js also allows static importing of images, which provides additional benefits such as automatic dimension detection:
import Image from 'next/image';
import quantumCityImage from '@/public/images/quantum-city.jpg';
export default function QuantumCityView() {
return (
<div className="city-view">
<h1>Panorama Metropolis Quantum</h1>
<Image
src={quantumCityImage}
alt="Aerial panorama of Metropolis Quantum"
// width and height are automatically determined from image metadata
/>
</div>
);
}Responsive Images
The Image component can be easily adapted to different screen sizes using CSS styles:
import Image from 'next/image';
import quantumCityImage from '@/public/images/quantum-city.jpg';
export default function QuantumCityView() {
return (
<div className="city-view">
<h1>Panorama Metropolis Quantum</h1>
<div className="relative w-full h-[50vh]">
<Image
src={quantumCityImage}
alt="Aerial panorama of Metropolis Quantum"
fill
style={{ objectFit: 'cover' }}
/>
</div>
</div>
);
}In this example:
- The
fillattribute causes the image to fill the parent container - The
objectFit: 'cover'style ensures the image maintains proportions, cropping it if necessary - The parent container must have
relativeorabsolutepositioning
Image Prioritization
For images that are visible "above the fold" (i.e., in the upper part of the page without scrolling), it is worth using the preload attribute. In Next.js 16 it replaced the deprecated priority attribute, which still works but should no longer be used in new code:
<Image
src="/images/hero-quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
preload
/>The preload attribute causes:
- Next.js inserts a
<link rel="preload">tag for this image into the<head> - The browser starts downloading the image before it reaches it in the page content
- The image is available as soon as possible, which improves LCP
The Next.js 16 documentation advises using preload only for a single LCP image, such as a hero photo. In other cases, loading="eager" or fetchPriority="high" is enough.
Handling Images from CMS or External Sources
To use images from external sources (e.g., CMS, CDN, API), you need to tell Next.js which servers are allowed in the next.config.js file. Older projects do it with the domains option:
// next.config.js
module.exports = {
images: {
domains: ['cdn.quantum-city.com', 'images.cms-provider.com'],
},
}This option has been deprecated since Next.js 14, so in Next.js 16 use "remotePatterns", which give you more precise control:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.quantum-city.com',
port: '',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '**.cms-provider.com',
port: '',
pathname: '/assets/**',
},
],
},
}Placeholders and Blur Effect
Next.js 16 offers several ways to improve the user experience during image loading:
Placeholder with Blur Effect
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
placeholder="blur"
blurDataURL="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='600'%3E%3Crect width='100%25' height='100%25' fill='%23071731'/%3E%3C/svg%3E"
/>In this example:
placeholder="blur"enables a blur effect during image loadingblurDataURLspecifies the background color used during image loading (encoded as a base64 URL)
Automatic Blur Effect for Static Imports
When you use a static image import, Next.js automatically generates a blurred version:
import Image from 'next/image';
import quantumCityImage from '@/public/images/quantum-city.jpg';
export default function QuantumCityView() {
return (
<Image
src={quantumCityImage}
alt="Panorama Metropolis Quantum"
placeholder="blur"
// blurDataURL is generated automatically
/>
);
}Image Loading Animations
You can add image loading animations using CSS:
import Image from 'next/image';
import styles from './image.module.css';
export default function QuantumCityView() {
return (
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
className={styles.imageTransition}
/>
);
}/* image.module.css */
.imageTransition {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.imageTransition:global(.loaded) {
opacity: 1;
}Advanced Features of the Image Component
Adaptive Images and Art Direction
If you want to display different images for different screen sizes, you can use the srcSet attribute:
<div className="relative aspect-video w-full">
<picture>
<source
media="(max-width: 640px)"
srcSet="/images/quantum-city-mobile.jpg"
/>
<source
media="(max-width: 1024px)"
srcSet="/images/quantum-city-tablet.jpg"
/>
<source
media="(min-width: 1025px)"
srcSet="/images/quantum-city-desktop.jpg"
/>
<Image
src="/images/quantum-city-desktop.jpg"
alt="Panorama Metropolis Quantum"
fill
style={{ objectFit: 'cover' }}
/>
</picture>
</div>Handling Images in Different Formats
Although Next.js automatically converts images to WebP, you can also use the <picture> tag to handle different formats:
<picture>
<source srcSet="/images/quantum-city.avif" type="image/avif" />
<source srcSet="/images/quantum-city.webp" type="image/webp" />
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
/>
</picture>Handling Images with Transparency
For images with transparency (PNG, SVG), it is worth considering disabling WebP conversion:
<Image
src="/images/quantum-logo.png"
alt="Logo Metropolis Quantum"
width={200}
height={200}
style={{ objectFit: 'contain' }}
unoptimized
/>The unoptimized attribute disables Next.js optimization for the given image, which can be useful for:
- Images with transparency
- Already optimized images
- Animated GIFs
Configuring Image Optimization in Next.js 16
Next.js offers many configuration options for image optimization in the next.config.js file:
// next.config.js
module.exports = {
images: {
// Deprecated since Next.js 14 - use remotePatterns instead of domains
domains: ['cdn.quantum-city.com'],
// More detailed configuration for external images
remotePatterns: [
{
protocol: 'https',
hostname: '**.cms-provider.com',
port: '',
pathname: '/assets/**',
},
],
// Supported formats
formats: ['image/webp', 'image/avif'],
// Allowed quality values (since Next.js 16 only [75] by default)
qualities: [50, 75, 90],
// Cache configuration (since Next.js 16 the default is 14400 s, i.e. 4 hours)
minimumCacheTTL: 60, // in seconds
// Image size limits
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // since Next.js 16, 16 is not in the default list
// Custom image loader (e.g., Cloudinary, imgix)
loader: 'default',
// Path to image optimization endpoint
path: '/_next/image',
// Disabling automatic optimization for all images
unoptimized: false,
},
}Next.js 16 changed several defaults: minimumCacheTTL is now 4 hours (previously 60 seconds), the 16-pixel width is gone from imageSizes, and qualities allows only quality 75 by default. There are also new security options: maximumRedirects (at most 3 redirects by default) and dangerouslyAllowLocalIP, without which Next.js does not optimize images from local addresses.
Using External Provider Loaders
Next.js also supports popular image optimization services:
// next.config.js
module.exports = {
images: {
loader: 'cloudinary',
loaderFile: './my/image/loader.js',
// Cloudinary domain
domains: ['res.cloudinary.com'],
},
}You can also create your own loader:
// my/image/loader.js
export default function myImageLoader({ src, width, quality }) {
return `https://cdn.quantum-city.com/images/${src}?w=${width}&q=${quality || 75}`
}And use it in the Image component:
import Image from 'next/image';
import myLoader from '@/my/image/loader';
export default function QuantumCityView() {
return (
<Image
loader={myLoader}
src="quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
/>
);
}Practical Application Examples
1. Image Gallery with Lazy Loading
// components/quantum-gallery.tsx
import { useState } from 'react';
import Image from 'next/image';
const images = [
{ src: '/images/quantum-city-1.jpg', alt: 'Panorama Metropolis Quantum' },
{ src: '/images/quantum-city-2.jpg', alt: 'Quantum Business District' },
{ src: '/images/quantum-city-3.jpg', alt: 'Quantum Parks' },
{ src: '/images/quantum-city-4.jpg', alt: 'Quantum Architecture' },
{ src: '/images/quantum-city-5.jpg', alt: 'Quantum Transport' },
// more images...
];
export default function QuantumGallery() {
const [selectedImage, setSelectedImage] = useState(null);
return (
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold mb-6">Metropolis Quantum Gallery</h2>
{/* Display selected image */}
{selectedImage && (
<div className="fixed inset-0 bg-black/75 flex items-center justify-center z-50">
<div className="max-w-4xl mx-auto p-4">
<div className="relative w-full" style={{ height: '80vh' }}>
<Image
src={selectedImage.src}
alt={selectedImage.alt}
fill
style={{ objectFit: 'contain' }}
preload
/>
</div>
<button
className="absolute top-4 right-4 text-white text-2xl"
onClick={() => setSelectedImage(null)}
>
×
</button>
</div>
</div>
)}
{/* Image grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{images.map((image, index) => (
<div
key={index}
className="relative aspect-square overflow-hidden rounded-lg cursor-pointer hover:opacity-90 transition-opacity"
onClick={() => setSelectedImage(image)}
>
<Image
src={image.src}
alt={image.alt}
fill
style={{ objectFit: 'cover' }}
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
// First image is priority, rest are loaded lazily
preload={index === 0}
/>
</div>
))}
</div>
</div>
);
}2. Responsive Banner with Different Images for Different Devices
// components/quantum-hero.tsx
import Image from 'next/image';
export default function QuantumHero() {
return (
<div className="relative">
{/* Image for mobile devices */}
<div className="sm:hidden relative h-[50vh]">
<Image
src="/images/quantum-hero-mobile.jpg"
alt="Metropolis Quantum - City of the Future"
fill
style={{ objectFit: 'cover' }}
preload
/>
</div>
{/* Image for tablets */}
<div className="hidden sm:block md:hidden relative h-[60vh]">
<Image
src="/images/quantum-hero-tablet.jpg"
alt="Metropolis Quantum - City of the Future"
fill
style={{ objectFit: 'cover' }}
preload
/>
</div>
{/* Image for desktops */}
<div className="hidden md:block relative h-[70vh]">
<Image
src="/images/quantum-hero-desktop.jpg"
alt="Metropolis Quantum - City of the Future"
fill
style={{ objectFit: 'cover' }}
preload
/>
</div>
{/* Overlay content */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-white p-4 backdrop-blur-sm bg-black/30 rounded-lg max-w-2xl">
<h1 className="text-4xl sm:text-5xl md:text-6xl font-bold mb-4">
Metropolis Quantum
</h1>
<p className="text-xl sm:text-2xl md:text-3xl">
The city of the future awaits you
</p>
</div>
</div>
</div>
);
}3. Product Cards with Images
// components/quantum-product-card.tsx
import Image from 'next/image';
import { useState } from 'react';
interface ProductCardProps {
product: {
id: string;
name: string;
image: string;
price: number;
description: string;
};
}
export default function ProductCard({ product }: ProductCardProps) {
const [isLoading, setIsLoading] = useState(true);
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<div className="relative h-64 w-full bg-gray-200 dark:bg-gray-700">
<Image
src={product.image}
alt={product.name}
fill
style={{ objectFit: 'cover' }}
sizes="(max-width: 768px) 100vw, 33vw"
className={`
duration-700 ease-in-out
${isLoading ? 'scale-110 blur-xl grayscale' : 'scale-100 blur-0 grayscale-0'}
`}
onLoad={() => setIsLoading(false)}
/>
</div>
<div className="p-4">
<h3 className="text-xl font-semibold mb-2 dark:text-white">{product.name}</h3>
<p className="text-gray-600 dark:text-gray-300 mb-2">{product.description}</p>
<div className="flex justify-between items-center">
<span className="text-lg font-bold dark:text-white">
{new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(product.price)}
</span>
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
Add to cart
</button>
</div>
</div>
</div>
);
}Best Practices for Working with the Image Component
- Always specify dimensions - for images with the
srcattribute, always definewidthandheightor usefillwith a container of defined dimensions - Use the
sizesattribute - helps in delivering appropriate image sizes for different devices - Prioritize "above the fold" images - use
preload(formerlypriority) for the most important image visible without scrolling - Use appropriate formats - let Next.js automatically convert to WebP/AVIF
- Optimize for Core Web Vitals - maintain low LCP and CLS
- Use static imports for local images
- Do not overuse
unoptimized- use only in justified cases - Use meaningful
altvalues - for accessibility and SEO - Support different formats - configure AVIF and WebP support in
next.config.js
Image Format Support in Next.js 16
Next.js supports automatic conversion of images to modern formats. By default, Next.js uses the WebP format, which offers good compression while maintaining quality. However, you can also configure support for the AVIF format, which offers even better compression:
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
}Image Format Comparison
| Format | Advantages | Disadvantages | Browser Support |
|---|---|---|---|
| JPEG | Widely supported, good for photography | No transparency, larger size than newer formats | All browsers |
| PNG | Supports transparency, lossless | Large file size | All browsers |
| WebP | Smaller size than JPEG/PNG, supports transparency | Incomplete support in older browsers | ~95% of browsers |
| AVIF | Best compression, good quality | Limited browser support, slower encoding | ~70% of browsers |
Debugging and Troubleshooting
Problem: Images are Blurry or Low Quality
Solution: Adjust the quality parameter:
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
quality={90} // Default is 75
/>In Next.js 16, the quality value must be on the images.qualities list in next.config. By default the list contains only 75, so unless you add 90, Next.js uses the closest allowed value, which is 75:
// next.config.js
module.exports = {
images: {
qualities: [50, 75, 90],
},
};Now the image is generated at quality 90, while other images can keep using the default 75.
Problem: Layout Shift During Image Loading
Solution: Always specify image dimensions or use fill with a container of defined dimensions:
<div className="relative w-full aspect-video">
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
fill
style={{ objectFit: 'cover' }}
/>
</div>Problem: Images are Not Responsive on Different Devices
Solution: Use the sizes attribute:
<Image
src="/images/quantum-city.jpg"
alt="Panorama Metropolis Quantum"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>Problem: SVG Images are Not Displaying Properly
Solution: For SVG files, the best option is often to use unoptimized:
<Image
src="/images/logo.svg"
alt="Logo Metropolis Quantum"
width={200}
height={200}
unoptimized
/>Image Optimization Beyond the Image Component
In addition to the Image component, it is also worth using other image optimization techniques:
1. Optimizing Images Before Adding to the Project
Before adding images to the project, it is worth optimizing them using tools such as:
- ImageOptim
- Squoosh
- TinyPNG
- SVGO (for SVG files)
2. Using CDN for Serving Images
Using a CDN (Content Delivery Network) can significantly speed up image delivery:
// next.config.js
module.exports = {
images: {
loader: 'akamai',
path: 'https://cdn.quantum-city.com',
},
}3. Using "Art Direction" Technique for Different Devices
<picture>
<source media="(max-width: 640px)" srcSet="/images/banner-mobile.jpg" />
<source media="(max-width: 1024px)" srcSet="/images/banner-tablet.jpg" />
<source media="(min-width: 1025px)" srcSet="/images/banner-desktop.jpg" />
<img src="/images/banner-fallback.jpg" alt="Banner" />
</picture>4. Using "Responsive Images" Technique with srcset
<img
src="/images/quantum-city.jpg"
srcSet="
/images/quantum-city-small.jpg 640w,
/images/quantum-city-medium.jpg 1024w,
/images/quantum-city-large.jpg 1920w
"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Panorama Metropolis Quantum"
/>Summary
Image optimization is a crucial aspect of creating efficient web applications. Next.js 16 offers a powerful Image component that automatically handles many aspects of optimization:
- Automatic optimization - conversion to modern formats (WebP, AVIF)
- Size adjustment - serving images in the appropriate size for the device
- Lazy loading - images are loaded only when they approach the viewport
- Avoiding layout shifts - reserving space for images before they load
- Support for multiple sources - easy specification of different images for different resolutions
Thanks to these features, you can create applications that load faster, consume less data, and provide a better user experience. Just as the engineers of Metropolis Quantum optimize hologram display systems and digital billboards, you can optimize images in your Next.js application to provide users with a smooth and efficient experience.
In the next chapter, we will learn about different image loading strategies (lazy, eager, priority), which will allow us to further customize image behavior to suit your application's needs.
Code for this lesson: app/page.tsx
// Next.js Image Component - Optimization Demo
'use client';
import { useState } from 'react';
// Note: In a real Next.js project, you'd use:
// import Image from 'next/image';
// Here we demonstrate the concepts with standard img tags
export default function ImageOptimizationDemo() {
const [showComparison, setShowComparison] = useState(false);
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-2">Image Optimization in Next.js 16</h1>
<p className="text-gray-400 mb-8">
Automatic optimization, lazy loading, and responsive images
</p>
{/* Traditional vs Next.js Image comparison */}
<section className="mb-12">
<h2 className="text-xl font-semibold mb-4">Traditional HTML vs Next.js Image</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Traditional approach */}
<div className="bg-gray-800 rounded-lg p-6">
<h3 className="text-red-400 font-mono mb-3">Traditional {"<img>"}</h3>
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
{`<img
src="/photo.jpg"
alt="City panorama"
width="800"
height="600"
/>`}
</pre>
<ul className="mt-4 space-y-2 text-sm text-gray-400">
<li>• No automatic optimization</li>
<li>• No lazy loading by default</li>
<li>• No responsive sizing</li>
<li>• Can cause layout shift (CLS)</li>
</ul>
</div>
{/* Next.js Image approach */}
<div className="bg-gray-800 rounded-lg p-6">
<h3 className="text-green-400 font-mono mb-3">Next.js {"<Image>"}</h3>
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
{`import Image from 'next/image';
<Image
src="/photo.jpg"
alt="City panorama"
width={800}
height={600}
placeholder="blur"
/>`}
</pre>
<ul className="mt-4 space-y-2 text-sm text-gray-400">
<li>Auto WebP/AVIF conversion</li>
<li>Built-in lazy loading</li>
<li>Responsive srcset</li>
<li>Prevents layout shift</li>
</ul>
</div>
</div>
</section>
{/* Image properties demo */}
<section className="mb-12">
<h2 className="text-xl font-semibold mb-4">Key Properties</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-gray-800 rounded-lg p-4">
<h3 className="text-blue-400 font-semibold mb-2">fill</h3>
<p className="text-sm text-gray-400">
Image fills parent container. Use with position: relative on parent.
</p>
<code className="text-xs text-green-400 mt-2 block">
{"<Image fill style={{objectFit: 'cover'}} />"}
</code>
</div>
<div className="bg-gray-800 rounded-lg p-4">
<h3 className="text-purple-400 font-semibold mb-2">sizes</h3>
<p className="text-sm text-gray-400">
Responsive breakpoints for different viewport widths.
</p>
<code className="text-xs text-green-400 mt-2 block">
{'sizes="(max-width: 768px) 100vw, 50vw"'}
</code>
</div>
<div className="bg-gray-800 rounded-lg p-4">
<h3 className="text-yellow-400 font-semibold mb-2">placeholder</h3>
<p className="text-sm text-gray-400">
Show blur or empty placeholder while image loads.
</p>
<code className="text-xs text-green-400 mt-2 block">
{'placeholder="blur" blurDataURL="..."'}
</code>
</div>
</div>
</section>
</div>
</div>
);
}Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. In Tailwind CSS the class 'flex justify-center items-center' means:
2. CSS-in-JS allows:
Hands-on tasks in the game
- Code editor
Create CSS with media queries for mobile-first responsive design (sm, md, lg, xl breakpoints)
- Horizontal ordering
Arrange the elements in the correct order: return { → notFound: → true
- Click in order
Arrange the media query syntax with min-width
- Code editor
Create a page layout with header, sidebar, main content and footer using CSS Grid