Witaj @name! Czas stworzyć Treasure Gallery - piracką galerię zdjęć inspirowaną Instagramem. 📸
Zanim zaczniemy budować galerię, musisz nauczyć się jak Next.js obsługuje obrazy. Spoiler: znacznie lepiej niż zwykły
<img>!Tradycyjny tag
<img> ma kilka problemów:❌ Brak automatycznej optymalizacji - duże pliki spowalniają stronę ❌ Brak lazy loading - wszystkie obrazy ładują się od razu ❌ Brak responsywności - jeden rozmiar dla wszystkich urządzeń ❌ Cumulative Layout Shift (CLS) - strona "skacze" gdy obrazy się ładują
Next.js ma wbudowany komponent
<Image> który automatycznie:✅ Optymalizuje obrazy do formatu WebP/AVIF ✅ Lazy loading - ładuje tylko widoczne obrazy ✅ Responsive - dostarcza odpowiedni rozmiar dla urządzenia ✅ Zapobiega CLS - rezerwuje miejsce przed załadowaniem
1import Image from 'next/image';
2
3export default function Gallery() {
4 return (
5 <Image
6 src="/treasure-map.jpg"
7 alt="Mapa skarbów"
8 width={800}
9 height={600}
10 />
11 );
12}Ważne: Musisz podać
width i height - Next.js potrzebuje ich do optymalizacji.Pliki w folderze
public/ są dostępne bezpośrednio przez URL.1public/
2├── images/
3│ ├── treasure-1.jpg
4│ ├── treasure-2.jpg
5│ └── pirate-ship.jpg
6└── logo.png1<Image
2 src="/images/treasure-1.jpg"
3 alt="Złoty skarb"
4 width={600}
5 height={400}
6/>Ścieżka zaczyna się od
- oznacza folder /
public/.Możesz też ładować obrazy z zewnętrznych źródeł, ale musisz je skonfigurować.
1// next.config.js
2module.exports = {
3 images: {
4 remotePatterns: [
5 {
6 protocol: 'https',
7 hostname: 'images.unsplash.com',
8 },
9 {
10 protocol: 'https',
11 hostname: 'i.imgur.com',
12 },
13 ],
14 },
15};1<Image
2 src="https://images.unsplash.com/photo-1234567890"
3 alt="Piracki statek"
4 width={800}
5 height={600}
6/>Gdy nie znasz dokładnych wymiarów lub chcesz responsywny obrazek, użyj
fill.1<div className="relative w-full h-96">
2 <Image
3 src="/treasure-map.jpg"
4 alt="Mapa"
5 fill
6 className="object-cover"
7 />
8</div>Ważne:
position: relativeobject-cover lub object-contain dla kontroli skalowania1// object-cover - obraz wypełnia kontener, może być przycinany
2<Image
3 src="/wide-image.jpg"
4 alt="Szeroki obraz"
5 fill
6 className="object-cover"
7/>
8
9// object-contain - cały obraz widoczny, może być pusty obszar
10<Image
11 src="/tall-image.jpg"
12 alt="Wysoki obraz"
13 fill
14 className="object-contain"
15/>Dla ważnych obrazów (powyżej fold - widocznych od razu), użyj
priority:1<Image
2 src="/hero-banner.jpg"
3 alt="Banner główny"
4 width={1200}
5 height={600}
6 priority
7/>To mówi Next.js: "Załaduj ten obraz natychmiast, nie czekaj".
Next.js może pokazać rozmyty placeholder podczas ładowania obrazu:
1<Image
2 src="/treasure.jpg"
3 alt="Skarb"
4 width={600}
5 height={400}
6 placeholder="blur"
7 blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." // Base64 tiny image
8/>Lub automatycznie dla lokalnych obrazów:
1import treasureImage from '@/public/treasure.jpg';
2
3<Image
4 src={treasureImage}
5 alt="Skarb"
6 placeholder="blur"
7/>1import Image from 'next/image';
2
3interface GalleryPost {
4 id: number;
5 imageUrl: string;
6 caption: string;
7 author: string;
8 likes: number;
9}
10
11function GalleryGrid({ posts }: { posts: GalleryPost[] }) {
12 return (
13 <div className="grid grid-cols-3 gap-1">
14 {posts.map((post) => (
15 <div key={post.id} className="relative aspect-square group cursor-pointer">
16 <Image
17 src={post.imageUrl}
18 alt={post.caption}
19 fill
20 className="object-cover"
21 />
22
23 {/* Overlay przy hover */}
24 <div className="absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
25 <div className="text-white text-center">
26 <p className="font-bold">❤️ {post.likes}</p>
27 <p className="text-sm">{post.caption}</p>
28 </div>
29 </div>
30 </div>
31 ))}
32 </div>
33 );
34}Pokaż placeholder ładowania, gdy obrazy się wczytują:
1'use client';
2
3import Image from 'next/image';
4import { useState } from 'react';
5
6export default function PhotoCard({ src, alt }: { src: string; alt: string }) {
7 const [isLoading, setIsLoading] = useState(true);
8
9 return (
10 <div className="relative aspect-square">
11 {isLoading && (
12 <div className="absolute inset-0 bg-gray-200 animate-pulse" />
13 )}
14
15 <Image
16 src={src}
17 alt={alt}
18 fill
19 className={`object-cover transition-opacity duration-300 ${
20 isLoading ? 'opacity-0' : 'opacity-100'
21 }`}
22 onLoad={() => setIsLoading(false)}
23 />
24 </div>
25 );
26}Powiedz Next.js jakie rozmiary obrazu potrzebujesz dla różnych ekranów:
1<Image
2 src="/treasure.jpg"
3 alt="Skarb"
4 fill
5 sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
6 className="object-cover"
7/>Znaczenie:
Domyślnie Next.js kompresuje obrazy do quality=75. Możesz to zmienić:
1<Image
2 src="/high-quality-art.jpg"
3 alt="Dzieło sztuki"
4 width={1200}
5 height={800}
6 quality={90} // Wyższa jakość (większy plik)
7/>✅ Next.js Image - automatyczna optymalizacja ✅ width/height - wymagane dla lokalnych obrazów ✅ fill - responsive images w kontenerze ✅ priority - wyłącz lazy loading dla ważnych obrazów ✅ placeholder="blur" - efekt rozmycia podczas ładowania ✅ object-cover - obraz wypełnia kontener ✅ object-contain - cały obraz widoczny ✅ remotePatterns - konfiguracja zewnętrznych źródeł ✅ sizes - responsywne rozmiary dla optymalizacji
W następnym ćwiczeniu zbudujemy grid galerii z modalami! 📸
Do zobaczenia! 🚀