Image optimization đúng giảm LCP 2-3x, save bandwidth 50-80%. Bài này deep-dive next/image — srcset, priority, sizes, placeholder — và setup CDN cho production scale.
Vì sao image quan trọng?
Image chiếm 50-70% bytes của page hiện đại. LCP element thường là hero image. Tối ưu image = quick win lớn nhất cho perf.
next/image cơ bản
import Image from 'next/image'
<Image
src="/photo.jpg"
alt="Mô tả ảnh có ý nghĩa"
width={800}
height={600}
/>
Next tự động:
- Generate srcset cho nhiều viewport (640w, 750w, 828w, 1080w, 1200w...)
- Convert sang WebP/AVIF nếu browser support
- Lazy load (loading="lazy" mặc định)
- Set width/height attribute → reserve space, tránh CLS
Responsive image với sizes
<Image
src="/banner.jpg"
alt="Banner"
width={1200}
height={630}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 1200px"
/>
Đọc: mobile chiếm 100% viewport → browser pick srcset gần với viewport width. Tablet 50% → pick srcset = viewport/2. Desktop max 1200px.
Sai sizes thường gặp:
- Bỏ sizes → browser pick lớn nhất → mobile tải 1200w trên 375w viewport
- Sai breakpoint với CSS thực tế
priority cho LCP
// Hero — LCP element
<Image
src="/hero.webp"
alt="Hero"
width={1920}
height={1080}
priority // ← preload + bỏ lazy
sizes="100vw"
/>
// Image dưới fold — không priority
<Image src="/section-2.jpg" alt="..." width={800} height={600} sizes="50vw" />
Mỗi page chỉ 1 priority image. Nhiều = race tải, đảo lộn LCP.
Blur placeholder
// Local image — blurDataURL auto generated
import hero from '@/public/hero.jpg' // Next plugin tự process
<Image
src={hero}
alt="Hero"
placeholder="blur"
// blurDataURL auto inject
/>
// Remote image — phải tự cung cấp blurDataURL
const blurDataURL = 'data:image/jpeg;base64,/9j/4AAQ...' // generate qua plaiceholder lib
<Image
src="https://cdn.alodev.vn/photo.jpg"
alt="..."
width={800}
height={600}
placeholder="blur"
blurDataURL={blurDataURL}
/>
Fill mode cho responsive container
// Khi không biết kích thước cụ thể, dùng fill + parent có position: relative
<div style={{ position: 'relative', aspectRatio: '16/9' }}>
<Image
src="/banner.jpg"
alt="Banner"
fill
sizes="100vw"
style={{ objectFit: 'cover' }}
/>
</div>
CDN production: Cloudflare Images
Self-host Next.js resize tốn CPU + memory. Production scale dùng external image service:
// next.config.ts
export default {
images: {
loader: 'custom',
loaderFile: './lib/image-loader.ts',
},
}
// lib/image-loader.ts — Cloudflare Images
export default function loader({ src, width, quality }) {
const params = ['format=auto', `width=${width}`, `quality=${quality || 75}`]
return `https://imagedelivery.net/${ACCOUNT_HASH}/${src}/${params.join(',')}`
}
Cloudflare $5/100k delivery, edge cache 200+ POP. R2 + Workers Image Resizing là alternative rẻ hơn.
WebP, AVIF — chọn nào?
| Format | Kích thước | Browser support |
|---|---|---|
| JPEG | 100% baseline | Mọi browser |
| WebP | ~70% JPEG | 97%+ browser |
| AVIF | ~50% JPEG | 92%+ browser (2026) |
Next.js mặc định serve AVIF nếu Accept header hỗ trợ, fallback WebP, fallback JPEG. Anh không phải lo.
Checklist image optimization
- ✅ Mọi
<img>dùngnext/image - ✅ width + height (hoặc fill + container có aspect-ratio)
- ✅ alt mô tả ý nghĩa, không "image" generic
- ✅ priority cho LCP image, lazy default cho rest
- ✅ sizes prop khớp CSS breakpoint thực tế
- ✅ Local image dùng import → auto blur
- ✅ Remote image whitelist trong next.config.ts
- ✅ Production: external CDN (Cloudflare Images, Imgix)
- ✅ Format AVIF/WebP auto serve
Kết luận
Image optimization đúng là một trong những đầu tư có ROI cao nhất cho perf. next/image cover 90% case sẵn — chỉ cần dùng đúng API. Production scale: external CDN tránh backend Next resize tốn CPU. Tham khảo Web Vitals để đo impact thực tế lên LCP.