Skip to main content
BlogWeb Dev

Cara Meningkatkan Core Web Vitals Next.js untuk Pemula

Cara meningkatkan Core Web Vitals Next.js untuk pemula: ukur LCP, CLS, INP, atur priority dan sizes next/image, pakai next/font, plus error umum.

Matthews Wong

Software Engineer

Oktober 202610 menit baca
Cara Meningkatkan Core Web Vitals Next.js untuk Pemula

Ringkasan Utama

Cara meningkatkan Core Web Vitals di Next.js: ukur dulu dengan PageSpeed Insights, beri gambar hero priority serta width dan height atau fill, tulis sizes untuk gambar responsif, pakai width dan height tetap untuk gambar kecil, dan muat font lewat next/font. Target baik menurut web.dev: LCP 2,5 detik, INP 200 milidetik, CLS 0,1.

Kamu menjalankan PageSpeed Insights untuk pertama kali dan melihat LCP berwarna merah, padahal halamannya hanya berisi satu gambar besar dan beberapa teks. Kamu menambahkan komponen Image seperti di dokumentasi, tetapi skornya tidak berubah. Atau teks di atas lipatan melompat sesaat setelah font masuk. Pemula biasanya tersangkut di sini karena tidak tahu bagian mana yang lambat, jadi semua perbaikan terasa seperti tebakan.

Panduan ini membenahi tiga hal yang paling sering menjadi penyebab: gambar hero, ukuran gambar responsif, dan font. Semua nama prop dan perilakunya saya cocokkan dengan dokumentasi Next.js 15 dan web.dev. Contohnya mengikuti pola yang dipakai situs ini: hero blog memakai fill, sizes, dan priority, logo kecil memakai width dan height tetap, dan font dihosting sendiri lewat next/font/local. Saya tidak menjanjikan berapa milidetik yang kamu hemat; itu harus kamu ukur di halamanmu sendiri.

Ingin memahami konsepnya dulu dari nol? Ada pelajaran gratis Core Web Vitals, next/image, dan next/font dengan diagram interaktif dan kuis. Artikel ini adalah versi praktik langkah demi langkahnya.

Yang kamu butuhkan

  • Project Next.js 15 dengan App Router yang bisa di-build (npm run build).
  • Satu halaman dengan gambar besar di bagian atas (hero) dan beberapa gambar kecil seperti logo.
  • Browser Chrome dengan DevTools, dan akses ke pagespeed.web.dev untuk situs yang sudah online.
  • Kemauan mengukur sebelum dan sesudah setiap perubahan, satu perubahan per kali.

Kalau kamu butuh gambaran performa Next.js yang lebih luas, baca juga panduan optimasi performa Next.js setelah selesai di sini.

Langkah 1: Ukur dulu, bedakan data lapangan dan data lab

Sebelum mengubah kode, ketahui angka awalnya. PageSpeed Insights menampilkan dua jenis data. Data lapangan berasal dari Chrome User Experience Report (CrUX), yaitu kunjungan nyata selama 28 hari terakhir, dan ditampilkan pada persentil ke-75. Data lab berasal dari Lighthouse yang berjalan di lingkungan simulasi. Jika halamanmu belum punya cukup sampel, PageSpeed Insights memakai data tingkat origin, dan kalau itu pun kurang, tidak ada data lapangan yang ditampilkan.

# Ukur build produksi, bukan npm run dev
npm run build && npm start

# Situs yang sudah online: tempel URL halamanmu di
# https://pagespeed.web.dev/
# Baca dua bagian: data lapangan (field) di atas, data lab (Lighthouse) di bawah.

Tiga metrik dan batas baik menurut web.dev, semuanya dinilai pada persentil ke-75: LCP (Largest Contentful Paint, kecepatan elemen terbesar muncul) 2,5 detik atau kurang, INP (Interaction to Next Paint, respons terhadap klik dan ketukan) 200 milidetik atau kurang, dan CLS (Cumulative Layout Shift, seberapa banyak tata letak bergeser) 0,1 atau kurang. Hasil yang kamu harapkan dari langkah ini: tiga angka awal yang kamu catat, dan kamu tahu mana yang merah.

Lighthouse tidak bisa mengukur INP karena tidak ada interaksi pengguna; ia memakai Total Blocking Time sebagai pengganti di lab. Data lab dan data lapangan bisa berbeda karena kondisinya berbeda. Pakai lab untuk mencari penyebab, pakai lapangan untuk menilai hasil akhir.

