Banyak developer Next.js yang asal pakai next/image tanpa
benar-benar paham cara kerjanya—dan akhirnya bingung waktu gambar
tiba-tiba tidak mau muncul. Di artikel ini, kita bedah tuntas ketiganya:
kelebihan, kekurangan, dan kapan harus pakai yang mana.
Halo! Kali ini kita akan bahas sesuatu yang kelihatannya sepele tapi
ternyata banyak banget yang salah kaprah:
cara menampilkan gambar di Next.js. Kamu punya tiga
pilihan next/image, next/image dengan prop
unoptimized, dan tag <img> HTML biasa.
Ketiganya bisa menampilkan gambar, tapi cara kerjanya, efeknya ke
performa, dan kondisi idealnya sangat berbeda.
Bagaimana next/image Bekerja di Balik Layar
Sebelum kita bandingkan, penting untuk pahami dulu arsitekturnya. Ketika
kamu pakai next/image, gambar tidak langsung dikirim dari
browser ke server sumber. Ada satu layer tambahan di tengah:
Next.js Image Optimization API.
// Alur next/image (default)
Browser → Next.js Server (/_next/image?url=...&w=...&q=...)
→ Fetch gambar dari source
→ Compress + Convert ke WebP/AVIF
→ Cache di disk server
→ Kirim ke Browser ✓
// Alur img HTML biasa / unoptimized
Browser → Langsung fetch ke URL sumber ✓
Inilah kenapa next/image bisa melakukan optimasi: karena
Next.js servernya yang berperan sebagai
image proxy sekaligus optimizer. Konsekuensinya, kalau
Next.js server tidak bisa mengakses URL sumber gambar (misalnya karena ada
di jaringan private), gambar tidak akan pernah muncul.
Opsi 1: next/image (Default / Optimized)
Ini adalah komponen resmi Next.js yang merupakan extension dari
tag <img> HTML biasa, tapi dengan superpower tambahan
di atasnya.
Cara Penggunaan Dasar
import Image from 'next/image' // 1. Local Image (dimensi otomatis terdeteksi) import profilePic from '@/public/images/profile.jpg' <Image src={profilePic} alt="Foto profil" /> // 2. Remote Image (dimensi wajib dideklarasi) <Image src="https://example.com/photo.jpg" alt="Foto dari server eksternal" width={800} height={600} quality={80} /> // 3. Fill Mode (mengisi parent container) <div style={{ position: 'relative', height: '400px' }}> <Image src="/hero.jpg" alt="Hero banner" fill style={{ objectFit: 'cover' }} sizes="100vw" /> </div>
Kelebihan
- Otomatis convert ke format modern (WebP/AVIF)
- Compress gambar sesuai kualitas yang ditentukan
- Resize otomatis sesuai lebar yang diminta
- Lazy loading aktif secara default
- Mencegah Cumulative Layout Shift (CLS)
- Gambar di-cache di server Next.js
- Mendukung placeholder blur saat loading
- Prop
altwajib diisi (aksesibilitas terjaga)
Kekurangan
- Next.js server harus bisa akses URL sumber gambar
-
Remote image perlu whitelist domain di
next.config.js - Ada CPU overhead di server saat gambar pertama kali diakses
-
Butuh deklarasi
width&heighteksplisit untuk remote image - Lebih verbose dan kompleks untuk gambar dinamis
priority sudah deprecated sejak Next.js
16. Sebagai gantinya, gunakan prop preload untuk gambar yang
ada di bagian atas halaman (above-the-fold) seperti hero image. Ini lebih
eksplisit dan menjelaskan behavior-nya dengan lebih jelas.
// ❌ Deprecated di Next.js 16 <Image src="/hero.jpg" priority ... /> // ✅ Gunakan ini sebagai gantinya <Image src="/hero.jpg" preload={true} ... />
Opsi 2: next/image dengan unoptimized
Ini adalah next/image yang "dilemahkan". Kamu tetap pakai
komponen yang sama, tapi Next.js tidak lagi melakukan proses optimasi di
server. Gambar akan diserve as-is langsung dari URL sumbernya.
<Image src="https://private-server.internal/photo.jpg" alt="Foto dari server private" width={800} height={600} unoptimized // ← bypass optimizer />
module.exports = { images: { unoptimized: true, // berlaku untuk semua Image di project }, }
Dengan unoptimized, yang terjadi di balik layar adalah
browser langsung fetch ke URL sumber, sama seperti tag
<img> biasa—tanpa melalui Next.js image proxy.
Yang Tetap Ada
- Lazy loading otomatis (default)
-
Mencegah Layout Shift (CLS) karena masih perlu
width&height - Prop
alttetap required (aksesibilitas) - Mode
filltetap berfungsi - Prop
sizestetap bisa digunakan
Yang Hilang
- Tidak ada konversi ke WebP/AVIF
- Tidak ada kompresi otomatis
- Tidak ada resize sesuai viewport
- Tidak ada cache di server Next.js
- Gambar berat tetap dikirim besar ke browser
unoptimized sangat bergantung pada kondisi
server sumber gambar kamu. Kalau server sudah menyajikan gambar dalam
format WebP dan sudah terkompresi dengan baik, dampaknya minimal. Tapi
kalau server menyajikan gambar PNG/JPEG raw berukuran besar, pengguna
mobile akan merasakan perbedaan yang signifikan.
Opsi 3: <img> HTML Biasa
Tag HTML paling tradisional. Tidak ada magic di baliknya—browser langsung fetch gambar dari URL yang diberikan, tanpa preprocessing apapun dari Next.js.
// Paling sederhana <img src="/photo.jpg" alt="Deskripsi gambar" /> // Kalau mau lazy loading, harus tulis manual <img src="/photo.jpg" alt="Deskripsi gambar" loading="lazy" width="800" height="600" />
Kelebihan
- Paling simpel dan tidak ada dependency
- Browser langsung fetch — cocok untuk gambar di balik VPN
- Tidak ada overhead di server Next.js
- Tidak perlu konfigurasi
remotePatterns - Cocok untuk email template, blob URL, atau gambar dekoratif
Kekurangan
- Tidak ada optimasi otomatis sama sekali
- Lazy loading harus ditambahkan manual
- Rentan menyebabkan Layout Shift jika tidak set dimensi
- Tidak ada enforcement
altdari framework - Tidak ada srcset generation otomatis
Perbandingan Lengkap Ketiganya
| Fitur | next/image | next/image unoptimized | <img> HTML |
|---|---|---|---|
| Convert ke WebP/AVIF | ✓ Otomatis | ✗ Tidak | ✗ Tidak |
| Kompresi otomatis | ✓ Ya | ✗ Tidak | ✗ Tidak |
| Resize sesuai viewport | ✓ Ya | ✗ Tidak | ✗ Tidak |
| Lazy loading | ✓ Default | ✓ Default | ~ Manual |
| Prevent CLS | ✓ Ya | ✓ Ya | ~ Manual |
| Cache di server | ✓ Ya | ✗ Tidak | ✗ Tidak |
| Fill mode | ✓ Ya | ✓ Ya | ✗ Tidak |
| Alt enforcement | ✓ Required | ✓ Required | ✗ Opsional |
| srcset generation | ✓ Otomatis | ~ Terbatas | ✗ Manual |
| Placeholder blur | ✓ Ya | ✗ Tidak | ✗ Tidak |
| Perlu konfigurasi domain | ✗ Ya (remotePatterns) | ~ Tidak selalu | ✓ Tidak perlu |
| Browser fetch langsung | ✗ Tidak (via server) | ✓ Ya | ✓ Ya |
| Cocok untuk private server | ✗ Bermasalah | ✓ Ya | ✓ Ya |
Tiga Fitur Penting yang Hanya Ada di next/image
Ada tiga fitur yang sering disepelekan tapi sebenarnya sangat berdampak, terutama untuk SEO dan aksesibilitas:
1. Prevent CLS (Cumulative Layout Shift)
CLS adalah metrik Google Core Web Vitals yang mengukur seberapa banyak konten bergeser saat halaman loading. Ketika gambar belum load dan tidak ada ruang yang disiapkan, teks dan elemen lain akan "melompat" ke bawah saat gambar tiba-tiba muncul. Ini bisa sangat mengganggu pengguna—bayangkan lagi mau klik tombol, tiba-tiba tombolnya bergeser.
next/image mengatasi ini karena kamu
wajib mendeklarasikan width dan
height—dari data ini, browser bisa mereservasi ruang yang
tepat sebelum gambar selesai diload. Skor CLS yang buruk juga berdampak
langsung ke ranking SEO di Google.
2. Alt Enforcement (Aksesibilitas)
Prop alt adalah Required di
next/image. Ini bukan sekadar formalitas—screen reader (alat
bantu untuk tunanetra) menggunakan teks alt untuk
mendeskripsikan gambar. Untuk gambar dekoratif yang tidak membawa
informasi, kamu tetap harus menulisnya, cukup dengan alt="".
// Gambar informatif — deskripsikan kontennya <Image src="/chart.png" alt="Grafik penjualan Q1 2026" ... /> // Gambar dekoratif — kosongkan, tapi tetap tulis atributnya <Image src="/divider.png" alt="" ... /> // ❌ JANGAN begini — akan error di next/image <Image src="/photo.jpg" ... />
3. Fill Mode
Mode fill membuat gambar mengisi ukuran parent container-nya
secara penuh. Ini sangat berguna untuk hero banner, card thumbnail dengan
rasio seragam, atau background section. Kunci utamanya:
parent element wajib memiliki
position: relative, absolute, atau
fixed.
// Hero banner full width <div style={{ position: 'relative', height: '500px', width: '100%' }}> <Image src="/hero.jpg" alt="Banner halaman utama" fill style={{ objectFit: 'cover' }} sizes="100vw" preload={true} // ← gunakan ini, bukan priority (deprecated) /> </div>
Studi Kasus: Gambar Tidak Muncul di Balik VPN / Private Server
Ini adalah kasus yang sangat umum tapi sering bikin pusing: kamu pakai
next/image dengan sumber gambar dari server internal (di
balik VPN atau jaringan private), tapi gambar tidak mau muncul sama
sekali.
Root cause-nya bukan di browser atau koneksi kamu—melainkan di server Next.js itu sendiri yang tidak punya akses VPN untuk fetch gambar dari server internal.
// Yang punya akses VPN: Browser / Laptop developer ✅ // Yang TIDAK punya akses VPN: Next.js Server di production ❌ // Akibatnya: Browser → Next.js Server → ❌ Private Image Server (fetch gagal, gambar tidak muncul)
Solusi yang Tersedia
Solusi A — unoptimized (Paling Cepat)
module.exports = { images: { unoptimized: true, // browser fetch langsung, bypass server }, }
Solusi B — Custom Loader (Lebih Proper)
'use client' const privateLoader = ({ src, width, quality }) => { // return URL langsung — browser yang akan fetch return `${src}?w=${width}&q=${quality || 75}` } <Image loader={privateLoader} src="https://private-server.internal/photo.jpg" alt="Foto internal" width={800} height={600} />
Solusi C — <img> HTML Biasa
<img src="https://private-server.internal/photo.jpg" alt="Foto internal" loading="lazy" width="800" height="600" />
| Solusi | Effort | Optimasi Tetap Ada? | Cocok Untuk |
|---|---|---|---|
| unoptimized: true | Sangat rendah | Tidak | Dev/staging, internal tools |
| Custom loader | Sedang | Sebagian | Production, butuh fitur next/image |
| <img> biasa | Paling rendah | Tidak | Quick fix, gambar sudah dioptimasi |
| Deploy Next.js di VPN | Tinggi | Ya, penuh | Production skala besar jangka panjang |
Panduan Memilih: A, B, atau C?
Supaya lebih mudah, ini decision guide berdasarkan kondisi nyata yang paling sering ditemui:
✓ Gunakan next/image
- Gambar statis (hero, product, blog thumbnail)
- Gambar dari CDN publik (Cloudinary, S3, dll)
- Situs publik yang prioritaskan SEO & Core Web Vitals
- Butuh placeholder blur saat loading
- Gambar dengan ukuran besar yang perlu kompresi
- Layout fluid yang butuh fill mode
~ Gunakan unoptimized
- Gambar dari server private / VPN
- Server sumber sudah optimasi gambar sendiri
- Aplikasi internal / tools (bukan publik)
- Staging / development environment
- Gambar SVG atau GIF animasi
- Ingin tetap pakai struktur next/image tapi performa bukan prioritas
→ Gunakan <img> biasa
- Gambar blob URL hasil upload user (dinamis)
- Icon sangat kecil (< 1KB) yang tidak perlu optimasi
- Email template atau konteks non-Next.js
- Gambar yang diakses via VPN dan tidak butuh fitur next/image
- Integrasi library pihak ketiga yang punya image handling sendiri
Checklist Sebelum Memutuskan
Apakah gambar berasal dari server yang bisa diakses Next.js? → TIDAK → Pakai unoptimized atau <img> biasa → YA → Lanjut ↓ Apakah ini aplikasi publik yang butuh SEO & performa optimal? → YA → Pakai next/image (default) → TIDAK → unoptimized atau <img> biasa bisa jadi pilihan Apakah gambar sangat kecil / SVG / GIF animasi? → YA → Pakai unoptimized atau <img> biasa → TIDAK → next/image memberikan nilai lebih Apakah butuh placeholder blur / fill mode / auto srcset? → YA → Hanya next/image yang punya ini → TIDAK → Semua opsi bisa dipertimbangkan
unoptimized untuk SVG, atau
aktifkan dangerouslyAllowSVG: true di
next.config.js (dengan konfigurasi CSP yang tepat). Kalau
src berakhiran .svg,
unoptimized sudah aktif secara otomatis.
Kesimpulan
Kalau harus diringkas dalam satu kalimat:
next/image adalah pilihan terbaik untuk performa, tapi ia
punya prasyarat—Next.js server harus bisa mengakses sumber
gambarnya.
Ketika prasyarat itu tidak bisa dipenuhi (misalnya gambar ada di balik
VPN), unoptimized adalah jembatan yang masuk akal—kamu tetap
punya lazy loading otomatis dan CLS prevention, tapi tanpa overhead
optimasi di server. Sedangkan <img> HTML biasa valid
digunakan untuk kasus-kasus spesifik seperti blob URL, gambar sangat
kecil, atau konteks di luar Next.js.
Tidak ada jawaban yang selalu benar. Yang penting adalah memahami cara kerja masing-masing, lalu pilih yang sesuai dengan kebutuhan dan kondisi proyek kamu.
priority sudah deprecated sejak Next.js
16. Kalau kamu masih pakai ini di kodebase, segera ganti dengan
preload={true}. Pastikan selalu cek dokumentasi resmi di
nextjs.org/docs
karena Next.js cukup aktif melakukan perubahan API.
Mungkin cukup sekian untuk pembahasan kali ini, semoga artikel ini bisa bermanfaat dan membantu kamu menentukan pilihan yang tepat saat bekerja dengan gambar di Next.js. Kalau ada pertanyaan atau pengalaman serupa, silakan tinggalkan komentar di bawah. Terimakasih!

Posting Komentar