Cari tahu apa itu pemindai pra-muat browser, bagaimana cara kerjanya dalam membantu performa, dan cara menghindarinya.
Salah satu aspek yang sering terlewat dalam mengoptimalkan kecepatan halaman adalah mengetahui sedikit tentang internal browser. Browser melakukan pengoptimalan tertentu untuk meningkatkan performa dengan cara yang tidak dapat kita lakukan sebagai developer—tetapi hanya selama pengoptimalan tersebut tidak terhalang secara tidak sengaja.
Salah satu pengoptimalan browser internal yang perlu dipahami adalah pemindai pra-muat browser. Postingan ini akan membahas cara kerja pemindai pramuat—dan yang lebih penting, cara menghindari gangguan terhadapnya.
Apa itu pemindai pra-muat?
Setiap browser memiliki parser HTML utama yang mengubah markup mentah menjadi token dan memprosesnya menjadi model objek. Semua ini akan terus berlanjut hingga parser berhenti saat menemukan resource pemblokiran, seperti stylesheet yang dimuat dengan elemen <link>, atau skrip yang dimuat dengan elemen <script> tanpa atribut async atau defer.
<link> untuk file CSS eksternal, yang mencegah browser mengurai sisa dokumen—atau bahkan merender dokumen—hingga CSS didownload dan diurai.
Dalam kasus file CSS, rendering diblokir untuk mencegah flash konten tidak bergaya (FOUC), yaitu saat versi halaman yang tidak bergaya dapat dilihat sebentar sebelum gaya diterapkan padanya.
Browser juga memblokir penguraian dan rendering halaman saat menemukan elemen <script> tanpa atribut defer atau async.
Alasannya adalah browser tidak dapat mengetahui secara pasti apakah skrip tertentu akan mengubah DOM saat parser HTML utama masih melakukan tugasnya. Itulah sebabnya memuat JavaScript di akhir dokumen telah menjadi praktik umum agar efek pemblokiran parsing dan rendering menjadi kecil.
Ini adalah alasan yang baik mengapa browser harus memblokir penguraian dan rendering. Namun, memblokir salah satu langkah penting ini tidak diinginkan, karena dapat menunda penemuan resource penting lainnya. Untungnya, browser melakukan yang terbaik untuk mengurangi masalah ini dengan menggunakan parser HTML sekunder yang disebut pemindai pramuat.
<body>, tetapi pemindai pramuat dapat melihat ke depan dalam markup mentah untuk menemukan resource gambar tersebut dan mulai memuatnya sebelum parser HTML utama tidak diblokir.
Peran pemindai pramuat bersifat spekulatif, yang berarti pemindai ini memeriksa markup mentah untuk menemukan resource yang dapat diambil secara oportunistik sebelum parser HTML utama menemukannya.
Cara mengetahui kapan pemindai pra-muat berfungsi
Pemindai pramuat ada karena pemblokiran rendering dan parsing. Jika kedua masalah performa ini tidak pernah ada, pemindai pra-muat tidak akan terlalu berguna. Kunci untuk mengetahui apakah halaman web mendapatkan manfaat dari pemindai pramuat bergantung pada fenomena pemblokiran ini. Untuk melakukannya, Anda dapat memperkenalkan penundaan buatan untuk permintaan guna mengetahui tempat pemindai pramuat berfungsi.
Ambil halaman ini yang berisi teks dan gambar dasar dengan stylesheet sebagai contoh. Karena file CSS memblokir rendering dan parsing, Anda akan memperkenalkan penundaan buatan selama dua detik untuk stylesheet melalui layanan proxy. Penundaan ini memudahkan untuk melihat di waterfall jaringan tempat pemindai pra-muat bekerja.
Seperti yang dapat Anda lihat dalam waterfall, pemindai pramuat menemukan elemen <img> bahkan saat rendering dan penguraian dokumen diblokir. Tanpa pengoptimalan ini, browser tidak dapat mengambil hal-hal secara oportunistik selama periode pemblokiran, dan lebih banyak permintaan resource akan berurutan, bukan serentak.
Setelah contoh sederhana tersebut, mari kita lihat beberapa pola dunia nyata yang dapat mengalahkan pemindai pramuat—dan apa yang dapat dilakukan untuk memperbaikinya.
Skrip async yang disisipkan
Misalkan Anda memiliki HTML di <head> yang menyertakan beberapa JavaScript inline seperti ini:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Skrip yang disisipkan bersifat async secara default, jadi saat skrip ini disisipkan, skrip akan berperilaku seolah-olah atribut async diterapkan padanya. Artinya, kode akan berjalan sesegera mungkin dan tidak memblokir rendering. Terdengar optimal, bukan? Namun, jika Anda menganggap bahwa <script> inline ini muncul setelah elemen <link> yang memuat file CSS eksternal, Anda akan mendapatkan hasil yang kurang optimal:
async yang disisipkan. Pemindai pramuat tidak dapat menemukan skrip selama fase pemblokiran rendering, karena skrip disuntikkan di klien.
Mari kita uraikan apa yang terjadi di sini:
- Pada detik ke-0, dokumen utama diminta.
- Pada 1,4 detik, byte pertama permintaan navigasi tiba.
- Pada detik ke-2, CSS dan gambar diminta.
- Karena parser diblokir saat memuat stylesheet dan JavaScript inline yang menyuntikkan skrip
asyncmuncul setelah stylesheet tersebut pada 2,6 detik, fungsi yang disediakan skrip tersebut tidak tersedia secepat yang seharusnya.
Hal ini kurang optimal karena permintaan skrip hanya terjadi setelah stylesheet selesai didownload. Hal ini menunda skrip agar tidak berjalan sesegera mungkin. Sebaliknya, karena elemen <img> dapat ditemukan dalam markup yang disediakan server, elemen tersebut ditemukan oleh pemindai pramuat.
Jadi, apa yang terjadi jika Anda menggunakan tag <script> reguler dengan atribut async, bukan menyuntikkan skrip ke DOM?
<script src="/yall.min.js" async></script>
Berikut hasilnya:
async <script>. Pemindai pramuat menemukan skrip selama fase pemblokiran render, dan memuatnya secara bersamaan dengan CSS.
Mungkin ada godaan untuk menyarankan bahwa masalah ini dapat diatasi dengan menggunakan rel=preload. Cara ini pasti akan berhasil, tetapi mungkin menimbulkan beberapa efek samping. Lagipula, mengapa menggunakan rel=preload untuk memperbaiki masalah yang dapat dihindari dengan tidak menyuntikkan elemen <script> ke dalam DOM?
async yang disisipkan, tetapi skrip async dimuat sebelumnya untuk memastikan skrip ditemukan lebih cepat.
Memuat ulang "memperbaiki" masalah di sini, tetapi menimbulkan masalah baru: skrip async dalam dua demo pertama—meskipun dimuat di <head>—dimuat dengan prioritas "Rendah", sedangkan stylesheet dimuat dengan prioritas "Tertinggi". Pada demo terakhir saat skrip async dimuat sebelumnya, stylesheet masih dimuat dengan prioritas "Tertinggi", tetapi prioritas skrip telah dipromosikan menjadi "Tinggi".
Saat prioritas resource ditingkatkan, browser akan mengalokasikan lebih banyak bandwidth untuk resource tersebut. Artinya, meskipun stylesheet memiliki prioritas tertinggi, prioritas skrip yang ditingkatkan dapat menyebabkan perebutan bandwidth. Hal ini dapat menjadi faktor pada koneksi yang lambat, atau dalam kasus ketika resource cukup besar.
Jawabannya di sini sangat jelas: jika skrip diperlukan selama startup, jangan batalkan pemindai pra-muat dengan menyuntikkannya ke DOM. Lakukan eksperimen sesuai kebutuhan dengan penempatan elemen <script>, serta dengan atribut seperti defer dan async.
Pemuatan lambat dengan JavaScript
Pemuatan lambat adalah metode yang sangat baik untuk menghemat data, yang sering diterapkan pada gambar. Namun, terkadang pemuatan lambat diterapkan secara tidak benar pada gambar yang berada "di paruh atas halaman".
Hal ini menimbulkan potensi masalah dengan penemuan resource yang terkait dengan pemindai pra-muat, dan dapat menunda secara tidak perlu berapa lama waktu yang dibutuhkan untuk menemukan referensi ke gambar, mendownloadnya, mendekodenya, dan menampilkannya. Misalnya, kita ambil markup gambar ini:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Penggunaan awalan data- adalah pola umum di pemuat lambat yang didukung JavaScript. Saat gambar di-scroll ke area pandang, pemuat lambat akan menghapus awalan data-, yang berarti bahwa dalam contoh sebelumnya, data-src menjadi src. Update ini akan meminta browser untuk mengambil resource.
Pola ini tidak bermasalah hingga diterapkan ke gambar yang berada di area tampilan selama startup. Karena pemindai pra-muat tidak membaca atribut data-src dengan cara yang sama seperti atribut src (atau srcset), referensi gambar tidak ditemukan lebih awal. Lebih buruk lagi, gambar tertunda pemuatannya hingga setelah JavaScript pemuat lambat didownload, dikompilasi, dan dieksekusi.
Bergantung pada ukuran gambar—yang mungkin bergantung pada ukuran area pandang—gambar tersebut dapat menjadi elemen kandidat untuk Largest Contentful Paint (LCP). Jika pemindai pramuat tidak dapat mengambil resource gambar secara spekulatif sebelumnya—mungkin selama titik saat stylesheet halaman memblokir rendering—LCP akan terpengaruh.
Solusinya adalah mengubah markup gambar:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Ini adalah pola optimal untuk gambar yang ada di area tampilan selama startup, karena pemindai pramuat akan menemukan dan mengambil resource gambar lebih cepat.
Hasil dalam contoh yang disederhanakan ini adalah peningkatan LCP sebesar 100 milidetik pada koneksi lambat. Mungkin tidak terlihat seperti peningkatan besar, tetapi memang demikian jika Anda mempertimbangkan bahwa solusinya adalah perbaikan markup cepat, dan sebagian besar halaman web lebih kompleks daripada kumpulan contoh ini. Artinya, kandidat LCP mungkin harus bersaing untuk mendapatkan bandwidth dengan banyak resource lain, sehingga pengoptimalan seperti ini menjadi semakin penting.
Gambar latar CSS
Ingat bahwa pemindai pramuat browser memindai markup. Fitur ini tidak memindai jenis resource lain, seperti CSS yang mungkin melibatkan pengambilan gambar yang dirujuk oleh properti background-image.
Seperti HTML, browser memproses CSS ke dalam model objeknya sendiri, yang dikenal sebagai CSSOM. Jika resource eksternal ditemukan saat CSSOM dibuat, resource tersebut akan diminta pada saat penemuan, dan bukan oleh pemindai pramuat.
Misalnya, kandidat LCP halaman Anda adalah elemen dengan properti CSS background-image. Berikut adalah yang terjadi saat resource dimuat:
background-image (baris 3). Gambar yang dimintanya tidak mulai diambil hingga parser CSS menemukannya.
Dalam hal ini, pemindai pra-muat tidak dikalahkan, melainkan tidak terlibat. Namun, jika kandidat LCP di halaman berasal dari properti CSS background-image, Anda harus melakukan pramuat gambar tersebut:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
Petunjuk rel=preload tersebut kecil, tetapi membantu browser menemukan gambar lebih cepat daripada yang seharusnya:
background-image (baris 3). Petunjuk rel=preload membantu browser menemukan gambar sekitar 250 milidetik lebih cepat daripada tanpa petunjuk.
Dengan petunjuk rel=preload, kandidat LCP ditemukan lebih cepat, sehingga mengurangi waktu LCP. Meskipun petunjuk tersebut membantu memperbaiki masalah ini, opsi yang lebih baik mungkin adalah menilai apakah kandidat LCP gambar Anda harus dimuat dari CSS atau tidak. Dengan tag <img>, Anda akan memiliki kontrol yang lebih besar untuk memuat gambar yang sesuai untuk area tampilan sambil memungkinkan pemindai pra-muat menemukannya.
Menyisipkan terlalu banyak resource
Penyematan adalah praktik yang menempatkan resource di dalam HTML. Anda dapat menyisipkan stylesheet dalam elemen <style>, skrip dalam elemen <script>, dan hampir semua resource lainnya menggunakan encoding base64.
Menyisipkan resource dapat lebih cepat daripada mendownloadnya karena permintaan terpisah tidak dikeluarkan untuk resource tersebut. Tersedia langsung di dokumen, dan dimuat secara instan. Namun, ada kekurangan yang signifikan:
- Jika Anda tidak menyimpan HTML dalam cache—dan Anda tidak dapat melakukannya jika respons HTML bersifat dinamis—resource inline tidak pernah disimpan dalam cache. Hal ini memengaruhi performa karena resource inline tidak dapat digunakan kembali.
- Meskipun Anda dapat meng-cache HTML, resource inline tidak dibagikan antar-dokumen. Hal ini mengurangi efisiensi caching dibandingkan dengan file eksternal yang dapat di-cache dan digunakan kembali di seluruh origin.
- Jika Anda menyisipkan terlalu banyak, Anda menunda pemindai pra-muat untuk menemukan resource di bagian selanjutnya dalam dokumen, karena mendownload konten tambahan yang disisipkan tersebut membutuhkan waktu lebih lama.
Lihat halaman ini sebagai contoh. Dalam kondisi tertentu, kandidat LCP adalah gambar di bagian atas halaman, dan CSS berada dalam file terpisah yang dimuat oleh elemen <link>. Halaman ini juga menggunakan empat font web yang diminta sebagai file terpisah dari resource CSS.
<img>, tetapi ditemukan oleh pemindai pramuat karena CSS dan font yang diperlukan untuk pemuatan halaman ada di resource terpisah, yang tidak menunda pemindai pramuat dalam melakukan tugasnya.
Lalu, apa yang terjadi jika CSS dan semua font disisipkan sebagai resource base64?
<img>, tetapi penyisipan CSS dan empat aset font-nya dalam `` menunda pemindai pramuat untuk menemukan gambar hingga aset tersebut didownload sepenuhnya.
Dampak penyisipan inline menghasilkan konsekuensi negatif untuk LCP dalam contoh ini—dan untuk performa secara umum. Versi halaman yang tidak menyisipkan apa pun secara inline akan merender gambar LCP dalam waktu sekitar 3,5 detik. Halaman yang menyisipkan semuanya tidak melukis gambar LCP hingga lebih dari 7 detik.
Ada lebih banyak hal yang terjadi di sini daripada sekadar pemindai pra-muat. Menyisipkan font bukanlah strategi yang baik karena base64 adalah format yang tidak efisien untuk resource biner. Faktor lain yang berperan adalah resource font eksternal tidak didownload kecuali jika dianggap perlu oleh CSSOM. Saat font tersebut disisipkan sebagai base64, font akan didownload terlepas dari apakah font tersebut diperlukan untuk halaman saat ini atau tidak.
Dapatkah pramuat meningkatkan kualitas di sini? Oke. Anda dapat memuat gambar LCP terlebih dahulu dan mengurangi waktu LCP, tetapi memuat HTML yang berpotensi tidak dapat di-cache dengan resource inline memiliki konsekuensi performa negatif lainnya. First Contentful Paint (FCP) juga terpengaruh oleh pola ini. Pada versi halaman yang tidak memiliki apa pun yang di-inline, FCP adalah sekitar 2,7 detik. Pada versi yang semuanya di-inline, FCP sekitar 5,8 detik.
Berhati-hatilah saat menyisipkan sesuatu ke dalam HTML, terutama resource yang dienkode base64. Secara umum, hal ini tidak direkomendasikan, kecuali untuk resource yang sangat kecil. Lakukan inline sesedikit mungkin, karena melakukan inline terlalu banyak sama saja bermain api.
Merender markup dengan JavaScript sisi klien
Tidak diragukan lagi: JavaScript pasti memengaruhi kecepatan halaman. Developer tidak hanya bergantung pada WebView untuk menyediakan interaktivitas, tetapi juga cenderung mengandalkannya untuk mengirimkan konten itu sendiri. Hal ini menghasilkan pengalaman developer yang lebih baik dalam beberapa hal; tetapi manfaat bagi developer tidak selalu berarti manfaat bagi pengguna.
Salah satu pola yang dapat mengalahkan pemindai pra-muat adalah merender markup dengan JavaScript sisi klien:
Jika payload markup dimuat dan dirender sepenuhnya oleh JavaScript di browser, semua resource dalam markup tersebut akan efektif tidak terlihat oleh pemindai pra-muat. Hal ini menunda penemuan resource penting, yang tentu saja memengaruhi LCP. Dalam contoh ini, permintaan untuk gambar LCP tertunda secara signifikan jika dibandingkan dengan pengalaman yang dirender server yang setara yang tidak memerlukan JavaScript untuk muncul.
Hal ini sedikit menyimpang dari fokus artikel ini, tetapi efek rendering markup pada klien jauh melampaui mengalahkan pemindai pramuat. Salah satunya, memperkenalkan JavaScript untuk mendukung pengalaman yang tidak memerlukannya akan menambah waktu pemrosesan yang tidak perlu dan dapat memengaruhi Interaction to Next Paint (INP). Merender markup dalam jumlah yang sangat besar di klien lebih cenderung menghasilkan tugas panjang dibandingkan dengan markup dalam jumlah yang sama yang dikirim oleh server. Alasannya—selain pemrosesan tambahan yang melibatkan JavaScript—adalah bahwa browser mengalirkan markup dari server dan membagi rendering sedemikian rupa sehingga cenderung membatasi tugas yang panjang. Di sisi lain, markup yang dirender klien ditangani sebagai satu tugas monolitik, yang dapat memengaruhi INP halaman.
Solusi untuk skenario ini bergantung pada jawaban atas pertanyaan ini: Apakah ada alasan mengapa markup halaman Anda tidak dapat disediakan oleh server, bukan dirender di klien? Jika jawabannya adalah "tidak", rendering sisi server (SSR) atau markup yang dibuat secara statis harus dipertimbangkan jika memungkinkan, karena akan membantu pemindai pramuat menemukan dan mengambil resource penting secara oportunistik sebelumnya.
Jika halaman Anda memerlukan JavaScript untuk melampirkan fungsi ke beberapa bagian markup halaman, Anda tetap dapat melakukannya dengan SSR, baik dengan JavaScript vanilla, atau hidrasi untuk mendapatkan yang terbaik dari keduanya.
Membantu pemindai pra-muat membantu Anda
Pemindai pramuat adalah pengoptimalan browser yang sangat efektif dan membantu halaman dimuat lebih cepat selama startup. Dengan menghindari pola yang menghambat kemampuannya untuk menemukan resource penting sebelumnya, Anda tidak hanya menyederhanakan pengembangan untuk diri sendiri, tetapi juga menciptakan pengalaman pengguna yang lebih baik yang akan memberikan hasil yang lebih baik dalam banyak metrik, termasuk beberapa data web inti.
Sebagai ringkasan, berikut hal-hal yang perlu Anda ketahui dari postingan ini:
- Pemindai pramuat browser adalah parser HTML sekunder yang memindai sebelum parser utama jika diblokir untuk menemukan resource yang dapat diambil lebih cepat secara oportunistik.
- Aset yang tidak ada dalam markup yang disediakan oleh server pada permintaan navigasi awal tidak dapat ditemukan oleh pemindai pra-muat. Cara agar pemindai pra-muat tidak berfungsi dapat mencakup (tetapi tidak terbatas pada):
- Menyuntikkan resource ke DOM dengan JavaScript, baik itu skrip, gambar, stylesheet, atau apa pun yang lebih baik berada di payload markup awal dari server.
- Pemuatan lambat gambar atau iframe di atas area yang terlihat menggunakan solusi JavaScript.
- Merender markup di klien yang mungkin berisi referensi ke subresource dokumen menggunakan JavaScript.
- Pemindai pramuat hanya memindai HTML. Audit ini tidak memeriksa konten resource lain—terutama CSS—yang mungkin menyertakan referensi ke aset penting, termasuk kandidat LCP.
Jika karena alasan apa pun, Anda tidak dapat menghindari pola yang berdampak negatif pada kemampuan pemindai pra-muat untuk mempercepat performa pemuatan, pertimbangkan petunjuk resource rel=preload. Jika Anda menggunakan rel=preload, uji di alat lab untuk memastikan bahwa efek yang dihasilkan sesuai dengan yang Anda inginkan. Terakhir, jangan memuat terlalu banyak resource, karena jika Anda memprioritaskan semuanya, tidak ada yang akan diprioritaskan.
Resource
- Injeksi skrip "Skrip asinkron" dianggap berbahaya
- Cara Kerja Pemuat Awal Browser dalam Mempercepat Pemuatan Halaman
- Memuat aset penting terlebih dahulu untuk meningkatkan kecepatan pemuatan
- Buat koneksi jaringan lebih awal untuk meningkatkan page speed yang dirasakan
- Mengoptimalkan Largest Contentful Paint (LCP)
Gambar banner besar dari Unsplash, oleh Mohammad Rahmani .