Mengapa performa ada
Performa (performance) adalah ukuran seberapa cepat halaman siap dipakai sejak dibuka. Halaman yang tampil dalam sekitar satu detik terasa langsung. Yang menunggu sepuluh detik membuat pembaca pergi sebelum melihat apa pun, apalagi di ponsel dengan sinyal lemah. Karena itu performa bukan pemanis di akhir pekerjaan, ia bagian dari pengalaman, sejajar dengan aksesibilitas di bab 19.
Alasannya sederhana dan bisa dihitung. Setiap halaman adalah unduhan. Browser mengambil HTML, CSS, JavaScript, dan gambar lewat jaringan, dan jaringan itu lambat serta berbayar kuota. Makin banyak dan makin besar yang diunduh, makin lama pembaca menunggu. Pekerjaan bab ini adalah menimbang tiga hal: apa yang diunduh, kapan diunduh, dan seberapa besar.
Apa yang dibawa browser saat membuka halaman
Rekap cara browser bekerja dari bab 1. Browser mengunduh HTML, membacanya dari atas ke bawah, lalu setiap kali menemukan berkas lain, CSS, JavaScript, atau gambar, ia mengunduhnya juga. Setiap unduhan dimulai dengan request, permintaan ke server, dan permintaan itu butuh waktu walau berkasnya kecil. Urutan unduhan sebuah halaman bisa dilihat di tab Network pada DevTools dari bab 0.
Dari sana muncul pola yang khas di hampir semua situs. Dua jenis berkas paling berat biasanya JavaScript dan gambar. JavaScript yang besar menahan halaman karena kode harus diunduh lalu dijalankan sebelum tombol dan daftar hidup. Gambar yang besar membengkakkan kuota dan membuat tata letak melompat saat gambarnya tiba. Lompatan ini disebut layout shift, pergeseran tata letak yang mengganggu mata dan jari. Tiga teknik bab ini menyasar dua beban itu: bundle mengatur JavaScript, lazy load menundanya, dan perawatan gambar mengecilkan beban terberatnya.
Bundle: banyak berkas jadi satu
Di proyek nyata, kode JavaScript jarang tinggal satu berkas. Ada berkas untuk tombol, untuk form, untuk galeri, untuk fitur yang jarang dibuka. Mengunduhnya satu per satu boros, karena tiap berkas membayar biaya request yang sama. Bundle adalah satu berkas JavaScript berisi banyak berkas kode yang sudah digabung. Alat penggabungnya disebut bundler, dan bundler termasuk build tool, alat yang memproses kode sebelum diterbitkan. Seri ini sengaja tidak memakai build tool supaya semua contoh bisa dicoba langsung di browser, sesuai ADR-0003, jadi di sini bundle cukup dipahami konsepnya.
Penggabungan menghemat, tapi menyimpan tagihan. Satu bundle besar berarti pembaca mengunduh semua kode, bahkan bagian yang tidak pernah ia buka. Halaman yang hanya menampilkan daftar tetap mengunduh kode galeri dan kode form. Jawabannya bukan memilih antara satu berkas raksasa dan banyak berkas kecil, tapi menggabungkan lalu memecah kembali dengan cerdas. Caranya di bagian berikutnya.
Lazy load: memuat saat dibutuhkan
Lazy load berarti menunda pemuatan sesuatu sampai benar-benar dibutuhkan. Lawannya disebut eager, memuat semuanya sejak awal. Untuk kode, tekniknya bernama code splitting, memecah bundle menjadi beberapa bagian yang diunduh terpisah. Bagian yang jarang dipakai, misalnya kode galeri foto, baru diunduh saat pembaca benar-benar membuka galerinya. Pemicu unduhannya dynamic import, perintah import() yang boleh dijalankan kapan saja saat halaman sudah hidup, bukan cuma di awal. Di React, kebiasaan ini dibungkus React.lazy dan komponen Suspense. React.lazy menunda pengunduhan kode komponen, Suspense menampilkan sesuatu, biasanya tulisan Memuat, selama kode itu di jalan.
Untuk gambar, browser sudah menyediakan cara paling murah: atribut loading="lazy" pada img. Tanpa satu baris JavaScript pun, browser menahan unduhan gambar sampai gambar itu mendekati layar. Demo Hidup di bawah membangun ulang perilaku itu secara manual dengan IntersectionObserver, API browser yang memberi tahu saat elemen masuk atau keluar dari layar, supaya urutan pemuatannya terlihat dan bisa diikuti.
Gambar: beban terberat
Di kebanyakan halaman, gambar adalah porsi unduhan terbesar, lebih besar daripada HTML dan JavaScript digabung. Tiga langkah perawatannya. Pertama, ukuran yang sesuai. Gambar 4000 piksel untuk avatar 48 piksel itu membawa galon untuk minum segelas, kecilkan gambar sampai seukuran tempatnya tampil. Kedua, format modern. Format adalah cara menyimpan gambar menjadi berkas, dan WebP serta AVIF menyimpan gambar yang sama bagus dalam berkas jauh lebih kecil daripada JPG atau PNG. Ketiga, srcset, atribut yang memberi beberapa pilihan berkas untuk satu gambar, lalu browser memilih sendiri sesuai lebar layar. Ponsel kecil tidak perlu mengunduh berkas ukuran desktop.
Dua kebiasaan kecil melengkapi. Pasang width dan height pada img supaya browser menyiapkan ruangnya sebelum gambar tiba, dan layout shift-nya hilang. Lalu pasang loading="lazy" untuk semua gambar di bawah lipatan, bagian halaman yang belum terlihat sebelum digulir.
Core Web Vitals: ukuran standar industri
Semua teknik bab ini mengejar satu tujuan yang punya nama resmi. Core Web Vitals adalah tiga angka dari Google yang dipakai industri menakar kecepatan halaman, dan ketiganya dipilih karena langsung dirasakan pembaca. LCP, Largest Contentful Paint, seberapa cepat elemen terbesar pada layar, biasanya gambar utama, selesai tampil. INP, Interaction to Next Paint, seberapa cepat halaman menanggapi klik atau sentuhan pembaca. CLS, Cumulative Layout Shift, seberapa besar tata letak melompat-lompat selama halaman dimuat.
Tiap metrik punya ambang yang disepakati. LCP dianggap baik bila 2,5 detik atau kurang. INP baik bila 200 milidetik atau kurang. CLS baik bila 0,1 atau kurang. Bila ketiganya lolos, halaman dipandang memberi pengalaman baik. Satu catatan sejarah: INP resmi menggantikan pendahulunya, FID, First Input Delay, sejak Maret 2024, karena mengukur semua interaksi, bukan hanya yang pertama.
Ketiganya mengikat kembali ke materi bab ini. LCP diurus teknik gambar: gambar utama yang pas ukurannya, berformat modern, dan lewat srcset membuat elemen terbesar cepat tiba. CLS diurus width dan height pada img serta loading="lazy": tata letak tidak melompat saat gambar tiba. INP diurus bundle dan code splitting: kode yang lebih kecil membuat halaman menanggapi klik lebih cepat, karena browser tidak sibuk menjalankan bundle raksasa.
Analogi: tas piknik
Satu gambar untuk semuanya. Kamu berangkat piknik. Bundle adalah mengepak semua barang ke satu tas besar, satu perjalanan dari dapur ke mobil, bukan bolak-balik untuk tiap sendok. Lazy load adalah menyisakan payung dan jaket tebal di bagasi mobil, diambil hanya kalau hujan benar-benar turun, membawanya jalan kaki hanya menambah beban. Dan gambar adalah galon air, beban terberat di dalam tas. Membawa galon 19 liter untuk empat orang minum segelas adalah halaman yang memuat gambar 4000 piksel untuk avatar kecil. Perkecil galonnya, tas langsung ringan.
Pasangan contoh
Contohnya galeri foto yang ditunda. Halaman baru memuat tiga foto setelah pembaca menekan tombol Muat galeri. Vanilanya satu pendengar klik. Di dalamnya, createElement membuat elemen img satu per satu, atribut loading diisi lazy, lalu appendChild memasangnya ke galeri. React-nya satu state dimuat yang awalnya false. Tombol mengubahnya jadi true, dan daftar img dirender hanya saat state itu sudah true.
Perilaku kedua sisi identik, yang berbeda cuma cara menulisnya, DOM dibangun dengan tangan atau tampilan lahir dari state. Kode React di kolom kanan memakai JSX yang sudah dipelajari sejak bab 15.
Vanilla JavaScript
const tombol = document.querySelector("#muat");
const galeri = document.querySelector("#galeri");
tombol.addEventListener("click", function () {
for (let i = 1; i <= 3; i++) {
const img = document.createElement("img");
img.loading = "lazy";
img.src = "foto-" + i + ".jpg";
img.alt = "Foto " + i;
galeri.appendChild(img);
}
tombol.disabled = true;
});
React
function Galeri() {
const [dimuat, setDimuat] = React.useState(false);
if (!dimuat) {
return (
<button onClick={() => setDimuat(true)}>
Muat galeri
</button>
);
}
return (
<div>
{[1, 2, 3].map((i) => (
<img key={i} loading="lazy"
src={"foto-" + i + ".jpg"}
alt={"Foto " + i} />
))}
</div>
);
}
Perbedaan inti bab ini ada di sini. Kedua versi menunda pekerjaan yang sama sampai pembaca memintanya. Vanilanya menunda lewat aksi, elemen img baru dibuat saat tombol ditekan. React-nya menunda lewat data, tampilan yang dirender mengikuti state. Kalau daftarnya nanti berubah jadi sepuluh foto, versi vanilla menambah baris, versi React cukup mengubah datanya. Dan di kedua versi, atribut loading="lazy" tetap yang mengurus penundaan terakhirnya ke browser.
Demo Hidup
Demo menampilkan kotak yang bisa digulir berisi delapan gambar. Saat halaman dibuka, tidak ada satu pun gambar yang diunduh, yang ada hanya kotak kosong bertinggi tetap. Gulir kotaknya, dan begitu sebuah kotak mendekati area layar, log di bawahnya mencatat bahwa src gambar itu baru dipasang saat itu. Gambarnya berupa data URI, gambar yang disandikan langsung sebagai teks di dalam halaman, supaya demo jalan tanpa jaringan. Mekanismenya sama persis dengan foto sungguhan, src hanya terpasang saat gambar dibutuhkan.
Gulir kotak gambar di bawah perlahan dan perhatikan lognya. Tekan Ulangi demo untuk kembali ke awal.
Log pemuatan
Begini cara baca demonya. Fungsi mulai membuat delapan kotak, masing-masing berisi elemen img tanpa src, dengan width dan height yang sudah ditentukan supaya ruangnya siap dari awal. IntersectionObserver mengawasi kotak-kotak itu, dan hanya saat sebuah kotak mendekati area layar, baru atribut src dipasang dan log mencatatnya. Observer yang lama diputus lewat disconnect sebelum demo diulang, supaya tidak ada pengamat ganda. Di halaman nyata, pekerjaan ini sering cukup dengan atribut loading="lazy", tapi versi manualnya membuat urutan pemuatannya kelihatan, bukan cuma terjadi.
Ringkasan bab ini. Halaman adalah unduhan, dan unduhan terbesarnya biasanya JavaScript dan gambar. Bundle menggabungkan kode supaya jumlah request turun, code splitting dan lazy load menunda bagian yang belum dibutuhkan, dan gambar dirawat dengan ukuran pas, format modern, srcset, serta loading="lazy". Bab berikutnya menjaga halaman tetap benar dengan cara mengujinya: testing front end.