Legacy JavaScript
Updated
The Lighthouse 13 insight Legacy JavaScript flags scripts that carry polyfills and syntax transforms for features every current browser already supports. A build that targets old browsers rewrites classes, arrow functions, optional chaining and async functions into longer ES5 code and adds polyfills for methods such as Array.prototype.includes or Object.fromEntries. Modern browsers download, parse and execute all of it without needing any of it. The insight succeeds the audit "Avoid serving legacy JavaScript to modern browsers", which is no longer part of the Lighthouse 13 default configuration (Lighthouse source code, core/config/default-config.js, version 13.5, retrieved 18 October 2026). Its companion Duplicated JavaScript, which replaces "Remove duplicate modules in JavaScript bundles", is covered further down because the fix lives in the same place: the build configuration.
From PerfDelta's measurements: In the mobile lab test, the median home page transfers 1.5 MB of JavaScript out of 4.3 MB in total (5,684 home pages). PerfDelta does not count legacy or duplicated JavaScript separately; both are part of that amount. More in the data section below.
What the Legacy JavaScript insight checks
Lighthouse scans every script for known polyfill and transform signatures and checks them against Baseline, the set of web features that are interoperable across the major browsers. Since Lighthouse 12.5 the check uses Baseline instead of the older ES-modules rule. According to Chrome's documentation the insight fails if more than 5 KiB of polyfills or transforms are found for features that are widely supported according to Baseline (Chrome for Developers, Legacy JavaScript).
The table in PageSpeed Insights lists one row per script with its URL and the estimated wasted bytes. Under each script, sub-rows name the signal that was found, for example Array.prototype.at, Object.hasOwn or a Babel transform such as @babel/plugin-transform-classes, together with the source location. If the site ships source maps, the location points to the original module, which makes it easy to see whether the code is yours or comes from a dependency.
The insight carries no weight in the performance score. The bytes it finds add to the page weight and to the parse and execution time that shows up as Total Blocking Time and in JavaScript execution time.
How to fix legacy JavaScript
1. Set a modern browser target
Most build tools read their target from Browserslist. A target of the browsers your visitors actually use removes most transforms in one step. Browserslist supports Baseline queries directly:
// package.json
{
"browserslist": ["baseline widely available"]
}
// or, more explicit
{
"browserslist": ["> 0.5%", "last 2 versions", "not dead", "not op_mini all"]
}
Check your own analytics before tightening the target. If a relevant share of visitors uses an old browser, the transform is there for a reason.
2. Load polyfills by usage, not wholesale
With Babel, @babel/preset-env together with core-js adds only the polyfills the target browsers need, and only for features the code actually uses:
// babel.config.json
{
"presets": [["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": "3.38",
"bugfixes": true
}]]
}
Remove global imports such as import 'core-js/stable' or import '@babel/polyfill' from the entry file; they load every polyfill regardless of the target.
3. Set the output target in TypeScript, esbuild and Vite
// tsconfig.json
{ "compilerOptions": { "target": "ES2020" } }
// vite.config.js
export default { build: { target: 'es2020' } };
// esbuild
esbuild app.ts --bundle --target=es2020 --outfile=app.js
A target of ES5 in an old tsconfig.json is one of the most common causes. Raising it removes the down-levelled classes and async functions without changing any source code.
4. Check dependencies that ship pre-compiled ES5
If the sub-rows point into node_modules, the package itself is published as transpiled code. Look for a modern build of the same package (an exports or module entry, or a package with the suffix -es), upgrade to a version that ships modern syntax, or replace a polyfill-heavy helper library with the native method.
5. Platforms and third parties
On WordPress, legacy JavaScript often comes from older plugins and themes that bundle their own polyfills; an update or a lighter plugin is usually the only lever. On Shopify, check app scripts. If the flagged script is from a third party, the fix lies with the vendor, and the third-party code guide explains how to load such scripts later.
Duplicated JavaScript: one module, shipped twice
The insight Duplicated JavaScript finds modules that occur more than once in the scripts of a page, for example two versions of lodash, a date library bundled into two chunks, or a framework runtime in the main bundle and again in a widget. Its description asks you to remove large, duplicate JavaScript modules from bundles to reduce unnecessary bytes. The table shows the source module and the duplicated bytes per script. Lighthouse identifies modules through source maps, so without source maps the insight can only see part of the duplication.
Find the duplicates in the dependency tree and in the bundle:
# Which versions of a package are installed?
npm ls lodash
pnpm why lodash
# Merge compatible versions
npm dedupe
# Inspect the built bundle
npx source-map-explorer dist/assets/*.js
npx webpack-bundle-analyzer dist/stats.json
Typical fixes:
- Align versions. Pin one version for all dependents with
overrides(npm),resolutions(Yarn) orpnpm.overrides. - Avoid two flavours of the same library.
lodashandlodash-es, ormomentandmoment-timezonewith their own copies, end up as separate modules. - Share chunks. In webpack,
optimization.splitChunksmoves modules used by several entry points into a shared chunk instead of copying them into each. - Separate builds on one page. Two independently built applications on one page, such as a shop and a configurator, each bring their own runtime. Module federation or one shared build removes the second copy.
Checking the result
- Rebuild and run PageSpeed Insights again. Scripts below the threshold disappear from the Legacy JavaScript table; the duplicated bytes should fall.
- Compare the JavaScript transfer size and the main-thread time in the Network and Performance panels of DevTools before and after.
- Test the site in the oldest browser you still support, so that the new target does not break it.
The free PerfDelta website speed test shows the JavaScript weight and the largest scripts for several pages of your site and next to your competitors, so you can see whether a build change has the effect you expected across the whole site. For code that is shipped but never executed, see reduce unused JavaScript.
Frequently asked questions
Can I drop all polyfills?
Only for features that all browsers of your audience support. Baseline "widely available" covers features that have been interoperable for at least 30 months; check your analytics for visitors on older devices before removing more.
Why does the insight flag code I did not write?
Dependencies and third-party scripts are part of the page. The source location in the sub-row shows which package or file the polyfill comes from.
Does fixing legacy JavaScript improve the score?
Indirectly. Less code means less parse and execution time, which lowers Total Blocking Time, and smaller transfers, which can help LCP when scripts compete with the LCP resource for bandwidth.
Why does Duplicated JavaScript show nothing on my site?
Either there are no large duplicates, or the scripts have no accessible source maps. Without source maps Lighthouse cannot tell which modules a bundle contains.
What PerfDelta's measurements show
In the mobile lab test, the median home page transfers 1.5 MB of JavaScript out of 4.3 MB in total (5,684 home pages). PerfDelta does not count legacy or duplicated JavaScript separately; both are part of that amount.
Lighthouse reports unused JavaScript on 86% of home pages, with a median saving of 1650 ms.
Data as of October 18, 2026, recalculated daily. Source: PerfDelta's own measurements (Chrome UX Report and Lighthouse); details in the website speed statistics and the methodology.
Thresholds and sources
Sources:
- Chrome for Developers: Legacy JavaScript insight
- Chrome for Developers: Duplicated JavaScript insight
- web.dev: Baseline and polyfills
- Lighthouse: legacy-javascript-insight (source code, version 13.5)
All sources retrieved on October 18, 2026. Thresholds and documentation change; the linked page is authoritative.
Test your site
Run a free scan to see whether this applies to your pages, on mobile and desktop, with lab and field data side by side.
Run a free testA free account saves your tests and shows how your values change from one test to the next. Daily monitoring with field data and alerts starts with the Solo plan.