Article 7 min read

How To Eliminate Render-Blocking Javascript

render-blocking JavaScript optimization - Focused shot of a laptop displaying code, suitable for tech and coding themes.

Mid-2023. Saya ingat betul, PageSpeed Insights score situs pribadi saya stuck di angka 50-an. LCP jelek, First Contentful Paint juga sama. Parah. Saya sudah coba semua cara optimasi gambar, kompresi server, bahkan ganti CDN. Tidak ada yang berubah signifikan. Frustrasi itu nyata, kok. Saya yakin masalahnya di server atau mungkin code base saya yang jelek. Ternyata, saya salah besar.

Biang keroknya? Sebaris kecil JavaScript untuk efek smooth scroll. Iya, smooth scroll. Yang katanya bikin user experience lebih enak. Tapi script itu saya taruh di <head>, tanpa atribut async atau defer. Setiap kali browser mau gambar sesuatu, dia harus nungguin script ini selesai dieksekusi dulu. Rasanya seperti menunggu antrean panjang di minimarket hanya untuk beli satu permen. Konyol, kan?

That One Time My Site Just Froze (And I Blamed the Hosting)

Itu kejadian nyata. Saya habiskan berhari-hari debugging, sampai menyalahkan provider hosting. Saya bahkan sempat berpikir untuk re-platforming seluruh situs. Padahal, masalahnya sederhana. JavaScript yang render-blocking. Ini bukan sekadar bikin lambat, tapi bikin browser enggak bisa melanjutkan proses rendering halaman. Semua berhenti sampai script itu beres.

Bagi yang belum tahu, render-blocking JavaScript itu script yang harus diunduh dan dieksekusi sebelum browser bisa membangun DOM (Document Object Model) dan merender konten visual. Artinya, teks, gambar, layout — semua tertahan. Ini yang bikin LCP (Largest Contentful Paint) jadi merah menyala di laporan Core Web Vitals kamu.

Dulu, saya cuma tahu ‘website harus cepat’. Sekarang, saya tahu ‘apa yang bikin website cepat’. Dan seringkali, itu bukan hal besar yang kamu duga. Justru detail kecil yang sering terlewat.

Why async and defer Aren’t Always a Magic Bullet

Setelah insiden smooth scroll itu, saya langsung belajar soal async dan defer. Banyak tutorial bilang, ‘pakai saja ini, beres’. Oh, kalau semudah itu, semua orang sudah pakai dari dulu, dong.

<script async> akan mengunduh script secara paralel dengan parsing HTML, dan mengeksekusinya begitu selesai diunduh. Artinya, parsing HTML bisa berhenti sebentar untuk eksekusi script. Cocok untuk script yang independen, seperti Google Analytics. Tapi kalau script itu butuh akses ke DOM atau punya ketergantungan dengan script lain, bisa jadi masalah.

<script defer> juga mengunduh script secara paralel, tapi eksekusinya baru dilakukan setelah parsing HTML selesai, dan sebelum event DOMContentLoaded. Ini lebih aman untuk script yang berinteraksi dengan DOM, karena DOM sudah siap. Tapi kalau kamu punya script yang *harus* jalan duluan untuk tampilan awal, defer bisa bikin FOUC (Flash Of Unstyled Content) atau bahkan error.

Jadi, mana yang harus saya pakai?

Jujur, enggak ada jawaban tunggal. Kalau script itu tidak penting untuk tampilan awal dan tidak bergantung pada script lain (atau script lain bergantung padanya), pakai async. Contoh: tracker, ads. Kalau script itu berinteraksi dengan DOM dan harus jalan setelah DOM siap, tapi tidak kritikal untuk tampilan awal, pakai defer. Contoh: interaktivitas UI, form validation.

Saya pernah salah pakai async untuk script yang memodifikasi layout awal. Hasilnya? Halaman berkedip-kedip, layout berubah drastis setelah beberapa detik. Pengunjung pasti langsung kabur. Itu pengalaman yang bikin saya sadar, setiap atribut itu punya konsekuensinya sendiri.

The “Critical Path” Juggling Act Nobody Talks About Enough

Selain JavaScript, CSS juga bisa jadi render-blocking. Kalau kamu loading semua CSS di awal, browser harus menunggu semua CSS diunduh dan diproses sebelum bisa merender halaman. Ini yang disebut Critical Rendering Path.

Solusinya? Ekstrak Critical CSS. Ini adalah CSS minimal yang dibutuhkan untuk merender bagian ‘above the fold’ (bagian yang terlihat tanpa scroll). CSS ini di-inline langsung di <head>, sementara CSS sisanya di-load secara asynchronous.

Dengar-dengar gampang. Praktiknya? Susah sekali. Tools otomatis seringkali enggak sempurna. Kamu harus identifikasi sendiri elemen-elemen kritikal, lalu extract CSS-nya. Lalu, bagaimana kalau layout berubah? Critical CSS harus di-update lagi. Ini pekerjaan yang merepotkan dan sering diabaikan. Saya pernah coba pakai plugin otomatis di WordPress, hasilnya malah ada bagian yang styling-nya hilang di mobile. Akhirnya, saya balik lagi ke manual atau pakai solusi hybrid.

