Reduce JavaScript Execution Time: Ship Less, Then Defer, Then Split

  • vuetelemetry
  • Guias
  • 8 min de leitura

Lighthouse reports the number, the fix is almost never in your own code. Most execution time belongs to dependencies that run before anything is visible. Three moves, in the order that actually works, and the one measurement to take first.

Lighthouse tells you JavaScript execution took 3.4 seconds and offers a list of files. The instinct is to open your own code and start optimising loops. That is almost always the wrong place, and the reason is arithmetic: in a typical application your own code is a small fraction of the bytes the browser parses, compiles and runs.

Execution time is three separate costs the tooling reports as one. Parsing turns text into an abstract syntax tree, compilation turns that into bytecode, and evaluation actually runs it. All three scale with the amount of JavaScript you ship, which is why the first lever is not faster code, it is less code.

Medir primeiro, nunca adivinhar

A city interchange seen from above: four lanes of cars, taxis and a bus completely stopped in queue on the right, while the two elevated lanes on the left carry no traffic at all.
A city interchange seen from above: four lanes of cars, taxis and a bus completely stopped in queue on the right, while the two elevated lanes on the left carry no traffic at all.

Measure before you touch anything. Open the Performance panel, record a page load with CPU throttling set to 4x, and read the Bottom-Up view grouped by URL. It attributes milliseconds to files. A guess about which dependency is expensive is wrong often enough to waste a day.

Enviar menos, antes de tudo

Move one: ship less. The coverage tab in Chrome DevTools shows what percentage of each file was never executed. Numbers above 70 percent unused are common, and they usually point at a library imported for one function, a polyfill bundle for browsers you no longer support, or an entire date or icon library where three imports were needed.

  • Record with 4x CPU throttling and read Bottom-Up by URL before changing anything
  • Coverage tab: files above 70 percent unused are the first candidates for removal
  • Replace a library with a browser built-in where one exists, Intl before a date library
  • defer for scripts that touch the DOM, load third party tags after the load event
  • Split by route only after removing what should not ship at all

The replacement is rarely dramatic. A date formatting library that costs 70 kB can often be replaced by `Intl.DateTimeFormat`, which costs nothing because it is already in the browser. That single substitution removes parse, compile and evaluate time in one move rather than making them faster.

Adiar o que a primeira pintura nao precisa

Move two: defer what is not needed for first paint. Analytics, chat widgets, consent banners and carousel libraries all execute before the page is usable while contributing nothing to it. `defer` on a script tag keeps download parallel and moves execution after parsing; `async` runs it whenever it arrives, which is worse for anything that touches the DOM.

For third party tags specifically, loading them after the load event, or on first interaction, moves their whole cost outside the window that gets measured and, more importantly, outside the window the user is waiting through.

Dividir o bundle, e por esta ordem

Move three: split the bundle. Route level code splitting means a visitor on the home page does not parse the checkout flow. In a Vue application `defineAsyncComponent` and dynamic `import()` handle this, and modern bundlers split by route automatically when routes are lazily imported.

The order of these three matters. Splitting a bundle that contains 200 kB of unused library code just delays the moment you pay for it. Removing the library first makes the split smaller and the win larger.

The order of these three matters. Splitting a bundle that contains 200 kB of unused library code just delays the moment you pay for it. Removing the library first makes the split smaller and the win larger.

- vuetelemetry

O que o numero nao diz

A warning about the number itself. Total Blocking Time and Interaction to Next Paint are what a visitor experiences; JavaScript execution time is an input to them, not a synonym. Cutting execution time on a page that was already fast changes nothing a user can feel, and a page can score badly on execution while feeling fine because the work happens after paint.

Field data settles the argument. A laboratory run on a fast laptop hides exactly the population that suffers: mid range Android phones on a congested network, where a 300 millisecond parse becomes two seconds. If the field numbers are good and the lab numbers are ugly, believe the field.

FAQ

What causes high JavaScript execution time?

The volume of JavaScript the browser has to parse, compile and evaluate, which is dominated by dependencies rather than by application code in most projects. Long tasks on the main thread during startup, typically framework initialisation and third party tags, are what turn that volume into a number the user feels.

Is execution time the same as Total Blocking Time?

No. Execution time is how long scripts run; Total Blocking Time measures how long the main thread was blocked for more than 50 milliseconds at a stretch between First Contentful Paint and interactivity. Execution time is an input to it, and work that happens after paint can add execution time without adding blocking time.

Does defer or async reduce execution time?

Neither reduces the work; both move when it happens. defer runs the script after HTML parsing in document order, which keeps rendering unblocked and is the right default for anything touching the DOM. async runs it as soon as it downloads, in unpredictable order.

How much JavaScript is too much?

There is no universal threshold, and any figure quoted as one is a heuristic. The useful test is field data: if Interaction to Next Paint and Largest Contentful Paint are within their thresholds for real users on real devices, the amount you ship is defensible whatever the byte count says.

Should I remove my framework to fix this?

Rarely, and not as a first step. Removing unused dependencies, deferring third party tags and splitting by route usually recovers more time than a framework migration, at a fraction of the risk. Reach for the migration only when the measurement points at framework initialisation itself after the other three moves.

Stack relacionado