Lazy Loading Komponen Vue untuk Performa

Setiap kali saya audit performa aplikasi Vue di klien saya — entah itu dashboard rumah sakit atau sistem administrasi internal — pola yang paling sering saya temukan selalu sama: bundle JavaScript membengkak karena semua komponen di-import secara eager di root, padahal separuh dari komponen itu baru dibutuhkan setelah user klik tab kedua atau ketiga. Lazy loading komponen sebenarnya solusi yang sudah lama tersedia di Vue, tapi banyak tim ragu menerapkannya karena tidak tahu komponen mana yang benar-benar layak di-lazy-load dan mana yang justru bikin pengalaman user lebih buruk kalau di-split.

Kapan Komponen Layak Di-lazy-load

Prinsip yang saya pegang: lazy-load komponen yang tidak terlihat pada initial paint dan yang frekuensi pemakaiannya rendah relatif terhadap total user. Contoh khas: modal, dialog konfirmasi, chart/grafik yang berat (biasanya bawa library visualisasi ukuran besar), editor rich-text, komponen di tab yang belum aktif, atau halaman admin yang cuma diakses segelintir role tertentu.

Sebaliknya, komponen yang tampil langsung di viewport — header, navigasi utama, komponen di atas fold — sebaiknya tetap eager. Kalau dipaksa lazy-load, user malah melihat layout kosong atau "flash" saat chunk-nya di-fetch, yang justru merusak Core Web Vitals, bukan memperbaikinya.

Cara Praktis Menerapkannya

Di Vue, cara paling umum adalah defineAsyncComponent dengan dynamic import:

const ChartPanel = defineAsyncComponent(() =>
  import('./components/ChartPanel.vue')
)

Build tool seperti Vite akan otomatis memecah komponen ini jadi chunk terpisah. Untuk routing, Vue Router mendukung pola yang sama secara native:

const routes = [
  {
    path: '/laporan',
    component: () => import('./views/Laporan.vue')
  }
]

Saya juga biasa tambahkan fallback loading state dan error boundary sederhana, karena chunk yang gagal di-fetch (misalnya koneksi user putus) itu skenario nyata, bukan edge case teoritis:

const ChartPanel = defineAsyncComponent({
  loader: () => import('./components/ChartPanel.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: LoadErrorFallback,
  delay: 200,
  timeout: 5000
})

Dampaknya ke Core Web Vitals

Metrik yang paling langsung terpengaruh adalah LCP (Largest Contentful Paint) dan TBT/INP. Kalau komponen berat seperti chart library atau editor teks tidak lagi ikut di-parse dan dieksekusi saat initial load, main thread lebih cepat idle, sehingga waktu sampai halaman interaktif jadi lebih singkat. Sebagai ilustrasi, kalau sebuah dashboard awalnya memuat satu bundle besar berisi semua widget, memecahnya jadi beberapa chunk yang di-load sesuai kebutuhan biasanya membuat LCP dan TBT membaik secara terasa — meski besarannya sangat bergantung pada seberapa berat komponen yang dipisah dan kondisi jaringan user.

Satu hal yang perlu diwaspadai: CLS (Cumulative Layout Shift). Kalau komponen yang di-lazy-load nanti mengubah tinggi layout setelah chunk-nya selesai dimuat, itu bisa menciptakan shift yang justru menaikkan CLS. Solusinya sederhana — beri placeholder dengan dimensi yang sudah dipatok (misalnya lewat CSS min-height atau skeleton loader) sebelum komponen asli render.

Kesalahan Umum yang Perlu Dihindari

Beberapa jebakan yang sering saya lihat: melakukan lazy-load pada komponen kecil yang overhead network request-nya justru lebih mahal dari ukuran komponennya sendiri; lupa preload komponen yang hampir pasti akan dibuka user berikutnya (misalnya modal yang terbuka begitu user hover tombol); dan menaruh terlalu banyak dynamic import di satu route sehingga malah terjadi waterfall request yang memperlambat, bukan mempercepat.

Takeaway singkat: lazy-load komponen berdasarkan visibilitas awal dan frekuensi pemakaian, bukan sekadar ukuran file. Ukur dampaknya dengan Lighthouse atau Web Vitals sebelum dan sesudah, karena split yang salah tempat bisa memperbaiki satu metrik sambil merusak metrik lain seperti CLS.