Langkah 2: Perbaiki gambar hero dengan priority

LCP biasanya adalah gambar atau blok teks terbesar yang terlihat saat halaman dibuka, dan di banyak halaman itu adalah gambar hero. Komponen Image dari next/image melakukan lazy load secara default. web.dev menyatakan jangan pernah me-lazy-load gambar LCP, karena unduhannya baru dimulai setelah browser memastikan gambar itu ada di viewport. Pada hero, tambahkan priority.

import Image from "next/image";

// Wrong: hero ini elemen LCP, tetapi next/image lazy load secara default,
// jadi unduhannya baru mulai setelah layout selesai.
<Image src="/hero.jpg" alt="Meja kerja" width={1200} height={630} />

// Right: priority = preload gambar + matikan lazy loading.
// width dan height memberi rasio aspek, jadi ruangnya sudah dipesan (CLS aman).
<Image
  src="/hero.jpg"
  alt="Meja kerja"
  width={1200}
  height={630}
  priority
/>

Untuk hero yang lebarnya mengikuti layar, pakai fill di dalam pembungkus yang punya rasio aspek tetap. Dokumentasi mensyaratkan induk dari gambar fill berposisi relative, fixed, atau absolute. Beginilah hero blog di situs ini dibuat.

// Gambar fluid: fill + pembungkus berposisi + sizes.
// Pembungkus punya rasio aspek tetap, jadi kotaknya ada sebelum gambarnya.
<div className="relative aspect-[1200/630] w-full">
  <Image
    src="/hero.jpg"
    alt="Meja kerja"
    fill
    sizes="(max-width: 768px) calc(100vw - 2rem), 768px"
    className="object-cover"
    priority
  />
</div>

// Wrong: fill tanpa pembungkus berposisi (relative, fixed, atau absolute).

Hasil yang diharapkan: gambar hero ikut diunduh sejak awal dan ruangnya sudah dipesan, sehingga tidak ada lompatan saat gambar muncul. Ingat, width dan height adalah ukuran intrinsik untuk menghitung rasio aspek; ukuran tampilan tetap diatur CSS. Keduanya wajib kecuali gambar diimpor statis atau memakai fill.

Langkah 3: Tulis sizes untuk gambar responsif

Browser tidak tahu selebar apa gambar akan ditampilkan saat membaca HTML, jadi sizes yang memberitahunya. Menurut dokumentasi Next.js, jika sizes hilang browser mengira gambar selebar viewport, yang bisa mengunduh gambar yang tidak perlu besar. Pakai sizes setiap kali gambar memakai fill atau dibuat responsif lewat CSS.

// Wrong: tanpa sizes, browser mengira gambar selebar layar (100vw),
// jadi ponsel bisa mengunduh file yang jauh lebih besar dari kartunya.
<Image src={post.cover} alt={post.title} fill />

// Right: sizes menjelaskan lebar tampilan yang sebenarnya per breakpoint.
<Image
  src={post.cover}
  alt={post.title}
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

sizes juga mengubah markup: tanpa sizes, Next.js menghasilkan srcset terbatas (misalnya 1x dan 2x) yang cocok untuk gambar berukuran tetap; dengan sizes, ia menghasilkan srcset lengkap berisi lebar seperti 640w dan 750w. Browser hanya mengunduh satu kandidat yang dipilihnya. Periksa HTML hasil build untuk memastikannya.

# Gambar ber-fill harus menampilkan string sizes yang kamu tulis
curl -s http://localhost:3000/ | grep -o 'sizes="[^"]*"' | head

# Dengan sizes, srcset berisi daftar lebar (kira-kira seperti ini):
# srcset="... 640w, ... 750w, ... 828w, ... 1080w, ..."
# Tanpa sizes, srcset hanya berisi 1x dan 2x.

Langkah 4: Pakai width dan height tetap untuk gambar kecil

Kebalikannya juga penting. Marquee sertifikasi di situs ini menampilkan logo 44 piksel. Komentar di kodenya mencatat bahwa fill dengan sizes berbasis piksel mencantumkan setiap lebar terkonfigurasi di srcset, sekitar 1 KB HTML per tile. Perbaikannya adalah ukuran tetap.

// Wrong: logo 44 px memakai fill + sizes piksel. Setiap lebar terkonfigurasi
// masuk ke srcset untuk TIAP logo, sekitar 1 KB HTML per tile di situs ini.
<div className="relative h-11 w-11">
  <Image src={logo} alt="" fill sizes="44px" />
