Most performance guides start with images, APIs, and React re-renders. Those matter—but on many sites the slowest work on the main thread is still JavaScript: fetch, parse, compile, and execute the app bundle. This post is a bottom-to-top way to think about that, with bundle size as the first lever after you rule out device and network.
If you publish with Dorae, the same idea applies to your own stack: keep the client lean and put long-form content in Markdown on disk instead of shipping more runtime for every page.
How the browser loads a page
Rough order of operations:
- The server sends HTML (and often a JS bundle reference).
- HTML is parsed. If JavaScript is present, it is downloaded, parsed, compiled, and executed.
- CSS is applied.
- Images load—often lazily.
Anything that blocks or competes for the main thread during step 2 shows up as “slow site,” even when images and APIs look fine.
Why the app bundle matters
The bundle is what carries most of your JavaScript. Whether it arrives compressed or not, the browser still has to parse and compile it before your app runs.
Send 100 KB of app JS versus 25 KB—the larger file wins on main-thread time almost every time. Same idea as tooling shows in the webpack-bundle-analyzer: visualize what you ship, then delete or split what you do not need.
We already suspect JS as a culprit. If the bundle is mostly JavaScript, smaller bundle → less CPU and memory → faster time-to-interactive. Not rocket science, but easy to skip when you only optimize images.
Same weight, different cost
In one comparison, a ~62 ms image load next to a ~2 s JavaScript load for a similar payload size: the JS file dominated processing time. Images and large static files are not always the bottleneck; executable script often is.
Root cause, in one line
More bundle size → more parse/compile/execute work. When the app still feels slow with an “okay” bundle, check:
- Device — older phones and laptops have slower CPUs.
- Network — slow connectivity stretches download, not just execution.
If both are fine, treat bundle size as the next hypothesis before chasing re-renders and font subsets.
Bottom-to-top plan of action
Bird’s-eye view: find the root cause instead of running the same React checklist every time.
Typical top-down list:
- Image weight
- Network
- API latency
- Repo size / architecture
- Framework quirks (e.g. React re-renders)
Bottom-to-top flips the order:
- Confirm device and connectivity (test on a recent phone with good Wi‑Fi).
- Measure JavaScript bundle size and shrink it.
- Then images, fonts, CSS, re-rendering, caching, and API shape.
Check JS file weight first—even inlined assets in the bundle count. Improve parse/compile/execute path before moving up the stack.
Ultimate checklist
- Test on a slow phone; if it is slow everywhere with good internet, inspect the bundle.
- Use code splitting and lazy routes; load code when the user needs it.
- Remove unused dependencies; minify with your bundler’s production build.
- Cache smartly (e.g. React Query, service workers where appropriate).
- Measure locally (
yarn build/npm run buildbundle output), then validate in Performance and Lighthouse.
How to improve bundle size
- Code splitting — route-level or component-level; reuse shared chunks.
- Tree-shaking and minification — production builds, sensible loader config.
- Drop dead dependencies — audit
package.jsonregularly. - Lazy load — dialogs, charts, editors only when opened.
- Cache stable assets — long cache headers for hashed filenames.
It is still work. Next.js, Vite, and others print chunk sizes on build; iterate and compare numbers after each change.
Real-world example: not every app can be “HTML only”
Frameworks like Astro popularized islands: mostly static HTML with sprinkles of JS. That is a strong fit for content sites. It is a poor default for heavy client state, rich animation, or large e-commerce flows—you still need a real JS runtime on those surfaces.
See Is Astro the fastest framework in 2022? for the tradeoff in practice. The lesson is not “avoid JavaScript,” but ship less of it by default and add interactivity where the product requires it.
Products like Figma, Gmail, or complex storefronts are easier to build when the UI is JavaScript-first. The answer to “why not only HTML and CSS?” is: you can for documents; you usually cannot for applications—but you can still control how much JS you send.
Under the hood (a year of React perf posts)
A year ago I collected 15 ways to improve React app performance. Bundle size was in the list, but it did not feel like the first question. Reading Addy Osmani on how browsers spend time on JS reframed that: measure the bundle before the usual React tantrums.
That connects to how we think about content agents too: research and writing can stay server-side while the public site stays thin—see what a content agent should actually do for that split.
Conclusion
Image optimization and render tuning are still necessary. They are not sufficient when the bundle is large. I am spending more time on bundle budgets, analyzer output, and “what actually shipped to the browser?”
If you have good readings on bundle size and Core Web Vitals, share them. Until next time—measure the JS first, then climb the stack.
Sources
