Shipping Less JavaScript
AdvancedThe fastest code is the code you never send. Splitting, deferring and choosing smaller dependencies beat almost any runtime optimisation.
Overview
React applications are usually slow to load for one reason: they ship too much JavaScript, and every kilobyte must be downloaded, parsed and executed before anything is interactive — on a mid-range Android over a patchy connection, not on the developer's laptop. The levers are few and effective: split by route, defer what is below the fold, choose lighter dependencies, and avoid loading third-party scripts on the critical path. None of it is clever, which is why it is often skipped in favour of memoisation that saves microseconds.
The Splitting Hierarchy
In order of value, and where the line stops being worth crossing.
// 1. By route — the biggest single win
const Admin = lazy(() => import('./features/admin/AdminPage'))
// 2. Heavy libraries, loaded on demand
async function exportPdf(rows) {
const { jsPDF } = await import('jspdf') // 300KB, only on click
...
}
// 3. Below the fold — comments, recommendations, related items
const Comments = lazy(() => import('./Comments'))
<Suspense fallback={<CommentsSkeleton />}>
{inView && <Comments />} // with an observer
</Suspense>
// 4. Conditional features — an editor only Pro users open,
// an admin toolbar, a debug panel
// Where to stop: a small component on the critical path. An extra
// round trip costs more than the few kilobytes saved, and too many
// chunks is its own problem.Dependency Weight
Check before installing, and revisit what is already there.
// Check the cost before adding: bundlephobia.com, or after the
// fact with the bundle analyzer.
// Common swaps, with typical savings:
// moment (70KB) -> Intl.DateTimeFormat (0) or date-fns (2KB used)
// lodash (70KB) -> lodash-es tree-shaken, or native methods
// axios (15KB) -> fetch (0)
// uuid (5KB) -> crypto.randomUUID() (0)
// a full icon set -> named imports only
import { Search } from 'lucide-react' // one icon
import * as Icons from 'lucide-react' // the whole set
// Barrel files quietly undo tree shaking:
import { Button } from '@/components' // may pull the graph in
import { Button } from '@/components/Button' // predictable
// Third-party scripts are the invisible half of the problem —
// analytics, chat widgets and tag managers often exceed the app
// bundle. Load them after interactive, never render-blocking.
<Script src="..." strategy="afterInteractive" />Virtualisation
When the list itself is the problem, render only what is visible.
// 5,000 rows = 5,000 components and 5,000+ DOM nodes. The browser
// struggles regardless of how efficient the React is.
import { useVirtualizer } from '@tanstack/react-virtual'
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 64,
overscan: 5, // render a few extra to hide scrolling
})
<div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(item => (
<div key={rows[item.index].id}
style={{ position: 'absolute', top: 0,
transform: `translateY(${item.start}px)` }}>
<Row row={rows[item.index]} />
</div>
))}
</div>
</div>
// Roughly 20 DOM nodes instead of 5,000.
// Costs: Ctrl+F does not find off-screen rows, and variable heights
// need measurement. Reach for it above ~200 rows, not before.Key Points to Remember
- 1Splitting by route is the highest-value change; then heavy libraries, below-the-fold content and conditional features
- 2Stop splitting when the extra round trip costs more than the bytes saved
- 3fetch, Intl and crypto.randomUUID have replaced several once-standard dependencies
- 4Third-party scripts often outweigh the app bundle — load them after interactive
- 5Virtualisation renders only visible rows and is worth it above roughly 200 items, at the cost of in-page find
Interview Questions
Sign in to ask AriaWhat are the highest-value things to code-split in a React app?
When is virtualisation worth its complexity, and what does it cost?
How would you reduce the initial JavaScript of an existing React app?
Ask Aria about Shipping Less JavaScript
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.