Pas user buka aplikasi pertama kali, browser download SEMUA JavaScript bundle — kalau gede (1-2MB), waktu loading awal lambat (terutama di koneksi lemah). Tapi user mungkin gak butuh semua kode itu sekaligus — fitur admin gak dipake user biasa, halaman Settings cuma sekali-kali dibuka. Kenapa download semua? Solusi: code splitting — bundle dipecah jadi chunk kecil, tiap chunk di-load on-demand. React.lazy + Suspense = pattern bawaan React buat ini. Halaman pertama lebih ringan, user happy, fitur lain di-load pas dibutuhkan.
💡 Real impact. Project hypersheets pas pakai
lazyper route turun bundle awal dari 800KB jadi 200KB. First Contentful Paint dari 2.8s jadi 1.2s di mobile slow 3G. Diff yang user rasain langsung.
React.lazy — Dynamic Import
import { lazy, Suspense } from "react"
// Component di-import dynamic — chunk terpisah
const HeavyComponent = lazy(() => import("./HeavyComponent"))
function App() {
return (
<Suspense fallback={<Spinner />}>
<HeavyComponent />
</Suspense>
)
}
import("./HeavyComponent") = dynamic import, return Promise. Build tool (Vite/Webpack) auto pisahin jadi chunk terpisah.
Route-based Code Splitting (Pattern Paling Umum)
const Home = lazy(() => import("./pages/Home"))
const About = lazy(() => import("./pages/About"))
const Dashboard = lazy(() => import("./pages/Dashboard"))
function App() {
return (
<Suspense fallback={<PageSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
)
}
User di Home → cuma load Home.chunk.js. Klik /dashboard → load Dashboard.chunk.js saat itu. Bundle awal kecil, navigasi tetap smooth (browser cache chunk-nya).
Suspense Lebih dari Loading State
Di React 19+, Suspense bisa handle:
- Lazy components (yang ini)
- Data fetching (dengan TanStack Query / RSC / use())
- Image loading
- Asset preloading
<Suspense fallback={<Spinner />}>
<DataComponent /> {/* yang fetch data via use() */}
<LazyImage src={url} />
<LazyComponent />
</Suspense>
Suspense = "tunjukin fallback sampai semua child READY (loaded + data ready)". Pattern unified untuk loading state.
Conditional Lazy Loading
function App({ isAdmin }) {
// AdminPanel cuma di-load kalau user admin
return (
<div>
<PublicContent />
{isAdmin && (
<Suspense fallback={<Spinner />}>
<AdminPanel /> {/* lazy() */}
</Suspense>
)}
</div>
)
}
User non-admin gak pernah download AdminPanel chunk.
🎭 Analogi sehari-hari
React.lazy + Suspense itu kayak buffet vs prasmanan. Buffet = semua makanan disajikan sekaligus pas tamu masuk (download all bundle = lambat). Prasmanan = makanan disiapin di stand-stand, tamu ambil yang dia mau (lazy load per chunk). Tamu yang cuma mau sushi gak nungguin tukang masak nasi padang. Hasilnya: tamu masuk gak nungguin lama, dapur kerja efisien (download sesuai kebutuhan), pengalaman makan lebih cepat. Suspense = "tanda tunggu sebentar" pas chef lagi masak ulang di stand tertentu.
⚠️ Jebakan yang sering ditemui
- Tanpa
<Suspense>wrap —lazy()component tanpa Suspense parent = error throw. Harus ada Suspense di atas. - Lazy load semua component — overkill. Lazy cuma untuk component berat atau jarang dipake. Component kecil = overhead chunk request > benefit.
- Lazy load dengan default export —
lazy(() => import("./X"))butuhX.tsxpunyaexport default. Named export butuh wrap manual. - Fallback gak match layout — pas chunk loading, fallback
<Spinner />muncul, layout shift jeglek. Pakai skeleton screen yang match dimensi. - Suspense terlalu deep — fallback granular (per component) bisa flicker chaos. Tempatkan Suspense di route level dulu.
- Lupa preload untuk navigation yang pasti — bisa preload chunk on hover:
import("./Dashboard")di handler hover button. - Server-side rendering dengan lazy = perlu
<Suspense>boundary di SSR setup yang proper.
🎯 Lazy load apa, kapan?
- Per route page (Home, Dashboard, Settings) → ✓ standar default
- Modal/dialog yang jarang dibuka → ✓ load saat dibuka
- Heavy feature (chart library, video player) → ✓ load saat fitur dipake
- Admin/internal-only sections → ✓ user biasa gak download
- Dependency besar (lodash full, moment.js, charting lib) → ✓ atau ganti yang lebih ringan
- Component kecil yang dipake di banyak tempat → ✗ overhead > benefit
- Above-the-fold content → ✗ user butuh segera, jangan delay
Aturan: split BIG + RARELY USED chunks. Small + always used = biarin di main bundle.
TL;DR: React.lazy(() => import("./X")) + <Suspense fallback={<Spinner />}> = code splitting bawaan. Pecah bundle JavaScript jadi chunk yang di-load on-demand. Route-based splitting = pattern paling umum (per page = chunk). Bundle awal jadi kecil = first paint cepat. Wajib Suspense parent. Default export di lazy component. Component kecil yang sering dipake = JANGAN lazy load (overhead > benefit).