Ada banyak cara untuk mengidentifikasi Critical CSS, salah satunya menggunakan Lighthouse atau tools seperti Critical CSS Generator. Tapi ingat, ini bukan ‘set-it-and-forget-it’. Ini adalah proses yang perlu pemeliharaan.

Untuk memahami lebih dalam bagaimana browser memproses semua ini, kamu bisa read also: Understanding The Rendering Pipeline For Better Performance. Itu penting untuk tahu kenapa optimasi ini krusial.

When Standard Advice Falls Short: Advanced Tactics I’ve Tried

Kadang, async dan defer saja tidak cukup. Apalagi kalau situs kamu kompleks, penuh fitur, atau pakai banyak library. Di sinilah kamu perlu mulai berpikir di luar kotak.

Salah satu yang saya coba adalah Resource Hints: <link rel="preload"> dan <link rel="preconnect">. preload itu seperti memberi tahu browser, ‘hei, resource ini pasti akan kamu butuhkan sebentar lagi, unduh duluan ya’. Ini efektif untuk font kustom atau script kritikal yang kamu defer tapi ingin diunduh lebih awal. Tapi jangan asal preload semua, nanti malah jadi render-blocking lagi.

preconnect itu untuk memberitahu browser agar membuat koneksi awal ke domain lain yang akan kamu gunakan. Misalnya, untuk CDN atau Google Fonts. Ini mengurangi latency saat koneksi sebenarnya dibuat. Saya pakai ini untuk domain Google Fonts dan CDN gambar saya, dan lumayan membantu mengurangi waktu tunggu.

Dulu ada juga HTTP/2 Server Push, ide bagus tapi implementasinya rumit dan support-nya makin berkurang. Jadi, saya tidak terlalu merekomendasikan ini lagi. Fokus saja ke apa yang browser bisa lakukan dengan lebih efisien.

Untuk JavaScript yang sangat besar, pertimbangkan code splitting atau module bundling. Daripada me-load semua JavaScript dalam satu file besar, pecah jadi modul-modul kecil dan load hanya yang dibutuhkan di halaman tersebut. Ini biasanya butuh setup build tool seperti Webpack atau Rollup, tapi dampaknya ke performa bisa besar.

Ini bukan hal yang bisa saya pelajari dari satu artikel saja. Ini butuh eksperimen, trial and error, dan kadang, debugging yang bikin kepala berasap. Tapi setiap kali berhasil, rasanya seperti menemukan harta karun.

The Catch-22 of Third-Party Scripts (And How I Manage Them)

Ini mungkin biang kerok terbesar yang sering saya temui di situs saya sendiri atau proyek-proyek personal. Skrip pihak ketiga. Google Analytics, Facebook Pixel, Intercom chat, YouTube embeds, iklan, A/B testing tools. Daftarnya panjang.

Masalahnya, kita tidak punya kontrol penuh atas skrip ini. Mereka bisa saja tiba-tiba lambat, atau bahkan punya error yang bikin situs kita crash. Tapi kita butuh mereka untuk analytics, marketing, atau fitur lainnya. Ini dilema.

Cara saya mengelola mereka? Sebisa mungkin, pakai async atau defer. Untuk skrip yang tidak kritikal di awal, saya tunda load-nya sampai ada interaksi pengguna (misalnya, scroll atau klik). Ada banyak library kecil untuk ‘lazy load’ skrip, atau kamu bisa implementasikan sendiri dengan Intersection Observer API.

Saya juga sering pakai Google Tag Manager (GTM). Ini bukan solusi ajaib, tapi GTM memberi kontrol lebih baik kapan dan bagaimana skrip pihak ketiga di-load. Kamu bisa set trigger, misalnya, ‘hanya load Facebook Pixel setelah 5 detik’ atau ‘setelah pengguna scroll 50%’. Ini membantu sekali untuk menunda eksekusi skrip yang tidak penting di awal, sehingga browser bisa fokus merender konten utama.

Pernah suatu ketika, sebuah script live chat pihak ketiga tiba-tiba jadi sangat lambat. LCP saya langsung anjlok. Saya tidak bisa menghapus script itu, karena penting untuk support. Solusinya? Saya ubah di GTM agar script itu hanya di-load setelah 10 detik atau setelah pengguna mengarahkan kursor ke ikon chat. Ini kompromi yang harus saya ambil, tapi setidaknya LCP saya kembali hijau.

Mengelola render-blocking JavaScript, terutama dari pihak ketiga, itu seperti bermain tarik tambang. Selalu ada yang baru, selalu ada yang harus disesuaikan. Tidak ada formula ‘satu ukuran untuk semua’.

Apa benar semua JavaScript harus dihilangkan?

Tentu saja tidak. JavaScript itu tulang punggung interaktivitas modern. Masalahnya bukan pada JavaScript-nya, tapi pada bagaimana kita me-load dan mengeksekusinya. Tujuannya adalah memastikan JavaScript tidak menghalangi proses rendering awal halaman. Jadi, bukan dihilangkan, tapi dioptimalkan.

Ini pekerjaan yang tidak ada habisnya. Saya menyalakan laptop, dan mulai dari langkah pertama yang tadi saya tulis: cek lagi setiap script yang ada di situs saya. Satu per satu.

← Back to Blog Next Article →