Reducing JavaScript bundle size: find it, split it, delete it
Bundle size is the cost you pay on every visit, on every device. It is also the one performance metric that only ever grows unless someone owns it.
Interaction to Next Paint measures the time from a user interaction to the next visual update, reporting roughly the worst interaction of a visit. It breaks into input delay (main thread busy), processing time (your event handler) and presentation delay (rendering). Improving it means shipping less JavaScript, breaking long tasks up, and yielding to the main thread.
| Phase | What is happening | Common cause of delay |
|---|---|---|
| Input delay | Waiting for the main thread to be free | A long task already running |
| Processing time | Your event handlers execute | Expensive work, large re-renders |
| Presentation delay | Browser computes style and paints | Complex layout, huge DOM |
Attribution matters as much as it does for LCP. If input delay dominates, the fix is elsewhere on the page — a script blocking the thread. If processing dominates, the fix is in your handler.
// Blocking: one long task, no paint until finished
function process(items) {
for (const item of items) expensiveWork(item);
render();
}
// Yielding: lets the browser paint between chunks
async function process(items) {
for (const [i, item] of items.entries()) {
expensiveWork(item);
if (i % 50 === 0) await yieldToMain();
}
render();
}
const yieldToMain = () =>
"scheduler" in window && "yield" in scheduler
? scheduler.yield()
: new Promise((r) => setTimeout(r, 0));In audits we run, third-party tags are frequently the largest single contributor to main-thread blocking — tag managers, chat widgets, session recording, A/B testing and ad scripts. Each one executes on your main thread and competes with your own code for the same 16 milliseconds per frame.
200ms or less at the 75th percentile is good; above 500ms is poor. It is a harder target than the old FID threshold because it measures every interaction, not just the first.
Mobile CPUs are far slower. JavaScript that takes 20ms on a laptop can take 200ms on a mid-range phone, which turns an invisible cost into a failed metric.
It helps initial load but not interaction cost — hydration itself can be a large blocking task. Reducing shipped JavaScript is what moves INP.
Animating layout properties like width, top or margin forces reflow on every frame. Animate transform and opacity instead; they can be handled by the compositor.
ROVQIX Growth
SEO & growth team, ROVQIX
The ROVQIX growth team handles technical SEO, Core Web Vitals and AI-search visibility for the sites we build. Recommendations here are the ones we apply to client projects and to rovqix.in itself.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Bundle size is the cost you pay on every visit, on every device. It is also the one performance metric that only ever grows unless someone owns it.
Every third-party tag runs on your main thread, in your users' browsers, with your performance budget. Almost none of them have an owner.
Sprinkling memo and useCallback across a codebase is not optimisation, it is superstition. Measure first, then apply one of four structural fixes.
No spam. Just the occasional case study and craft breakdown.