Server bisa menjawab dalam 3 ms, namun pengguna tetap menunggu 3 detik — karena separuh waktu tunggu halaman modern terjadi di browser: mengunduh CSS, JavaScript, dan gambar. Optimasi sisi klien ini punya dua tuas besar: perkecil berkasnya, dan pastikan browser tidak mengunduh ulang berkas yang tidak berubah. Keduanya sudah ditangani otomatis oleh integrasi Vite yang kita pakai sejak awal; chapter ini membuka mesinnya, membaca output build nyata, dan melengkapi bagian yang Vite tidak urus: header cache di web server.
npm run build: Minifikasi dan Hash dalam Satu Perintah #
Di pengembangan, npm run dev menyajikan aset apa adanya — tidak dimutilasi agar mudah di-debug. Perintah produksinya:
npm run buildOutput yang Dihasilkan:
vite v8.3.4 building client environment for production...
✓ 6 modules transformed.
rendering chunks...
computing gzip size...
public/build/manifest.json 1.86 kB │ gzip: 0.42 kB
public/build/assets/app-n8mIr9Kk.css 93.28 kB │ gzip: 18.26 kB
public/build/assets/app-CgJgeXhP.js 0.19 kB │ gzip: 0.15 kB
public/build/assets/admin-Bf_4N00d.js 1.80 kB │ gzip: 0.91 kB
public/build/assets/dashboard-C2c3s-wU.js 0.16 kB │ gzip: 0.16 kB
✓ built in 1.58sEmpat hal terjadi dalam 1,58 detik itu. Pertama, CSS Tailwind di-purge menjadi 93,28 kB (dari seluruh utilitas framework, hanya class yang benar-benar dipakai yang ikut). Kedua, JavaScript diminifikasi — nama variabel dipendekkan, komentar dan spasi dibuang (berkas app.js sumber 295 byte menjadi 0,19 kB). Ketiga, setiap berkas mendapat nama ber-hash: app-n8mIr9Kk.css. Keempat, kolom gzip menunjukkan ukuran aktual yang dikirim ketika kompresi server aktif — 93,28 kB CSS menjadi 18,26 kB di kabel.
Nama Ber-hash Berarti Cache Selamanya #
Hash pada nama berkas adalah kunci strategi cache browser. Konten berubah → hash berubah → nama berkas berubah. Dengan begitu berkas dapat di-cache selamanya tanpa risiko basi:
# nginx: /build/* tidak pernah berubah isinya
location /build/ {
add_header Cache-Control "public, max-age=31536000, immutable";
} # Apache: padanan .htaccess
<FilesMatch "\.(css|js|woff2)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>Direktif immutable bahkan membebaskan browser dari permintaan validasi ulang (revalidation). Kunci dari seluruh mekanisme ini: yang tidak boleh di-cache lama adalah dokumen HTML yang merujuk berkas-berkas ber-hash itu — HTML harus tetap divalidasi tiap kunjungan agar rujukan terbarunya selalu ditemukan. Laravel menjembatani keduanya lewat manifest yang dibaca directive @vite:
{
"resources/css/app.css": {
"file": "assets/app-n8mIr9Kk.css",
"isEntry": true
},
"resources/js/app.js": {
"file": "assets/app-CgJgeXhP.js",
"isEntry": true
}
}Manifest nyata di atas (dipotong dari public/build/manifest.json) memetakan nama sumber yang kita tulis di @vite([...]) ke berkas ber-hash hasil build. Directive @vite membaca manifest ini di produksi dan menyuntikkan tag <link>/<script> yang tepat — kode Blade kita tidak pernah tahu nama berhash mana yang sedang aktif.
Code Splitting: Muat yang Dibutuhkan Halaman Itu #
Tidak semua JavaScript berguna di semua halaman. Vite memecah bundel menjadi chunk per titik masuk, dan import() dinamis memuat modul hanya saat dibutuhkan. Konfigurasi kita mendaftarkan dua entry:
// vite.config.js
laravel({
input: ['resources/css/app.css', 'resources/js/app.js', 'resources/js/admin.js'],
}),Berkas resources/js/admin.js memuat modul grafik secara dinamis:
document.addEventListener('DOMContentLoaded', async () => {
const el = document.querySelector('#statistik');
if (el && el.dataset.nilai) {
const { renderStatistik } = await import('./dashboard');
renderStatistik(el, JSON.parse(el.dataset.nilai));
}
});Perhatikan kembali output build di atas: dashboard-C2c3s-wU.js lahir sebagai berkas terpisah (chunk) — kode grafik tidak ikut ke bundel utama. Halaman publik yang hanya memuat app.js tidak pernah mengunduhnya; halaman admin memuatnya saat elemen #statistik benar-benar ada. Pada aplikasi besar, pola ini memangkas ukuran bundel awal yang menahan render pertama.
Gambar: Tetap Kandidat Terbesar #
JavaScript dan CSS sering kalah besar dari gambar. Tiga kebiasaan yang berdampak paling cepat:
- Kompresi dan format modern — WebP/AVIF umumnya 30–50% lebih kecil dari JPEG dengan kualitas setara; banyak CDN yang mengonversi otomatis.
- Ukuran responsif — layar ponsel tidak perlu mengunduh gambar 3.000 piksel; atribut
srcsetmenyediakan beberapa ukuran agar browser memilih. loading="lazy"— gambar di bawah lipatan layar tidak diunduh sampai pengguna menggulir; satu atribut, gratis.
Rasio pada aplikasi nyata: satu hero image 2 MB yang dikompresi menjadi 150 KB menghemat lebih banyak byte daripada seluruh optimasi JS di halaman yang sama.
Mengukur dengan DevTools #
Angka-angka di atas diverifikasi di panel Network browser DevTools: filter Css/Js/Img, aktifkan kolom Size dan Time, lalu reload dengan cache dinonaktifkan. Bandingkan ukuran transfer (hasil gzip) dengan ukuran resource (berkas asli), dan pastikan reload kedua menunjukkan aset ber-hash diambil dari memory/disk cache — bukti strategi cache bekerja. Panel Lighthouse melengkapi dengan skor dan daftar aset yang menahan render.
Aset kini kecil dan di-cache agresif. Dari sini, pekerjaan optimasi berpindah dari “membuat cepat” ke “menjaga tetap cepat”: bagaimana menemukan titik lambat baru sebelum pengguna mengeluh — pekerjaan chapter Profiling Laravel .