Halaman web yang memuat lambat dapat meningkatkan tingkat bounce rate pengunjung, yang berarti banyak pengguna meninggalkan situs Anda dengan cepat. Hal ini seringkali disebabkan oleh penggunaan tools dan teknik konvensional yang kurang efisien. Namun, dengan pengetahuan dan strategi yang tepat, masalah ini dapat diatasi dengan mudah.
Tujuan utama adalah menjaga halaman web tetap ringan, meminimalkan permintaan jaringan saat pemuatan awal halaman, dan memprioritaskan penampilan konten secepat mungkin. Untuk mencapai ini, gunakan teknik-teknik berikut dan hindari konvensi seperti framework dan library yang cenderung memperbesar ukuran halaman. Pendekatan yang berbeda ini akan membedakan Anda dari pesaing.
Hindari Framework yang Bloated
Sebagian besar framework web cenderung memperbanyak pekerjaan dengan mendelegasikan banyak tugas ke JavaScript, yang seringkali tidak optimal. Framework mengklaim nilai lebih pada aplikasi single-page, organisasi kode yang lebih baik, dan pembaruan DOM yang lebih efisien—dua yang pertama masih diperdebatkan, dan yang ketiga hanya berlaku dalam beberapa kasus.
Untuk proyek yang lebih kecil dan terfokus, komponen web adalah pilihan yang lebih baik. Mereka lebih ringan dan akan tetap berfungsi lama setelah framework populer seperti React, Angular, dan Vue tidak lagi relevan, karena mereka adalah bagian dari standar W3C. Framework Lit (dari Google) adalah wrapper ringan untuk komponen web yang menjanjikan, karena mengatasi beberapa boilerplate (kode yang berulang dan membosankan).
Oleh karena itu, sebisa mungkin, hindari framework yang berat. Sebagian besar saran berikut mengasumsikan Anda melakukannya. Namun, jika Anda menggunakan framework (seperti React), beberapa hal mungkin tidak berlaku, tetapi tetap fundamental.
Gunakan Dependensi yang Lebih Sedikit dan Lebih Ringan
Saat halaman dimuat pertama kali, permintaan jaringan untuk menarik dependensi adalah penyebab utama kinerja yang buruk. Sebisa mungkin, hindari dependensi, terutama library pembantu seperti Lodash atau Ramda. Sayangnya, ini berarti harus membuat solusi kustom untuk beberapa hal.
Sebagai contoh, jika Anda membutuhkan event bus sederhana—yaitu kelas yang pada dasarnya adalah saluran komunikasi yang dibagi di seluruh kode Anda—solusi populer adalah library bernama RxJS. Namun, alih-alih memasukkannya sebagai dependensi (berukuran 17,7 kB terkompresi), Anda bisa menulis kelas sendiri yang hanya berukuran 200 byte. Ini hanya menghemat 100 ms waktu muat awal, tetapi penghematan kecil ini dapat bertambah.
Metode tradisional menulis kode untuk browser melibatkan bundling. Proses ini mengharuskan Anda menulis kode di Node.js dan menggunakan tool seperti webpack untuk mengubahnya menjadi format yang dapat digunakan browser. Hasilnya adalah satu blok kode besar yang mencakup segalanya, dan seringkali ukurannya bisa lebih dari 100 KB. Teknik-teknik kemudian berkembang untuk mengatasi ini, seperti code-splitting, yang membagi kode menjadi chunk yang kemudian diunduh oleh framework atau browser hanya saat dibutuhkan. Teknik lain adalah tree-shaking, yang menganalisis kode untuk mengekstrak hanya yang diperlukan; ESM melakukan sesuatu yang mirip.
ESM (ECMAScript Modules) adalah sistem modul JavaScript modern dan cara standar baru untuk mengimpor library. Contoh ESM:
<script type=”module”>
const {foo} = “https://example.com/all-the-things.js”
</script>
Contoh di atas akan mengunduh modul yang diinginkan dan modul lain yang bergantung padanya. Beginilah cara kerja ESM, dan ada beberapa kesamaan dengan tree-shaking.
ESM bukanlah solusi ajaib dan memiliki keterbatasan. Meskipun direkomendasikan untuk digunakan, tetap penting untuk menulis solusi minimal Anda sendiri (jika memungkinkan). Melakukan hal ini akan menghindari dependency tree yang besar untuk library pihak ketiga. Jika proses penulisannya kurang dari 30 menit, mengapa tidak?
Salah satu keterbatasan ESM adalah jika sebuah modul bergantung pada banyak modul lain, browser harus mengunduh semuanya—koleksi ini disebut dependency tree. Misalnya, mengimpor fungsi capitalize dari Lodash memerlukan pemuatan banyak berkas tambahan:
<script type=”module”>
import capitalize from ‘https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/capitalize.js’;
alert(capitalize(“hello world!”))
</script>
Menggunakan Lodash untuk mengkapitalisasi huruf pertama sebuah kata dapat menghasilkan 23 permintaan dan memakan waktu 4,4 detik pada koneksi GPRS (20 KB/detik); sedangkan pada koneksi 100 Mb, masih membutuhkan 2,2 detik. Jika kecepatan koneksi 5000 kali lebih cepat hanya menghasilkan pengunduhan 2 kali lebih cepat, ini menunjukkan bahwa bandwidth bukanlah masalahnya.
Pada gambar di artikel asli, bagian yang diberi label 1 menunjukkan bahwa browser mengalirkan permintaan, hanya mengunduh kode yang dibutuhkan (vaguely similar dengan tree-shaking). Browser mengunduh setiap berkas, memeriksa impor (dependensinya), lalu mengunduhnya. Koneksi menggunakan HTTP/2, yang beroperasi secara signifikan lebih cepat daripada HTTP/1 dalam beberapa skenario. Ini karena HTTP/2 mempertahankan koneksi tunggal yang persisten, sementara dalam beberapa kasus, HTTP/1 bergantung pada beberapa koneksi terpisah, masing-masing melibatkan overhead negosiasi. Bayangkan jika browser lama memuat halaman web ini, dan menggunakan HTTP/1 untuk memuat setiap modul.
Untuk mengkapitalisasi huruf pertama sebuah kalimat, hanya dibutuhkan sedikit kode:
function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
Lebih baik mengimplementasikan solusi kustom jika mudah; ini menjaga kode tetap ringan dan terfokus pada tujuan Anda. Selain itu, pahami bahwa impor mahal saat menggunakan ESM, dan Anda harus berpikir hati-hati tentang cara Anda membagi kode; coba minimalkan dependensi antar modul. Juga, bundling masih merupakan cara yang sangat efektif untuk meminimalkan jumlah permintaan yang dibuat oleh browser. Ada banyak hal yang perlu dipertimbangkan, jadi luangkan waktu untuk ini.
Intinya adalah menggunakan dependensi dengan hemat. Framework adalah dependensi besar, dan library adalah beban. Tujuan kita adalah ringan dan cepat, dan latensi jaringan akan membunuh kinerja Anda.
Gunakan defer atau async pada Script Tags
Baik defer maupun async adalah atribut yang dapat Anda gunakan pada script tags untuk mengubah perilakunya. Keduanya memengaruhi kinerja, tetapi bekerja sedikit berbeda satu sama lain, dan ada kasus penggunaan yang jelas untuk masing-masing.
Tidak termasuk defer, async, dan ESM untuk saat ini, perlu diingat bahwa semua script tags mengunduh secara paralel satu sama lain tetapi dieksekusi sebelum parser HTML. Beginilah cara script tags biasanya berperilaku.
Dengan menggunakan web server kustom, Anda dapat secara sengaja mengatur penundaan pengunduhan untuk script 1 dan 2. Perhatikan bahwa kedua script mulai mengunduh pada waktu yang hampir bersamaan, dan script dengan penundaan 2000 ms mulai lebih dulu. Jika pengunduhan tidak terjadi secara paralel, script yang lebih panjang (2000 ms) akan memblokir script lain untuk mengunduh sampai selesai.
Anda juga dapat melihat bahwa HTML di-parse hanya setelah semua script selesai. Ini menunjukkan bahwa script memblokir parser HTML saat mereka diunduh dan dieksekusi.
Defer
Atribut defer pada script tags memberi tahu browser untuk menunda eksekusi script hingga setelah HTML selesai dirender. Ini penting, karena kita tidak ingin memblokir konten halaman awal saat kita menunggu kode JavaScript yang berat untuk diunduh dan dieksekusi. Perlu juga dicatat bahwa script yang ditunda dieksekusi dalam urutan kemunculannya di HTML.
<html> <head> <script src="/js/foo.js" defer></script> </head> <p>I render before the script.</p> </html>
ESM juga ditunda secara default.
Script yang ditunda berjalan setelah HTML dimuat sepenuhnya, sehingga Anda dapat dengan aman mengakses DOM. Jika Anda perlu menjalankan kode setelah semua script yang ditunda selesai, gunakan peristiwa DOMContentLoaded—ini akan aktif setelah HTML di-parse dan semua script yang ditunda telah berjalan:
// /js/foo.js document.addEventListener("DOMContentLoaded", () => { console.log("The DOM is fully loaded!") })
Namun, DOMContentLoaded tidak menunggu gambar, script async, atau iframe.
Script yang ditunda menunggu sampai browser mengunduh dan meng-parse semua stylesheet juga, yang berbeda dari perilaku script tag normal.
Banyak yang harus diingat, tetapi sederhananya: script yang ditunda menunggu hingga renderer HTML dan stylesheet selesai terlebih dahulu. Untuk memastikan 100% bahwa halaman siap, dengarkan peristiwa DOMContentLoaded.
Async
Atribut async memberi tahu browser untuk mengunduh script saat meng-parse HTML. Script dapat berjalan kapan saja. Script dan parser HTML tidak menunggu satu sama lain, tetapi script mungkin mengganggu parser HTML jika diperlukan. Ini seperti perpaduan antara script normal dan ditunda.
<head> <script src="/js/foo.js" async></script> </head>
Ringkasan
Script yang ditunda (dan ESM) diunduh segera dan dieksekusi setelah parser HTML dan stylesheet.
Script async diunduh segera dan dieksekusi kapan saja, mungkin mengganggu parser HTML.
Script normal diunduh segera dan dieksekusi sebelum parser HTML.
Gambar di artikel asli menunjukkan timeline untuk setiap jenis script, menunjukkan kapan mereka mengunduh dan mengeksekusi dalam kaitannya dengan parser HTML. (Sumber: WHATWG (Apple, Google, Mozilla, Microsoft), dilisensikan di bawah CC-BY-4.0).
Gunakan script async untuk script pihak ketiga yang terisolasi seperti iklan—script yang tidak berinteraksi dengan sisa halaman Anda. Gunakan script yang ditunda sebanyak mungkin. Penggunaan script yang ditunda akan sangat penting di bagian selanjutnya, yang membahas CSS kritikal.
Inline CSS Kritikal
Saat halaman web pertama kali dirender, gaya yang terlihat di layar disebut above the fold, dan gaya di bawah layar disebut below the fold. CSS Kritikal berkaitan dengan merender gaya above-the-fold secepat mungkin untuk memberikan kesan kepada pengguna bahwa halaman web dimuat dengan cepat dan untuk menghindari menguji kesabaran mereka. Ini penting karena semakin lama pengguna menunggu halaman dimuat, semakin besar kemungkinan mereka akan pergi.
Untuk merender konten above the fold secepat mungkin, kita perlu membagi gaya kita menjadi dua bundle, satu untuk konten above-the-fold dan yang lainnya untuk di bawahnya. Untuk mencapai ini, cukup dengan menganalisis secara manual gaya mana yang berlaku untuk setiap bagian—untuk desktop dan mobile.
Untuk membagi gaya Anda menjadi dua bundle, contoh paling dasar terlihat seperti ini:
<!DOCTYPE html> <html lang="en"> <head> <script src="below-the-fold.js" defer></script> <style> /* Critical CSS. */ </style> </head> <body> <aside>Above the fold.</aside> <main> <p>Below the fold.</p> </main> <link rel="stylesheet" type="text/css" href="below-the-fold.css"> </body> </html>
Agar lebih mudah dibaca, contoh di atas tidak lengkap; contoh dunia nyata terlihat seperti ini:
Dua jendela berdampingan: di kiri, kode HTML dengan anotasi bernomor 1 hingga 6; di kanan, halaman web yang menampilkan konten, dengan bagian di bawah dan di atas lipatan terlihat.
Tag style di bagian 1 berisi CSS kritikal, yang harus berisi gaya above-the-fold. Dalam contoh kita, tag aside (bagian 6) berisi semua konten yang above the fold. Jika Anda ingin menatanya, modifikasi bagian 1.
Ketika halaman pertama kali dirender, konten below the fold tidak memiliki gaya, sehingga akan berkedip sebentar, menunjukkan konten tanpa gaya—ini disebut flash of unstyled content (FOUC). Untuk mengatasinya, sembunyikan konten dengan opacity: 0 (bagian 1). Ketika JavaScript (bagian 4) dieksekusi, itu akan mengubah opacity menjadi 1. Ada efek transisi yang lembut, sehingga akan memudar dengan indah.
Karena stylesheet di elemen <head> memblokir renderer HTML, kita justru memuat CSS non-kritikal kita di dekat akhir body (bagian 3) setelah semua HTML lainnya telah dirender. Intinya, browser pertama-tama merender HTML dan kemudian mengunduh stylesheet di akhir body, sebelum mengeksekusi script yang ditunda.
Kita menggunakan script yang ditunda karena ia dieksekusi setelah semua HTML selesai dirender dan setelah semua stylesheet diunduh dan diterapkan.
Prosesnya terlihat seperti ini:
- Browser meminta HTML.
- Browser mulai meng-parse dokumen.
- Browser mengunduh JavaScript di latar belakang dan segera melanjutkan (bagian 5).
- Browser meng-parse gaya inline (bagian 1).
- Browser merender tag aside, yang above the fold (bagian 6).
- Browser merender konten utama (bagian 2), yang below the fold dan opacity: 0, berkat CSS kritikal (bagian 1).
- Browser mengunduh CSS non-kritikal (bagian 3).
- Browser menyelesaikan parsing HTML dan gaya, lalu mengaktifkan DOMContentLoaded.
- JavaScript aktif untuk membuat konten below-the-fold terlihat (anotasi 4).
Singkatnya: Browser bekerja hingga CSS di akhir dokumen, merender semua HTML dan membuat konten below-the-fold tidak terlihat. Setelah itu selesai, JavaScript aktif untuk membuat konten below-the-fold terlihat.
Pendeknya: Render above the fold dengan cepat dan sembunyikan yang lainnya sampai siap.
Luangkan waktu untuk mencerna apa yang telah dikatakan di sini, dan bangunlah di atasnya. Disarankan agar Anda meninggalkan rekomendasi umum React, Angular, dan ribuan dependensi. Sebaliknya, Anda harus:
- Saring dependensi Anda, dan jaga agar tetap minimal. Lebih sedikit permintaan berarti pemuatan halaman lebih cepat.
Pahami cara kerja script tags, kapan mereka dimuat, dan bagaimana menghindari memblokir parser HTML. - Pahami kapan stylesheet dimuat—baik di <head> maupun <body>—dan kemudian pahami apakah mereka memblokir renderer HTML.
- Render HTML yang terlihat secepat mungkin; muat semuanya pada waktunya.
- Jika Anda ingin menerapkan apa yang baru saja Anda pelajari, tetapi membutuhkan beberapa sumber daya untuk membantu Anda, kunjungi situs web yang harus diketahui setiap pengembang web pemula seperti Wikipedia (https://id.wikipedia.org/wiki/Pengembangan_web) atau Youtube (https://www.youtube.com/results?search_query=web+development+performance).
- Atau, jika Anda ingin memulai tetapi kurang inspirasi, lihat panduan kami tentang cara membangun aplikasi web penghitung kata sederhana pertama Anda.