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

Foto oleh Unsplash

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.