Code Splitting — createLazy splits whole routes

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Compare with lazy_suspense: React.lazyReact.lazy splits one component; createLazy splits an entire route (component + loader) and keeps the route's types in the main bundle for type-safe links.

main bundle vs. lazy chunk — what ships where

A route is split into two files. The non-lazy file (chart.tsx) holds the route config — path, validateSearch, beforeLoad, and the generated types. It is bundled into the main bundle so every <Link to="/chart"> and useSearch() stays type-safe without pulling in the heavy chart code. The lazy file (chart.lazy.tsx) holds the component and loader — it becomes its own chunk, fetched on demand.

piecebundlewhy
createFileRoute config
(path, validateSearch, beforeLoad)
main bundle
(~always)
needed for type-safe <Link>, useSearch, and matching before the route mounts
createLazyFileRoute
(component + loader)
lazy chunk
(~on demand)
heavy runtime code (charts, editors, maps) — only paid for when the route is visited
route types (inferred) main bundle type info is erased at compile time; it never reaches the wire
<Suspense> boundary main bundle router auto-wraps the lazy route; shows a fallback while the chunk loads

1 · the code-split router you write (edit me)

2 · Babel compiles JSX → element tree

<CodeSplitDemo/> becomes React.createElement(CodeSplitDemo). lazyChart.useComponent() returns the cached component or null (the <Suspense> fallback handles the gap). The status poll re-renders the bundle bar as the chunk transitions idle → loading → loaded.

// (hit "compile & render" to see Babel's output)

3 · live React (the split, proven)

Click Chart to navigate to the lazy route — watch the "Loading chunk…" state, then the chunk resolves (~600ms) and the bar grows to ~250KB. Hit prefetch first and the next navigation is instant (cache hit). The gold-check runs all of this automatically.

chunk: · gold: home → chart (loading → loaded, ~250KB) → home

intent → pattern

intentpatternwhy
split a heavy route createLazyFileRoute('/x')({{component, loader}}) in x.lazy.tsx component + loader ship in their own chunk; fetched only when visited
keep links type-safe leave createFileRoute('/x')({{validateSearch, beforeLoad}}) in x.tsx (main bundle) types + matching config are always present; <Link to="/x"> validates
avoid the loading flash <Link preload="intent"> or router.preloadRoute() fetch the chunk on hover/viewport; by click time it's cached → instant
show a fallback while loading wrap in <Suspense fallback={{…}}> (router does this per-route) lazy component returns a promise; Suspense renders the fallback
split ONLY a component (not a route) use React.lazy() + <Suspense> instead createLazy is route-scoped and also splits the loader — React.lazy cannot