From React Code to Browser: The Journey Simplified
October 20, 20254 min readShrivatsa Kashyap
The journey, simplified
A developer writes hundreds of lines of React and TypeScript - carefully
typed, neatly organized, sprinkled with just the right amount of prop drilling
and context magic. Then, one build command later, everything vanishes into a
folder named dist/.
Inside it sits a handful of mysterious files like app.a9d5313.js and
vendor.91f4ce.css, each only a few kilobytes in size. All those types,
components, and imports - reduced to a minified blob of JavaScript that
somehow still works perfectly in the browser.

What actually happens between writing App.tsx and that final bundle is a
fascinating chain of transformations - equal parts engineering and sorcery.
This post traces that journey, step by step, through the modern React build
process.
1. Writing code in .ts / .tsx
Everything in a modern React app starts here - TypeScript or TSX files that describe your UI. TypeScript helps catch type errors early, while JSX makes your UI expressive and declarative.
These files aren't directly understood by browsers, so they'll first need to be converted into standard JavaScript.
What happens to the types we write?
All those interface, type, and Props definitions you spend time writing?
They don't exist at runtime - TypeScript's type system is completely erased
during the build step.
When the compiler transpiles your .ts or .tsx files, it:
- Removes every type annotation and interface
- Replaces
import typeandexport typewith nothing - Leaves behind only plain JavaScript logic
The result is zero type overhead in production - the browser never even knows TypeScript was used. TypeScript is purely a developer-time tool that ensures safety, autocompletion, and maintainability, not something shipped to users.
2. Transpiling to JavaScript
This step is known as transpilation. Your TypeScript code goes through tools like esbuild, SWC, or Babel, which:
- Strip away TypeScript types, since the browser doesn't use them
- Convert JSX into valid JavaScript calls, like
jsxorReact.createElement - Optionally apply optimizations or compatibility transforms
After this step, we're left with clean, browser-readable JavaScript - no types, no TSX syntax.
3. Vite starts from index.html and follows every import
Vite acts as the entry point resolver and module graph builder. It starts from
your index.html, finds main.tsx, then follows every import down the
dependency tree.
In development, Vite serves each module individually for fast hot reloads. But in production, it uses Rollup to bundle them together.
4. Tree shaking
Now that Vite knows which modules are actually used, it performs tree shaking - removing any unused code and imports.
This relies on ES Modules' static structure to safely eliminate dead code. For example, if you imported a utility function but never called it, it's gone. (Though this problem can be solved during development using eslint plugins.)
Tree shaking = leaner bundles = faster load times.
5. Bundling, code splitting, and lazy loading
Next comes bundling - combining related modules into optimized chunks. Vite and Rollup group code intelligently:
- App chunks: your React components and logic
- Vendor chunks: third-party libraries like
reactandreact-dom
With code splitting, each chunk can load lazily - meaning the browser only fetches what it needs when it needs it. This improves first-load performance dramatically.
6. Minification
Once bundled, the JS code gets minified. This step removes anything unnecessary:
- Whitespace and comments
- Long variable names, shortened to single letters
- Unused branches, like
if (process.env.NODE_ENV !== 'production')
Minification doesn't change behavior - it just makes the payload smaller and faster to download.
7. CSS and asset processing
Your CSS goes through a similar optimization pipeline:
- CSS is extracted, deduplicated, and minified
- It's processed by PostCSS for autoprefixing, Tailwind, and more
- Images and fonts can be inlined as data URLs or emitted as separate, optimized files
This ensures styles and assets load quickly and efficiently.
8. Hashing files
Each generated file gets a content hash, like app.a9d5313.js.
Why? Because if your code doesn't change, the hash stays the same, allowing CDNs to cache aggressively. When you push a new build, new content means a new hash, which means instant cache busting. This is a best practice for long-term caching and version safety.
9. Output to /dist
Finally, everything lands in the dist/ folder:
index.htmlupdated to point to the hashed assets- JavaScript chunks, CSS, and images neatly organized
This is the production build, ready to be hosted on any static server or CDN.
Compression, at the server and CDN level
Once deployed, most servers automatically serve compressed versions, like Gzip or Brotli. This step isn't handled by React or Vite - it's a network-level optimization handled by CDNs.
Compressed payloads = faster downloads = happier users.
TL;DR
Here's the full journey, compressed:
.ts/.tsx → transpile → bundle → tree-shake → split → minify → process CSS → hash → output to dist → compress → serve
Final thoughts
There was always a bit of curiosity about how the code written in React
eventually ends up running in the browser. That question lingered long enough
to spark a small deep dive into the build process. The result is this
simplified breakdown of what actually happens behind the scenes - from .tsx
files to the final bundle.
Maybe the next one will explore how HMR manages to make all of it feel instant.