</div>

// Right: ukuran tetap = width dan height saja. srcset hanya 1x dan 2x.
<Image src={logo} alt="" width={44} height={44} />

Aturan sederhananya: gambar fluid seperti hero dan sampul kartu memakai fill dan sizes; gambar berukuran tetap seperti logo dan avatar memakai width dan height. Kalau kamu penasaran bagaimana srcset dan cache gambar bekerja di balik layar, baca bedah internal optimasi gambar next/image.

Langkah 5: Muat font lewat next/font

next/font melakukan self-hosting file font bersama aset statis lainnya, sehingga browser tidak mengirim request ke penyedia font. Untuk font Google, CSS dan file diunduh saat build. Menurut dokumentasi, ini memungkinkan memuat font web tanpa layout shift. Default display adalah swap dan default preload adalah true. Rekomendasi dokumentasi: pakai font variabel.

// app/fonts.ts: muat font SEKALI di satu modul
import { Inter } from "next/font/google";

// Font variabel: tidak perlu menulis weight
export const inter = Inter({
  subsets: ["latin"],  // tanpa subsets, preload memberi peringatan
  display: "swap",
  variable: "--font-inter",
});

// app/layout.tsx
import { inter } from "./fonts";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="id" className={inter.variable}>
      <body>{children}</body>
    </html>
  );
}

Kalau build di CI tidak boleh bergantung pada jaringan ke penyedia font, simpan file-nya di repo dan pakai next/font/local. Situs ini pindah ke cara itu setelah build Docker di CI gagal mengunduh font dari Google dan seluruh deploy ikut gagal. Satu file font variabel sekarang mencakup bobot 400 sampai 800.

// Alternatif: file font ada di repo, build tidak butuh jaringan
import localFont from "next/font/local";

export const myFont = localFont({
  src: "./my-font.woff2",   // relatif terhadap file yang memanggil localFont
  weight: "400 800",        // rentang bobot untuk font variabel
  display: "swap",
  variable: "--font-my",
});

// Wrong: memanggil Inter() atau localFont() di banyak file.
// Tiap pemanggilan menghosting satu instance font lagi.

Menurut dokumentasi, next/font menyediakan opsi adjustFontFallback (aktif secara default untuk font Google) agar font cadangan disesuaikan dan CLS berkurang. Untuk font lokal, nilainya Arial, Times New Roman, atau false. Jangan matikan kecuali kamu tahu alasannya.

Langkah 6: Verifikasi hasilnya

Build produksi lalu baca HTML yang benar-benar dikirim, bukan kodenya. Hasil yang diharapkan: ada link preload untuk gambar hero, gambar responsif membawa string sizes yang kamu tulis, dan tidak ada request ke penyedia font.

npm run build && npm start

# 1. Hero harus punya link preload di head
curl -s http://localhost:3000/ | grep -o 'rel="preload"[^>]*' | head

# 2. Gambar responsif harus membawa sizes yang kamu tulis
curl -s http://localhost:3000/ | grep -o 'sizes="[^"]*"' | head

# 3. Tidak boleh ada request ke penyedia font saat halaman dibuka
#    (cek tab Network di DevTools; font dilayani dari domainmu sendiri)

Setelah itu jalankan PageSpeed Insights lagi dan bandingkan dengan angka di Langkah 1. Data lab berubah segera, tetapi data lapangan memakai jendela 28 hari, jadi perbaikan baru terlihat penuh setelah beberapa minggu.

Bagaimana dengan INP? next/image dan next/font menyasar pemuatan dan kestabilan. INP menyangkut kecepatan respons setelah klik atau ketukan, dan itu biasanya soal pekerjaan berat di event handler, bukan gambar. Ukur interaksi nyata sebelum menebak.

Error umum dan cara memperbaikinya

Tujuh dari delapan masalah ini adalah kesalahan penerapan yang gampang dikenali; yang kedelapan adalah salah paham cara membaca angka.

