Mengukur dan mengoptimasi performa web dengan Core Web Vitals: memahami LCP, INP, dan CLS, teknik code-splitting dan lazy loading, optimasi gambar & font, plus workflow audit TokoKita dengan Lighthouse dan web-vitals

Setelah di episode 11 TokoKita punya data yang robust, episode 12 menjawab pertanyaan yang menentukan: seberapa cepat halaman ini terasa bagi pengguna? Performa bukan sekadar angka teknis — Google menggunakannya untuk peringkat pencarian, dan yang lebih penting, performa buruk langsung terasa sebagai pengalaman buruk.
Inti performa modern adalah Core Web Vitals, tiga metrik yang mengukur pengalaman nyata pengguna, bukan sekadar waktu server. Memahami ketiganya — LCP, INP, dan CLS — plus teknik optimasinya, adalah kompetensi wajib frontend di 2026.
| Metrik | Mengukur | Target "Good" |
|---|---|---|
| LCP (Largest Contentful Paint) | Seberapa cepat konten utama terlihat | ≤ 2,5 detik |
| INP (Interaction to Next Paint) | Seberapa cepat respons terhadap interaksi | ≤ 200 ms |
| CLS (Cumulative Layout Shift) | Seberapa banyak layout "melompat" | ≤ 0,1 |
Note
INP adalah pengingat mengapa episode 5 menekankan async dan episode 11 menekankan optimistic update: interaksi yang terasa "lambat" hampir selalu karena main thread diblokir atau UI menunggu server tanpa umpan balik.
Langkah pertama sebelum optimasi adalah mengukur — jangan menebak.
bunx @lhci/cli autorun --collect.url=http://localhost:3000 --collect.numberOfRuns=3Di browser: DevTools → Lighthouse → Generate report. Atau untuk pemantauan real user, gunakan library web-vitals:
import { onLCP, onINP, onCLS } from "web-vitals";
onLCP((m) => console.log("LCP:", m.value));
onINP((m) => console.log("INP:", m.value));
onCLS((m) => console.log("CLS:", m.value));Metrik dari pengguna nyata (field data) lebih jujur daripada lab — itulah yang dilihat Google Search Console.
Bundle JavaScript raksasa adalah biang LCP/INP buruk. Solusinya: kirim hanya yang dibutuhkan layar ini.
import { lazy, Suspense } from "react";
const CheckoutForm = lazy(() => import("./CheckoutForm"));
// CheckoutForm hanya dimuat saat dialog checkout dibuka
<Suspense fallback={<p>Memuat…</p>}>
{isOpen && <CheckoutForm />}
</Suspense>Praktik terkait:
next/dynamic untuk komponen besar yang jarang dipakai (chart, editor).<img loading="lazy"> atau next/image dengan priority untuk hero.Gambar biasanya menyumbang 50-70% ukuran halaman. Prinsipnya:
import Image from "next/image";
<Image
src="/kopi.jpg"
alt="Kemasan kopi arabika 250 gram"
width={640}
height={480}
priority
/>width/height eksplisit mencegah layout shift (CLS), priority mempercepat LCP hero, dan Next.js otomatis mengubah format ke modern (WebP/AVIF) + responsive sizes.
Font: gunakan next/font (self-host, font-display: swap dengan fallback yang dekat) supaya teks tidak melompat saat font termuat:
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });Urutan kerja yang realistis:
web-vitals).bunx next build # lihat ukuran tiap route di outputTip
Optimasi dimulai dari pengukuran: jika LCP kalian sudah 1,8 detik, jangan habiskan waktu mengoptimasi hal yang tidak menyentuh LCP. Kerjakan yang berdampak paling besar pada metrik yang paling buruk.
Inti yang harus dibawa pulang:
web-vitals, baru optimasi.Di episode 13 selanjutnya kita memastikan kualitas: testing frontend — unit test dengan Vitest, component test, dan end-to-end dengan Playwright/Cypress, sehingga TokoKita bisa diubah tanpa takut rusak. Sampai jumpa di episode 13!