Bundlers and builds
Transpiling, the module graph, tree shaking, code splitting, hashes; Turbopack and Vite.
Updated
What a bundler is
The code you write is not the code that reaches the browser. You write hundreds of TypeScript and JSX files, importing each other and from node_modules. The browser would have to make hundreds of requests, and it understands neither TypeScript nor JSX.
A bundler takes all this code and turns it into a few optimized files:
source codeTS, JSX, CSS, importstranspiling→plain JavaScriptbundling→combined filesoptimizing→buildminified, split, hashedWhat it does, step by step
| Step | What it means |
|---|---|
| transpiling | TS → JS (types are erased), JSX → function calls, new syntax → compatible syntax |
| resolving imports | starts from an entry file and follows every import — the module graph |
| tree shaking | removes exports nobody imports |
| code splitting | splits the code into chunks: each page loads only what it needs; a dynamic import() = a separate chunk |
| minification | short names, no whitespace or comments |
| a hash in file names | app.3f2a9c.js — new content = new name → the browser can cache the files forever |
Today's bundlers
| Tool | Where you'll meet it | Defining trait |
|---|---|---|
| Turbopack | Next.js (the default since Next 16) | written in Rust, incremental, very fast in dev |
| Vite | React SPAs, Vue, Svelte, Vitest | an instant dev server on native ES modules, builds with Rollup / Rolldown |
| webpack | older projects, Next before 16 | the most configurable, the slowest |
| esbuild, SWC | inside the others | ultra-fast transpiling |
In practice you don't configure the bundler by hand — the framework (Next, Vite) does it. But you need to understand what it does, to know why an import matters.
Dev vs build
npm run dev |
npm run build |
|
|---|---|---|
| Goal | speed for you while working | speed for users |
| Optimizations | minimal | all of them (minification, tree shaking, splitting) |
| Reloading | HMR — the changed module is swapped without a reload, keeping state | — |
| Errors | detailed, with an overlay | the build fails |
What you can do for a small bundle
- Named imports from libraries that support tree shaking (
import { debounce } from 'lodash-es'). next/dynamic/import()for heavy, rarely used components (editors, charts, maps) — exactly how the editor in this app is loaded.- Server Components for everything that isn't interactive — their code never reaches the bundle.
- Check what takes up space:
next buildshows the sizes, and the bundle analyzer shows what takes them up.
Summary
- A bundler = transpiling + an import graph + tree shaking + code splitting + minification + hashes.
- Turbopack in Next, Vite elsewhere; you don't configure them, but you understand them.
- A small bundle: named imports,
import()for heavy code, Server Components.