Membangun Landing Page Produksi yang Meraih Skor 100 di Core Web Vitals

Foto oleh Unsplash
Lighthouse berjalan di lingkungan lab yang dikontrol pada perangkat simulasi kelas menengah, sedangkan Core Web Vitals diukur pada pengguna nyata dan dilaporkan melalui Chrome User Experience Report (CrUX). Kondisi perangkat keras, jaringan, dan perilaku pengguna di dunia nyata sangat berbeda dari simulasi lab. Artinya, Anda harus mengoptimalkan untuk pengguna sebenarnya, bukan sekadar untuk alat pengujian.
Penyebab paling umum adalah lazy-loading pada gambar hero, yang merupakan perilaku default Next.js Image. Menambahkan prop priority pada gambar hero menghasilkan fetchpriority high dan menghapus intersection observer lazy-loading, sehingga browser dapat mengambil gambar segera saat parsing HTML. Perubahan satu ini biasanya memberikan dampak terbesar pada LCP.
Menetapkan AVIF sebagai preferensi format gambar pertama di konfigurasi Next.js dapat memotong ukuran file gambar 40 hingga 50 persen dibandingkan WebP, dengan browser yang secara otomatis jatuh kembali ke WebP atau JPEG untuk klien yang tidak mendukung. Dikombinasikan dengan minimumCacheTTL yang panjang, gambar yang telah dioptimalkan juga disajikan dari cache pada kunjungan ulang, mengurangi beban origin dan memperbaiki LCP kunjungan berulang.
Skrip yang dimuat dengan strategy beforeInteractive memblokir main thread selama hidrasi, yang langsung menaikkan INP — metrik yang mengukur keterlambatan interaksi terburuk sepanjang sesi halaman. Solusinya adalah mengaudit setiap tag skrip dan menjadikan strategy afterInteractive atau lazyOnload sebagai default, kecuali skrip benar-benar kritis untuk rendering. Selalu uji perbedaan sebelum dan sesudah di Lighthouse sebelum menerima persyaratan vendor untuk memuat skrip lebih awal.
Artikel ini merekomendasikan integrasi Lighthouse CI ke GitHub Actions, dikonfigurasi untuk menggagalkan build jika metrik CWV apa pun turun di bawah ambang batas yang ditentukan. Workflow ini berjalan pada setiap pull request terhadap URL deployment preview. Menggabungkannya dengan komentar diff ukuran bundle berarti setiap reviewer dapat melihat dampak performa dari sebuah perubahan sebelum di-merge, sehingga regresi tidak mungkin terkirim secara tidak sengaja.