GejalaPenyebabPerbaikan
LCP merah, hero muncul terlambatHero memakai default next/image, yaitu lazy load, sehingga unduhan menunggu layoutTambahkan priority pada gambar hero
Setelah menambah priority, LCP tidak membaikpriority dipasang di banyak gambar, padahal web.dev menyebut lebih dari satu atau dua gambar membuatnya tidak membantuPasang hanya pada gambar yang menjadi elemen LCP
Next.js meminta width dan height, atau tata letak melompat saat gambar munculRasio aspek tidak diketahui, sehingga ruang tidak dipesanSet width dan height, impor gambar secara statis, atau pakai fill di dalam pembungkus berposisi
Gambar fill tidak tampil atau ukurannya anehElemen induk tidak berposisi relative, fixed, atau absoluteBeri induknya position relative dan tinggi atau rasio aspek yang jelas
Ponsel mengunduh gambar yang terlalu besarsizes tidak ditulis, jadi browser mengira gambar selebar viewportTulis sizes sesuai lebar tampilan sebenarnya per breakpoint
HTML membengkak oleh srcset panjang untuk logo atau ikon kecilfill dengan sizes piksel mencantumkan setiap lebar terkonfigurasiPakai width dan height tetap agar srcset hanya 1x dan 2x
Gambar dari domain lain gagal dengan 400 Bad RequestHost gambar belum didaftarkan di remotePatterns pada konfigurasi Next.jsTambahkan hostname dan path-nya ke images.remotePatterns, lalu restart
Lab hijau, tetapi PageSpeed Insights lapangan masih merahData lab adalah simulasi; data lapangan adalah pengguna nyata selama 28 hari pada persentil ke-75Nilai hasil dari data lapangan, pakai lab untuk debugging, dan tunggu jendela 28 hari

Checklist sebelum deploy

  • Kamu sudah mencatat LCP, INP, dan CLS awal dari PageSpeed Insights.
  • Gambar hero punya priority, dan hanya satu atau dua gambar yang punya.
  • Setiap gambar punya width dan height, atau fill di dalam pembungkus berposisi.
  • Gambar fluid punya sizes; gambar kecil berukuran tetap tidak memakai fill.
  • Font dimuat lewat next/font, sekali di satu modul, dengan subsets atau file lokal.
  • HTML hasil build diperiksa dengan curl, lalu PageSpeed Insights dijalankan ulang.

Belajar lebih dalam

Artikel ini fokus pada langkah praktik. Penjelasan konsepnya, termasuk empat subbagian LCP dan diagram jalur satu gambar dari HTML sampai tampil, ada di pelajaran gratis berikut.

Pelajaran: dasar Core Web Vitals, next/image, dan next/font

Aturan yang bisa kamu bawa: ukur dulu, perbaiki elemen LCP dengan priority, jelaskan ukuran gambar lewat width, height, atau sizes, dan biarkan next/font menghosting font. Satu perubahan, satu pengukuran.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Bagaimana cara meningkatkan LCP di aplikasi Next.js?

Cari elemen terbesar, biasanya gambar hero, lalu tambahkan priority pada komponen next/image-nya agar dipreload dan tidak di-lazy-load. web.dev menyatakan jangan pernah me-lazy-load gambar LCP. Pasang priority hanya pada satu atau dua gambar, karena lebih dari itu justru tidak membantu.

Kenapa layout Next.js saya bergeser saat gambar dimuat?

Browser tidak tahu rasio aspek gambar, sehingga tidak memesan ruang. Set width dan height, impor gambar secara statis, atau pakai fill di dalam pembungkus yang berposisi dan punya rasio aspek tetap. Hal yang sama berlaku untuk font: next/font menghindari layout shift lewat self-hosting dan penyesuaian font cadangan.

Apakah prop sizes di next/image wajib ditulis?

Ya, setiap kali gambar memakai fill atau dibuat responsif lewat CSS. Tanpa sizes, browser mengira gambar selebar viewport dan bisa mengunduh file yang terlalu besar. Dengan sizes, Next.js juga menghasilkan srcset lengkap berisi daftar lebar.

Kenapa skor Lighthouse bagus tetapi data lapangan PageSpeed Insights jelek?

Lighthouse adalah data lab dari lingkungan simulasi, sedangkan data lapangan berasal dari pengguna Chrome nyata selama 28 hari terakhir pada persentil ke-75. Keduanya mengukur kondisi yang berbeda, jadi bisa tidak sama. Pakai data lab untuk debugging dan data lapangan untuk menilai apakah pengguna benar-benar terbantu.

Apakah next/image atau next/font bisa memperbaiki INP?

Tidak secara langsung. Keduanya menyasar pemuatan dan kestabilan visual, yaitu LCP dan CLS. INP mengukur seberapa cepat halaman merespons klik, ketukan, dan penekanan tombol, jadi kamu perlu memeriksa apa yang dikerjakan event handler dan mengukur interaksi nyata.