Lazy Loading & Code Splitting
Every component we've written so far has been part of the application's FIRST loaded JavaScript bundle. As an application grows, so does this bundle -- users may end up downloading code for pages they'll never even visit. This lesson covers how to avoid that.
Loading a Component Later with React.lazy
lazy() turns a component's code into a SEPARATE file that's
downloaded when needed, instead of a regular import:
import { lazy, Suspense, useState } from "react";
// lazy(), component'in KODUNU normal bir import yerine dinamik bir
// import() ile yükler -- bu dosya, uygulamanın ilk yüklenen paketine
// (bundle) DAHİL EDİLMEZ, yalnızca gerçekten gerektiğinde ayrı bir dosya
// olarak indirilir. Bu, "code splitting" dediğimiz şey.
const CourseDetails = lazy(() => import("./CourseDetails.jsx"));
function ReactLazyBasicExample() {
const [showDetails, setShowDetails] = useState(false);
return (
<div>
<button onClick={() => setShowDetails(true)}>Show Details</button>
{showDetails && (
// Suspense, lazy component'in kodu YÜKLENIRKEN gösterilecek bir
// fallback UI belirtir -- kod indirilene kadar `fallback`
// gösterilir, indirilince gerçek component render edilir.
<Suspense fallback={<p>Loading...</p>}>
<CourseDetails />
</Suspense>
)}
</div>
);
}
lazy(() => import("./CourseDetails.jsx")) REMOVES CourseDetails's
code from the app's initial bundle -- it's only downloaded once
showDetails becomes true. Suspense is required to show a
fallback during this download (we'll take a closer look at Suspense
in the next lesson).
Route-Based Code Splitting
The most common use of lazy() is splitting the pages from the
Routing lesson into separate bundles:
import { lazy, Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router";
// Routing dersinde her sayfayı normal import ile yüklemiştik -- gerçek bir
// uygulamada, KULLANICI O SAYFAYA GİTMEDEN, hiç ziyaret etmeyeceği
// sayfaların kodunu indirmek istemeyiz. lazy() ile her sayfayı ayrı bir
// paket haline getirip, yalnızca o route'a gidildiğinde indirebiliriz.
const CoursesPage = lazy(() => import("./CoursesPage.jsx"));
const AboutPage = lazy(() => import("./AboutPage.jsx"));
function RouteBasedCodeSplittingExample() {
return (
<BrowserRouter>
{/* Suspense, Routes'un DIŞINA sarmalanıyor -- hangi sayfaya
gidilirse gidilsin, o sayfanın kodu yüklenene kadar TEK bir
fallback gösterilir. */}
<Suspense fallback={<p>Loading page...</p>}>
<Routes>
<Route path="/courses" element={<CoursesPage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Each page (CoursesPage, AboutPage) is its own separate file -- if a
user never visits /about, that page's code is never downloaded. This
pattern is called code splitting: breaking an application into
multiple small pieces instead of one giant bundle.
Using lazy with Named Exports
lazy() expects import() to resolve to a DEFAULT export -- a
component with a named export needs a small adaptation:
import { lazy, Suspense } from "react";
// lazy(), import() fonksiyonunun DEFAULT export döndürmesini bekler --
// eğer CourseChart yalnızca bir named export ise (`export function
// CourseChart() {}`, `export default` DEĞİL), .then() ile onu bir
// "default" alanına sarmalamamız gerekir.
const CourseChart = lazy(() =>
import("./CourseChart.jsx").then((module) => ({ default: module.CourseChart })),
);
function NamedExportLazyExample() {
return (
<Suspense fallback={<p>Loading chart...</p>}>
<CourseChart />
</Suspense>
);
}
.then((module) => ({ default: module.CourseChart })) CONVERTS the
named export (CourseChart) into the { default: ... } shape that
lazy expects.
Conditional Lazy Loading
lazy() is useful not just for pages, but for ANY rarely-used
component:
import { lazy, Suspense, useState } from "react";
// EmojiPicker gibi büyük, nadiren kullanılan bir component'i lazy
// yapmak özellikle faydalı -- kullanıcıların çoğu belki hiç açmaz, o
// zaman kodunu hiç indirmemiş oluruz.
const EmojiPicker = lazy(() => import("./EmojiPicker.jsx"));
function ConditionalLazyLoadExample() {
const [showPicker, setShowPicker] = useState(false);
return (
<div>
<button onClick={() => setShowPicker(!showPicker)}>
{showPicker ? "Hide" : "Show"} Emoji Picker
</button>
{/* EmojiPicker'ın kodu, `showPicker` İLK KEZ true olana kadar HİÇ
indirilmez -- yalnızca gerçekten kullanılacaksa yükleniyor. */}
{showPicker && (
<Suspense fallback={<p>Loading emoji picker...</p>}>
<EmojiPicker />
</Suspense>
)}
</div>
);
}
EmojiPicker's code is never downloaded until the user makes
showPicker true for the FIRST time -- most users may never use it,
in which case we never download its code at all.
Summary and Glossary
lazy() splits a component's code into a separate file (chunk),
downloading it only when it's actually needed -- this REDUCES the
amount of JavaScript loaded initially. Its most common uses are
splitting pages (routes) or rarely-used components (modals, emoji
pickers). lazy() is always used together with Suspense -- a
fallback is needed while the code downloads.
Glossary
Code Splitting — The technique of breaking an application's JavaScript into small pieces that are downloaded as needed, instead of one large bundle.
Bundle — An application's JavaScript files combined together to be sent to the browser.
Chunk — A small, separately downloadable JavaScript file produced by code splitting.