Foto oleh Unsplash
Setiap developer pernah meluncurkan landing page yang terasa membanggakan, lalu menjalankan Lighthouse dan melihat skornya ambrol dihujani merah. Saya pernah di posisi itu. Setelah mengirimkan beberapa situs Next.js ke produksi — termasuk portofolio ini — saya mengembangkan proses yang bisa diulang untuk meraih hijau di semua metrik: LCP di bawah 2,5 detik, CLS di bawah 0,1, dan INP di bawah 200 milidetik. Artikel ini adalah proses tersebut, tanpa filter.
Core Web Vitals (CWV) Google adalah metrik lapangan, bukan metrik lab. Perbedaan ini sangat penting. Lighthouse berjalan di lingkungan lab yang dikontrol pada perangkat simulasi kelas menengah. CWV diukur pada pengguna nyata yang mengunjungi situs Anda, dilaporkan kembali melalui Chrome User Experience Report (CrUX). Skor Lighthouse sempurna tidak menjamin data lapangan yang baik — dan sebaliknya.
LCP mengukur kapan elemen konten terbesar yang terlihat selesai dirender. Pada landing page biasa, itu adalah gambar hero atau judul di atas lipatan. Penyebab paling umum LCP buruk adalah memperlakukan gambar hero seperti gambar biasa — menggunakan lazy-loading. Selalu tambahkan priority pada gambar hero Anda. Ini menghasilkan fetchpriority high dan membiarkan browser mulai mengambil gambar segera saat parsing HTML.
CLS adalah metrik yang bisa meledak tanpa perubahan kode — cukup tambahkan banner, popup persetujuan cookie, atau slot iklan yang dimuat setelah paint awal. Aturannya sederhana: jangan pernah menyuntikkan konten di atas konten yang sudah ada. Reservasi ruang untuk setiap elemen dinamis menggunakan width/height eksplisit atau CSS aspect-ratio sebelum konten dimuat.
Browser Request
│
▼
┌─────────────────────────────────────────────────────┐
│ NAVIGATION TIMING │
│ │
│ TTFB (Time to First Byte) │
│ ├── DNS Lookup │
│ ├── TCP Connect │
│ └── Server Response │
│ │
│ FCP (First Contentful Paint) ◄── HTML/CSS parse │
│ LCP (Largest Contentful Paint)◄── Hero image load │
│ CLS (Cumulative Layout Shift) ◄── Layout complete │
│ INP (Interaction to Next Paint)◄── JS hydration │
└─────────────────────────────────────────────────────┘
Target: LCP < 2.5s │ INP < 200ms │ CLS < 0.1Gunakan preload link untuk gambar LCP Anda jika itu adalah background-image yang diatur melalui CSS, bukan tag img — prop priority di Next.js Image hanya berfungsi untuk elemen img. Tambahkan secara manual di document head melalui next/head atau metadata API.
next.config.ts yang dikonfigurasi dengan baik menghilangkan seluruh kategori masalah performa sebelum satu baris kode komponen ditulis. Lever utamanya adalah optimasi gambar, penanganan CSS, dan analisis bundle. Mengaktifkan AVIF sebagai preferensi format gambar pertama dapat memotong ukuran gambar 40 hingga 50 persen dibandingkan WebP.
Font adalah salah satu sumber CLS dan regresi LCP yang paling sering diabaikan. Menggunakan next/font/google daripada @import manual atau tag link memberikan subset otomatis, self-hosting di domain Anda sendiri, dan preloading yang terpadu dalam komponen.
// next.config.ts — production-grade config
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
images: {
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200],
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
},
experimental: {
optimizeCss: true, // inline critical CSS
},
}
export default nextConfig
// Hero component — LCP-critical image
import Image from "next/image"
export function HeroSection() {
return (
<section>
<Image
src="/hero.webp"
alt="Product hero"
width={1200}
height={630}
priority // fetchpriority="high" — never lazy-load LCP
placeholder="blur"
blurDataURL="data:image/webp;base64,..."
/>
</section>
)
}
// app/layout.tsx — preload critical font
import { Inter } from "next/font/google"
const inter = Inter({
subsets: ["latin"],
display: "swap", // prevent FOIT
preload: true,
})
// Prevent CLS: always reserve space for dynamic content
// .ad-slot { min-height: 90px; }
// Never inject content above existing DOM nodesINP menggantikan FID sebagai Core Web Vital pada Maret 2024, dan jauh lebih sulit untuk dioptimalkan. Penyebab utama INP buruk adalah tugas-tugas panjang di main thread — JavaScript yang berjalan lebih dari 50 milidetik tanpa menghasilkan. Server components Next.js membantu dengan memindahkan rendering dari klien sepenuhnya.
Satu skrip pihak ketiga yang dimuat dengan strategy beforeInteractive bisa merusak skor INP Anda sendirian dengan memblokir main thread selama hidrasi. Audit setiap tag skrip di codebase Anda. Gunakan strategy afterInteractive atau lazyOnload sebagai default.
Satu-satunya cara untuk mempertahankan skor Lighthouse sempurna dari waktu ke waktu adalah membuat tidak mungkin melakukan merge PR yang merusaknya. Saya menggunakan Lighthouse CI di GitHub Actions, dikonfigurasi untuk gagalkan build jika metrik CWV apa pun turun di bawah ambang batas.
Sebelum landing page apa pun diluncurkan, saya menjalankan checklist ini: gambar hero memiliki prop priority dan disajikan sebagai WebP atau AVIF; semua font menggunakan next/font; tidak ada tag skrip biasa di layout; dimensi eksplisit pada semua gambar dan embed; Lighthouse CI lulus di PR.