Welcome @name! Time to build Treasure Gallery - a pirate photo gallery inspired by Instagram. 📸
Before we start building the gallery, you need to learn how Next.js handles images. Spoiler: much better than a plain
<img>!The traditional
<img> tag has several problems:❌ No automatic optimization - large files slow down the page ❌ No lazy loading - all images load at once ❌ No responsiveness - one size for all devices ❌ Cumulative Layout Shift (CLS) - the page "jumps" when images load
Next.js has a built-in
<Image> component that automatically:✅ Optimizes images to WebP/AVIF format ✅ Lazy loading - only loads visible images ✅ Responsive - delivers the right size for the device ✅ Prevents CLS - reserves space before loading
1import Image from 'next/image';
2
3export default function Gallery() {
4 return (
5 <Image
6 src="/treasure-map.jpg"
7 alt="Treasure map"
8 width={800}
9 height={600}
10 />
11 );
12}Important: You must provide
width and height - Next.js needs them for optimization.Files in the
public/ folder are directly accessible via 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="Golden treasure"
4 width={600}
5 height={400}
6/>The path starts with
- this means the /
public/ folder.You can also load images from external sources, but you need to configure them.
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="Pirate ship"
4 width={800}
5 height={600}
6/>When you don't know the exact dimensions or want a responsive image, use
fill.1<div className="relative w-full h-96">
2 <Image
3 src="/treasure-map.jpg"
4 alt="Map"
5 fill
6 className="object-cover"
7 />
8</div>Important:
position: relativeobject-cover or object-contain to control scaling1// object-cover - image fills the container, may be cropped
2<Image
3 src="/wide-image.jpg"
4 alt="Wide image"
5 fill
6 className="object-cover"
7/>
8
9// object-contain - entire image visible, may have empty space
10<Image
11 src="/tall-image.jpg"
12 alt="Tall image"
13 fill
14 className="object-contain"
15/>For important images (above the fold - visible immediately), use
priority:1<Image
2 src="/hero-banner.jpg"
3 alt="Main banner"
4 width={1200}
5 height={600}
6 priority
7/>This tells Next.js: "Load this image immediately, don't wait".
Next.js can show a blurred placeholder while the image loads:
1<Image
2 src="/treasure.jpg"
3 alt="Treasure"
4 width={600}
5 height={400}
6 placeholder="blur"
7 blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." // Base64 tiny image
8/>Or automatically for local images:
1import treasureImage from '@/public/treasure.jpg';
2
3<Image
4 src={treasureImage}
5 alt="Treasure"
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 on 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}Show a loading placeholder while images load:
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}Tell Next.js what image sizes you need for different screens:
1<Image
2 src="/treasure.jpg"
3 alt="Treasure"
4 fill
5 sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
6 className="object-cover"
7/>Meaning:
By default Next.js compresses images to quality=75. You can change this:
1<Image
2 src="/high-quality-art.jpg"
3 alt="Artwork"
4 width={1200}
5 height={800}
6 quality={90} // Higher quality (larger file)
7/>✅ Next.js Image - automatic optimization ✅ width/height - required for local images ✅ fill - responsive images in a container ✅ priority - disable lazy loading for important images ✅ placeholder="blur" - blur effect while loading ✅ object-cover - image fills the container ✅ object-contain - entire image visible ✅ remotePatterns - configuration for external sources ✅ sizes - responsive sizes for optimization
In the next exercise we'll build a gallery grid with modals! 📸
See you there! 🚀