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 15 offers a powerful tool for image optimization - the
Image component.Before we dive into the details of the
Image component, it is worth understanding why image optimization is so important:In traditional HTML, images are usually added using the
<img> tag:1<img src="/images/quantum-city.jpg" alt="Panorama Metropolis Quantum" />This simple approach, however, has several drawbacks:
The
Image component from Next.js solves these problems and offers a number of additional benefits:To start using the
Image component, you first need to import it from next/image:1import Image from 'next/image';
2
3export default function QuantumCityView() {
4 return (
5 <div className="city-view">
6 <h1>Panorama Metropolis Quantum</h1>
7 <Image
8 src="/images/quantum-city.jpg"
9 alt="Panorama Metropolis Quantum z lotu ptaka"
10 width={1200}
11 height={600}
12 />
13 </div>
14 );
15}In the above example, the
Image component requires several attributes:src - path to the imagealt - alternative text for accessibilitywidth and height - width and height of the image in pixelsNext.js also allows static importing of images, which provides additional benefits such as automatic dimension detection:
1import Image from 'next/image';
2import quantumCityImage from '@/public/images/quantum-city.jpg';
3
4export default function QuantumCityView() {
5 return (
6 <div className="city-view">
7 <h1>Panorama Metropolis Quantum</h1>
8 <Image
9 src={quantumCityImage}
10 alt="Panorama Metropolis Quantum z lotu ptaka"
11 // width and height are automatically determined from image metadata
12 />
13 </div>
14 );
15}The
Image component can be easily adapted to different screen sizes using CSS styles:1import Image from 'next/image';
2import quantumCityImage from '@/public/images/quantum-city.jpg';
3
4export default function QuantumCityView() {
5 return (
6 <div className="city-view">
7 <h1>Panorama Metropolis Quantum</h1>
8 <div className="relative w-full h-[50vh]">
9 <Image
10 src={quantumCityImage}
11 alt="Panorama Metropolis Quantum z lotu ptaka"
12 fill
13 style={{ objectFit: 'cover' }}
14 />
15 </div>
16 </div>
17 );
18}In this example:
fill attribute causes the image to fill the parent containerobjectFit: 'cover' style ensures the image maintains proportions, cropping it if necessaryrelative or absolute positioningFor images that are visible "above the fold" (i.e., in the upper part of the page without scrolling), it is worth using the
priority attribute:1<Image
2 src="/images/hero-quantum-city.jpg"
3 alt="Panorama Metropolis Quantum"
4 width={1200}
5 height={600}
6 priority
7/>The
priority attribute causes:To use images from external sources (e.g., CMS, CDN, API), you need to configure the list of allowed domains in the
next.config.js file:1// next.config.js
2module.exports = {
3 images: {
4 domains: ['cdn.quantum-city.com', 'images.cms-provider.com'],
5 },
6}Alternatively, you can use "remotePatterns" for more precise control:
1// next.config.js
2module.exports = {
3 images: {
4 remotePatterns: [
5 {
6 protocol: 'https',
7 hostname: 'cdn.quantum-city.com',
8 port: '',
9 pathname: '/images/**',
10 },
11 {
12 protocol: 'https',
13 hostname: '**.cms-provider.com',
14 port: '',
15 pathname: '/assets/**',
16 },
17 ],
18 },
19}Next.js 15 offers several ways to improve the user experience during image loading:
1<Image
2 src="/images/quantum-city.jpg"
3 alt="Panorama Metropolis Quantum"
4 width={1200}
5 height={600}
6 placeholder="blur"
7 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"
8/>In this example:
placeholder="blur" enables a blur effect during image loadingblurDataURL specifies the background color used during image loading (encoded as a base64 URL)When you use a static image import, Next.js automatically generates a blurred version:
1import Image from 'next/image';
2import quantumCityImage from '@/public/images/quantum-city.jpg';
3
4export default function QuantumCityView() {
5 return (
6 <Image
7 src={quantumCityImage}
8 alt="Panorama Metropolis Quantum"
9 placeholder="blur"
10 // blurDataURL jest generowany automatycznie
11 />
12 );
13}You can add image loading animations using CSS:
1import Image from 'next/image';
2import styles from './image.module.css';
3
4export default function QuantumCityView() {
5 return (
6 <Image
7 src="/images/quantum-city.jpg"
8 alt="Panorama Metropolis Quantum"
9 width={1200}
10 height={600}
11 className={styles.imageTransition}
12 />
13 );
14}1/* image.module.css */
2.imageTransition {
3 opacity: 0;
4 transition: opacity 0.5s ease-in-out;
5}
6
7.imageTransition:global(.loaded) {
8 opacity: 1;
9}If you want to display different images for different screen sizes, you can use the
srcSet attribute:1<div className="relative aspect-video w-full">
2 <picture>
3 <source
4 media="(max-width: 640px)"
5 srcSet="/images/quantum-city-mobile.jpg"
6 />
7 <source
8 media="(max-width: 1024px)"
9 srcSet="/images/quantum-city-tablet.jpg"
10 />
11 <source
12 media="(min-width: 1025px)"
13 srcSet="/images/quantum-city-desktop.jpg"
14 />
15 <Image
16 src="/images/quantum-city-desktop.jpg"
17 alt="Panorama Metropolis Quantum"
18 fill
19 style={{ objectFit: 'cover' }}
20 />
21 </picture>
22</div>Although Next.js automatically converts images to WebP, you can also use the
<picture> tag to handle different formats:1<picture>
2 <source srcSet="/images/quantum-city.avif" type="image/avif" />
3 <source srcSet="/images/quantum-city.webp" type="image/webp" />
4 <Image
5 src="/images/quantum-city.jpg"
6 alt="Panorama Metropolis Quantum"
7 width={1200}
8 height={600}
9 />
10</picture>For images with transparency (PNG, SVG), it is worth considering disabling WebP conversion:
1<Image
2 src="/images/quantum-logo.png"
3 alt="Logo Metropolis Quantum"
4 width={200}
5 height={200}
6 style={{ objectFit: 'contain' }}
7 unoptimized
8/>The
unoptimized attribute disables Next.js optimization for the given image, which can be useful for:Next.js offers many configuration options for image optimization in the
next.config.js file:1// next.config.js
2module.exports = {
3 images: {
4 // Allowed domains for external images
5 domains: ['cdn.quantum-city.com'],
6
7 // More detailed configuration for external images
8 remotePatterns: [
9 {
10 protocol: 'https',
11 hostname: '**.cms-provider.com',
12 port: '',
13 pathname: '/assets/**',
14 },
15 ],
16
17 // Supported formats
18 formats: ['image/webp', 'image/avif'],
19
20 // Konfiguracja cache
21 minimumCacheTTL: 60, // w sekundach
22
23 // Image size limits
24 deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
25 imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
26
27 // Custom image loader (e.g., Cloudinary, imgix)
28 loader: 'default',
29
30 // Path to image optimization endpoint
31 path: '/_next/image',
32
33 // Disabling automatic optimization for all images
34 unoptimized: false,
35 },
36}Next.js also supports popular image optimization services:
1// next.config.js
2module.exports = {
3 images: {
4 loader: 'cloudinary',
5 loaderFile: './my/image/loader.js',
6 // Domena Cloudinary
7 domains: ['res.cloudinary.com'],
8 },
9}You can also create your own loader:
1// my/image/loader.js
2export default function myImageLoader({ src, width, quality }) {
3 return `https://cdn.quantum-city.com/images/${src}?w=${width}&q=${quality || 75}`
4}And use it in the
Image component:1import Image from 'next/image';
2import myLoader from '@/my/image/loader';
3
4export default function QuantumCityView() {
5 return (
6 <Image
7 loader={myLoader}
8 src="quantum-city.jpg"
9 alt="Panorama Metropolis Quantum"
10 width={1200}
11 height={600}
12 />
13 );
14}1// components/quantum-gallery.tsx
2import { useState } from 'react';
3import Image from 'next/image';
4
5const images = [
6 { src: '/images/quantum-city-1.jpg', alt: 'Panorama Metropolis Quantum' },
7 { src: '/images/quantum-city-2.jpg', alt: 'Dzielnica Business Quantum' },
8 { src: '/images/quantum-city-3.jpg', alt: 'Parki Kwantowe' },
9 { src: '/images/quantum-city-4.jpg', alt: 'Architektura Kwantowa' },
10 { src: '/images/quantum-city-5.jpg', alt: 'Transport Kwantowy' },
11 // more images...
12];
13
14export default function QuantumGallery() {
15 const [selectedImage, setSelectedImage] = useState(null);
16
17 return (
18 <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
19 <h2 className="text-3xl font-bold mb-6">Galeria Metropolis Quantum</h2>
20
21 {/* Display selected image */}
22 {selectedImage && (
23 <div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
24 <div className="max-w-4xl mx-auto p-4">
25 <div className="relative w-full" style={{ height: '80vh' }}>
26 <Image
27 src={selectedImage.src}
28 alt={selectedImage.alt}
29 fill
30 style={{ objectFit: 'contain' }}
31 priority
32 />
33 </div>
34 <button
35 className="absolute top-4 right-4 text-white text-2xl"
36 onClick={() => setSelectedImage(null)}
37 >
38 ×
39 </button>
40 </div>
41 </div>
42 )}
43
44 {/* Image grid */}
45 <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
46 {images.map((image, index) => (
47 <div
48 key={index}
49 className="relative aspect-square overflow-hidden rounded-lg cursor-pointer hover:opacity-90 transition-opacity"
50 onClick={() => setSelectedImage(image)}
51 >
52 <Image
53 src={image.src}
54 alt={image.alt}
55 fill
56 style={{ objectFit: 'cover' }}
57 sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
58 // First image is priority, rest are loaded lazily
59 priority={index === 0}
60 />
61 </div>
62 ))}
63 </div>
64 </div>
65 );
66}1// components/quantum-hero.tsx
2import Image from 'next/image';
3
4export default function QuantumHero() {
5 return (
6 <div className="relative">
7 {/* Image for mobile devices */}
8 <div className="sm:hidden relative h-[50vh]">
9 <Image
10 src="/images/quantum-hero-mobile.jpg"
11 alt="Metropolis Quantum - City of the Future"
12 fill
13 style={{ objectFit: 'cover' }}
14 priority
15 />
16 </div>
17
18 {/* Image for tablets */}
19 <div className="hidden sm:block md:hidden relative h-[60vh]">
20 <Image
21 src="/images/quantum-hero-tablet.jpg"
22 alt="Metropolis Quantum - City of the Future"
23 fill
24 style={{ objectFit: 'cover' }}
25 priority
26 />
27 </div>
28
29 {/* Image for desktops */}
30 <div className="hidden md:block relative h-[70vh]">
31 <Image
32 src="/images/quantum-hero-desktop.jpg"
33 alt="Metropolis Quantum - City of the Future"
34 fill
35 style={{ objectFit: 'cover' }}
36 priority
37 />
38 </div>
39
40 {/* Overlay content */}
41 <div className="absolute inset-0 flex items-center justify-center">
42 <div className="text-center text-white p-4 backdrop-blur-sm bg-black/30 rounded-lg max-w-2xl">
43 <h1 className="text-4xl sm:text-5xl md:text-6xl font-bold mb-4">
44 Metropolis Quantum
45 </h1>
46 <p className="text-xl sm:text-2xl md:text-3xl">
47 The city of the future awaits you
48 </p>
49 </div>
50 </div>
51 </div>
52 );
53}1// components/quantum-product-card.tsx
2import Image from 'next/image';
3import { useState } from 'react';
4
5interface ProductCardProps {
6 product: {
7 id: string;
8 name: string;
9 image: string;
10 price: number;
11 description: string;
12 };
13}
14
15export default function ProductCard({ product }: ProductCardProps) {
16 const [isLoading, setIsLoading] = useState(true);
17
18 return (
19 <div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
20 <div className="relative h-64 w-full bg-gray-200 dark:bg-gray-700">
21 <Image
22 src={product.image}
23 alt={product.name}
24 fill
25 style={{ objectFit: 'cover' }}
26 sizes="(max-width: 768px) 100vw, 33vw"
27 className={`
28 duration-700 ease-in-out
29 ${isLoading ? 'scale-110 blur-xl grayscale' : 'scale-100 blur-0 grayscale-0'}
30 `}
31 onLoadingComplete={() => setIsLoading(false)}
32 />
33 </div>
34 <div className="p-4">
35 <h3 className="text-xl font-semibold mb-2 dark:text-white">{product.name}</h3>
36 <p className="text-gray-600 dark:text-gray-300 mb-2">{product.description}</p>
37 <div className="flex justify-between items-center">
38 <span className="text-lg font-bold dark:text-white">
39 {new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(product.price)}
40 </span>
41 <button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
42 Dodaj do koszyka
43 </button>
44 </div>
45 </div>
46 </div>
47 );
48}src attribute, always define width and height or use fill with a container of defined dimensionssizes attribute - helps in delivering appropriate image sizes for different devicespriority for images visible without scrollingunoptimized - use only in justified casesalt values - for accessibility and SEOnext.config.jsNext.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:
1// next.config.js
2module.exports = {
3 images: {
4 formats: ['image/avif', 'image/webp'],
5 },
6}| 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 |
Solution: Dostosuj parametr
quality:1<Image
2 src="/images/quantum-city.jpg"
3 alt="Panorama Metropolis Quantum"
4 width={1200}
5 height={600}
6 quality={90} // Default is 75
7/>Solution: Always specify image dimensions or use
fill with a container of defined dimensions:1<div className="relative w-full aspect-video">
2 <Image
3 src="/images/quantum-city.jpg"
4 alt="Panorama Metropolis Quantum"
5 fill
6 style={{ objectFit: 'cover' }}
7 />
8</div>Solution: Use the
sizes attribute:1<Image
2 src="/images/quantum-city.jpg"
3 alt="Panorama Metropolis Quantum"
4 width={1200}
5 height={600}
6 sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
7/>Solution: For SVG files, the best option is often to use
unoptimized:1<Image
2 src="/images/logo.svg"
3 alt="Logo Metropolis Quantum"
4 width={200}
5 height={200}
6 unoptimized
7/>In addition to the
Image component, it is also worth using other image optimization techniques:Before adding images to the project, it is worth optimizing them using tools such as:
Using a CDN (Content Delivery Network) can significantly speed up image delivery:
1// next.config.js
2module.exports = {
3 images: {
4 loader: 'akamai',
5 path: 'https://cdn.quantum-city.com',
6 },
7}1<picture>
2 <source media="(max-width: 640px)" srcSet="/images/banner-mobile.jpg" />
3 <source media="(max-width: 1024px)" srcSet="/images/banner-tablet.jpg" />
4 <source media="(min-width: 1025px)" srcSet="/images/banner-desktop.jpg" />
5 <img src="/images/banner-fallback.jpg" alt="Banner" />
6</picture>srcset1<img
2 src="/images/quantum-city.jpg"
3 srcSet="
4 /images/quantum-city-small.jpg 640w,
5 /images/quantum-city-medium.jpg 1024w,
6 /images/quantum-city-large.jpg 1920w
7 "
8 sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
9 alt="Panorama Metropolis Quantum"
10/>Image optimization is a crucial aspect of creating efficient web applications. Next.js 15 offers a powerful
Image component that automatically handles many aspects of optimization: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.