W Metropolis Quantum, wizualna komunikacja odgrywa kluczową rolę. Futurystyczne hologramy, cyfrowe billboardy i immersyjne wyświetlacze są wszechobecne w krajobrazie miasta. Jednak, podobnie jak w przypadku rzeczywistych miast, inżynierowie muszą dbać o wydajność systemów wyświetlania, aby obrazy ładowały się szybko i nie zużywały zbyt wiele zasobów.
W świecie aplikacji webowych, obrazy stanowią jedną z największych przeszkód w osiąganiu optymalnej wydajności stron. Duże, nieskompresowane zdjęcia mogą znacząco spowolnić ładowanie strony i pogorszyć doświadczenie użytkownika. Na szczęście, Next.js 15 oferuje potężne narzędzie do optymalizacji obrazów - komponent
Image.Zanim zagłębimy się w szczegóły komponentu
Image, warto zrozumieć, dlaczego optymalizacja obrazów jest tak istotna:W tradycyjnym HTML, obrazy są zazwyczaj dodawane za pomocą tagu
<img>:1<img src="/images/quantum-city.jpg" alt="Panorama Metropolis Quantum" />To proste podejście ma jednak kilka wad:
Komponent
Image z Next.js rozwiązuje te problemy i oferuje szereg dodatkowych korzyści:Aby zacząć korzystać z komponentu
Image, najpierw należy go zaimportować z 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}W powyższym przykładzie, komponent
Image wymaga kilku atrybutów:src - ścieżka do obrazualt - alternatywny tekst dla dostępnościwidth i height - szerokość i wysokość obrazu w pikselachNext.js pozwala również na statyczne importowanie obrazów, co daje dodatkowe korzyści, takie jak automatyczne określanie wymiarów:
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 i height są automatycznie określane z metadanych obrazu
12 />
13 </div>
14 );
15}Komponent
Image można łatwo dostosować do różnych rozmiarów ekranu za pomocą stylów CSS: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}W tym przykładzie:
fill powoduje, że obraz wypełnia kontener rodzicaobjectFit: 'cover' zapewnia, że obraz zachowuje proporcje, przycinając go w razie potrzebyrelative lub absoluteDla obrazów, które są widoczne "above the fold" (czyli w górnej części strony bez przewijania), warto użyć atrybutu
priority:1<Image
2 src="/images/hero-quantum-city.jpg"
3 alt="Panorama Metropolis Quantum"
4 width={1200}
5 height={600}
6 priority
7/>Atrybut
priority powoduje, że:Aby używać obrazów z zewnętrznych źródeł (np. CMS, CDN, API), należy skonfigurować listę dozwolonych domen w pliku
next.config.js:1// next.config.js
2module.exports = {
3 images: {
4 domains: ['cdn.quantum-city.com', 'images.cms-provider.com'],
5 },
6}Alternatywnie, można użyć tzw. "remotePatterns" dla bardziej precyzyjnej kontroli:
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 oferuje kilka sposóbów na poprawę doświadczenia użytkownika podczas ładowania obrazów:
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/>W tym przykładzie:
placeholder="blur" włącza efekt rozmazania podczas ładowania obrazublurDataURL określa kolor tła używany podczas ładowania obrazu (zakodowany jako URL base64)Kiedy używasz statycznego importu obrazu, Next.js automatycznie generuje wersję rozmazaną:
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}Możesz dodać animacje ładowania obrazu za pomocą 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}W przypadku, gdy chcesz wyświetlać różne obrazy dla różnych rozmiarów ekranu, możesz użyć atrybutu
srcSet: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>Choć Next.js automatycznie konwertuje obrazy do WebP, możesz również użyć tagu
<picture> do obsługi różnych formatów: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>Dla obrazów z transparentnością (PNG, SVG), warto rozważyć wyłączenie konwersji do WebP:
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/>Atrybut
unoptimized wyłącza optymalizację Next.js dla danego obrazu, co może być przydatne dla:Next.js oferuje wiele opcji konfiguracyjnych dla optymalizacji obrazów w pliku
next.config.js:1// next.config.js
2module.exports = {
3 images: {
4 // Dozwolone domeny dla zewnętrznych obrazów
5 domains: ['cdn.quantum-city.com'],
6
7 // Bardziej szczegółowa konfiguracja dla zewnętrznych obrazów
8 remotePatterns: [
9 {
10 protocol: 'https',
11 hostname: '**.cms-provider.com',
12 port: '',
13 pathname: '/assets/**',
14 },
15 ],
16
17 // Obsługiwane formaty
18 formats: ['image/webp', 'image/avif'],
19
20 // Konfiguracja cache
21 minimumCacheTTL: 60, // w sekundach
22
23 // Limit rozmiarów obrazów
24 deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
25 imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
26
27 // Własny loader dla obrazów (np. Cloudinary, imgix)
28 loader: 'default',
29
30 // Ścieżka do endpointu optymalizacji obrazów
31 path: '/_next/image',
32
33 // Wyłączenie automatycznej optymalizacji dla wszystkich obrazów
34 unoptimized: false,
35 },
36}Next.js obsługuje również popularne serwisy optymalizacji obrazów:
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}Możesz również stworzyć własny 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}I użyć go w komponencie
Image: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 // więcej obrazów...
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 {/* Wyświetlanie wybranego obrazu */}
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 {/* Siatka obrazów */}
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 // Pierwszy obraz jest priorytetowy, pozostałe są ładowane leniwie
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 {/* Obraz dla urządzeń mobilnych */}
8 <div className="sm:hidden relative h-[50vh]">
9 <Image
10 src="/images/quantum-hero-mobile.jpg"
11 alt="Metropolis Quantum - Miasto Przyszłości"
12 fill
13 style={{ objectFit: 'cover' }}
14 priority
15 />
16 </div>
17
18 {/* Obraz dla tabletów */}
19 <div className="hidden sm:block md:hidden relative h-[60vh]">
20 <Image
21 src="/images/quantum-hero-tablet.jpg"
22 alt="Metropolis Quantum - Miasto Przyszłości"
23 fill
24 style={{ objectFit: 'cover' }}
25 priority
26 />
27 </div>
28
29 {/* Obraz dla desktopów */}
30 <div className="hidden md:block relative h-[70vh]">
31 <Image
32 src="/images/quantum-hero-desktop.jpg"
33 alt="Metropolis Quantum - Miasto Przyszłości"
34 fill
35 style={{ objectFit: 'cover' }}
36 priority
37 />
38 </div>
39
40 {/* Zawartość nakładkowa */}
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 Miasto przyszłości czeka na Ciebie
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 zawsze definiuj width i height lub używaj fill z kontenerem o określonych wymiarachsizes - pomaga w dostarczaniu odpowiednich rozmiarów obrazu dla różnych urządzeńpriority dla obrazów widocznych bez przewijaniaunoptimized - korzystaj tylko w uzasadnionych przypadkachalt - dla dostępności i SEOnext.config.jsNext.js obsługuje automatyczną konwersję obrazów do nowoczesnych formatów. Domyślnie, Next.js używa formatu WebP, który oferuje dobrą kompresję przy zachowaniu jakości. Jednak możesz również skonfigurować obsługę formatu AVIF, który oferuje jeszcze lepszą kompresję:
1// next.config.js
2module.exports = {
3 images: {
4 formats: ['image/avif', 'image/webp'],
5 },
6}| Format | Zalety | Wady | Wsparcie przeglądarek | |--------|--------|------|----------------------| | JPEG | Szeroko wspierany, dobry dla fotografii | Brak transparentności, większy rozmiar niż nowsze formaty | Wszystkie przeglądarki | | PNG | Obsługuje transparentność, bezstratny | Duży rozmiar pliku | Wszystkie przeglądarki | | WebP | Mniejszy rozmiar niż JPEG/PNG, obsługuje transparentność | Niepełne wsparcie w starszych przeglądarkach | ~95% przeglądarek | | AVIF | Najlepsza kompresja, dobra jakość | Ograniczone wsparcie w przeglądarkach, wolniejsze kodowanie | ~70% przeglądarek |
Rozwiązanie: 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} // Domyślnie 75
7/>Rozwiązanie: Zawsze określaj wymiary obrazu lub używaj
fill z kontenerem o określonych wymiarach: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>Rozwiązanie: Używaj atrybutu
sizes: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/>Rozwiązanie: Dla plików SVG, często najlepszą opcją jest użycie
unoptimized:1<Image
2 src="/images/logo.svg"
3 alt="Logo Metropolis Quantum"
4 width={200}
5 height={200}
6 unoptimized
7/>Oprócz komponentu
Image, warto również stosować inne techniki optymalizacji obrazów:Przed dodaniem obrazów do projektu, warto je zoptymalizować za pomocą narzędzi takich jak:
Korzystanie z CDN (Content Delivery Network) może znacząco przyspieszyć dostarczanie obrazów:
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/>Optymalizacja obrazów jest kluczowym aspektem tworzenia wydajnych aplikacji webowych. Next.js 15 oferuje potężny komponent
Image, który automatycznie zajmuje się wieloma aspektami optymalizacji:Dzięki tym funkcjom, możesz tworzyć aplikacje, które ładują się szybciej, zużywają mniej danych i zapewniają lepsze doświadczenie użytkownika. Podobnie jak inżynierowie Metropolis Quantum optymalizują systemy wyświetlania hologramów i cyfrowych billboardów, tak ty możesz optymalizować obrazy w swojej aplikacji Next.js, aby zapewnić użytkownikom płynne i wydajne doświadczenie.
W następnym rozdziale poznamy różne strategie ładowania obrazów (lazy, eager, priority), które pozwolą jeszcze bardziej dostosować zachowanie obrazów do potrzeb twojej aplikacji.