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

JSX app/page.jsx
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 alt wajib 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 & height eksplisit untuk remote image
  • Lebih verbose dan kompleks untuk gambar dinamis
Catatan Penting (Next.js 16+) Prop 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.
JSX Penggunaan preload (Next.js 16+)
// ❌ 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.

JSX Per komponen
<Image
  src="https://private-server.internal/photo.jpg"
  alt="Foto dari server private"
  width={800}
  height={600}
  unoptimized  // ← bypass optimizer
/>
JavaScript next.config.js — Global setting
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 alt tetap required (aksesibilitas)
  • Mode fill tetap berfungsi
  • Prop sizes tetap 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
Perlu Diperhatikan Dampak nyata dari 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.

JSX / HTML Penggunaan
// 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 alt dari 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="".

JSX Contoh penggunaan 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.

JSX Contoh fill mode
// 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.
Diagram Visualisasi masalah
// 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)

JavaScript next.config.js
module.exports = {
  images: {
    unoptimized: true, // browser fetch langsung, bypass server
  },
}

Solusi B — Custom Loader (Lebih Proper)

JSX Penggunaan custom loader
'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

JSX Paling sederhana
<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

Checklist Decision flow
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
Tips dari Dokumentasi Resmi Untuk gambar SVG, Next.js secara default tidak mengoptimasi SVG karena alasan keamanan. Gunakan prop 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.

Satu Hal yang Jangan Sampai Lupa Prop 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!