Legacy JavaScript in Lighthouse: what to fix, what to leave

Code for old browsers that every visitor downloads. Whose file Lighthouse points at, and what to do on WordPress, Shopify, Next.js and your own build.

Arjen Karel
Updated · 6 min read
Share

Should you fix Legacy JavaScript?

Lighthouse shows Legacy JavaScript ("Polyfills and transforms enable older browsers to use new JavaScript features") where it used to show Avoid serving legacy JavaScript to modern browsers. It lists scripts that carry code for browsers almost nobody uses, and every visitor downloads it. Fix your own JavaScript by building it for Baseline Widely available browsers; update, replace or leave a plugin's or vendor's file.

Just want the quick fix? Jump to WordPress, Shopify, Next.js or your own build.

What does Legacy JavaScript find in your scripts?

The notice reads: "Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren’t necessary for modern browsers. Consider modifying your JavaScript build process to not transpile Baseline features, unless you know you must support older browsers."

Lighthouse report, Legacy JavaScript in red with Est savings of 22 KiB and the labels FCP, LCP and Unscored; the script booking.legacy.js with 22.0 KiB wasted bytes and under it 18 lines such as @babel/plugin-transform-classes, Array.from and Object.entries

The booking widget of a ferry test page, run with the settings PageSpeed Insights uses: one script with 22 KiB.

Lighthouse groups the scripts by domain ("1st Party" for your own, the company's name for a vendor) and gives each one its "Wasted bytes". Under each script, a line named after a feature, such as Array.prototype.find or Object.entries, is a polyfill: code that adds that feature to browsers that lack it. A line that starts with @babel/plugin-transform is a transform: Babel, the tool that built the file, rewrote modern code, such as a class or an async function, into older JavaScript with helper functions.

The kilobytes are an estimate and can be larger than the file itself. One small fallback for a single feature is counted as if the script carried the shared base code of core-js, the usual polyfill library, so a file that downloads 4 KiB can show 10 KiB.

Red means a listed script downloads at the same time as your largest element (LCP) or your first content (FCP); orange means it only adds kilobytes. Unscored: Legacy JavaScript itself doesn't count toward the Performance score, but LCP and FCP do.

What does legacy JavaScript do to your visitors?

Your visitor waits a little longer for whatever the script builds: a booking form, a menu, a slider. The browser downloads the extra code and runs it, even when it has every feature built in: each polyfill checks for its feature and keeps the browser's own version, but the bytes and the check come on every page load.

On a test page, the home page of a car ferry company, I built the booking widget in React twice from the same code: once for Internet Explorer 11 with Babel and core-js, once for current browsers. Both ran on the same phone connection, a 150 ms round trip at 1.6 Mbps, with the CPU of a mid-range phone:

BuildBooking form on screenJavaScript (gzip)Script running
For Internet Explorer 111.93 s83 kB80 ms
For current browsers1.73 s49 kB50 ms
Phone screen 1.8 seconds after the tap with the Internet Explorer 11 build: the ferry page's heading, no photo yet and a grey placeholder where the booking form goes

Before

The same moment with the build for current browsers: the car deck photo and the booking form with route, date and passengers

After

The ferry page 1.8 seconds after the visitor tapped the link.

That is 0.2 seconds for one script, 30 ms of it running. A deferred script still shares the connection with the photo at the top of the page, which is why the notice turned red here. A bundle with all of core-js, or a page with several bundles like this one, has more extra code than this widget.

Whose file is Lighthouse pointing at?

The file's address in the report tells you who built it, and that decides what you can do:

  • Your own bundle: a theme or app your developer builds. You change one build setting and rebuild (Your own build).
  • A plugin's script: on WordPress, the folder after /wp-content/plugins/ in the address is the plugin. Lighthouse lists MonsterInsights' frontend-gtag.min.js, the Stripe gateway for WooCommerce on product and checkout pages, and date pickers in form plugins (flatpickr in WPForms, Ninja Forms and Elementor). wp-polyfill.min.js is a WordPress file that loads when a plugin asks for it: WooCommerce's blocks or Contact Form 7 with reCAPTCHA switched on (WordPress).
  • A vendor's tag, listed under the company's name: Meta Pixel (fbevents.js), the OneTrust cookie banner, HubSpot's form and feedback scripts, and the beacon of Cloudflare Web Analytics, which Cloudflare adds to your pages itself. Google Tag Manager and gtag.js are not listed. Only the vendor can rebuild these files: keep the tag or remove it. You switch off Cloudflare Web Analytics in your Cloudflare dashboard.
  • Next.js: every Next.js site has a script listed with Next.js's own polyfills, lines such as Array.prototype.at and Object.hasOwn.
Lighthouse report, Legacy JavaScript in orange with Est savings of 11 KiB: only Facebook's fbevents.js from connect.facebook.net with 10.9 KiB and eight lines, the three Babel transforms and Array.from, filter, find, map and String.prototype.startsWith

The ferry page with the build for current browsers and a Meta Pixel added: only Facebook's file is left.

How do you fix Legacy JavaScript?

Start with the largest file you control, then the plugin or app that loads old code on every page.

WordPress

Update WordPress and the plugin first: a new version may be built for current browsers. Then, for each listed file:

  • A plugin you don't need on every page: a form plugin's date picker or a payment gateway's script belongs on the pages that use it. Unload it everywhere else with Asset CleanUp (menu "Asset CleanUp", "CSS & JS Manager") or the Script Manager in Perfmatters.
  • A plugin with an alternative: if an analytics plugin such as MonsterInsights loads its own listed script on every page, switch to one that only adds Google's own tag, such as Site Kit by Google.
  • wp-polyfill: the Query Monitor plugin lists the scripts that depend on it. Find the plugin behind them and load that plugin only where it is used, such as Contact Form 7's reCAPTCHA on the contact page. Don't unload wp-polyfill itself in those tools or deregister it in code: WordPress then silently drops every script that needs it, and on a WooCommerce store that is the mini-cart, the cart and the checkout.

"Delay JavaScript execution" in WP Rocket clears the notice, but the visitor still downloads and runs the same code, on the first tap or scroll.

Shopify

A listed file comes from an app, a tag pasted into the theme or a custom theme's own scripts. Update the app or ask its vendor for a current build, remove the apps you no longer use, and send the developer of a custom theme to the build setting.

Next.js

Next.js builds your own code for current browsers by default, and for the browserslist in package.json if you set one. No setting removes the polyfills Next.js adds itself. Leave that line.

Your own build

Tell your build which browsers to support and stop adding polyfills. Baseline Widely available is the set of features every major browser has supported for at least 30 months. Worldwide about 0.2% of visitors use a browser that can't run classes or async functions; check your own analytics for old iPhones stuck on iOS 15 or 16 before you switch. In package.json:

"browserslist": [
  "baseline widely available"
]

This needs browserslist 4.26 or newer. With Babel 7, remove useBuiltIns, corejs and targets from @babel/preset-env, and the line import "core-js/stable" if your entry file has it:

// babel.config.js
module.exports = {
  presets: ['@babel/preset-env']
};

Without targets, Babel reads the browserslist in package.json: it then only rewrites syntax those browsers lack, and adds no polyfills. Babel 8 dropped useBuiltIns and corejs, and Vite, esbuild and webpack add no polyfills themselves: in a webpack build they come from babel-loader, so the Babel change covers it. Vite also builds for Baseline Widely available by default.

A line that stays after the rebuild comes from a package in node_modules with its own old build: update or replace that package.

Then run Lighthouse again, in the same way as before: it reads "No legacy JavaScript found", or lists only files you don't build.

A script your page doesn't use at all is a different notice: Reduce unused JavaScript.

When can you leave Legacy JavaScript in the report?

Leave it when the listed lines are Next.js's own polyfills, or when the file is a vendor's tag you need and can't replace, as long as the notice is orange. Fix it when the file is your own bundle with tens of KiB, or when the notice is red: then the file downloads together with your LCP element, and a smaller file lets that element arrive sooner. A vendor's tag you can't make smaller can start after your LCP element has loaded instead: ways to load a script later.

A site with ten vendor tags and twenty plugins has a different question: which of those scripts each page needs at all. In a power hour we open the report of your own pages and settle which listed scripts stay, and on which pages.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Need a faster site?

I'm Arjen, and I've been fixing slow sites for 17 years. If you're running into issues you can't fix yourself, send me your URL and let's have a chat.

Send me your URL
Arjen Karel
Arjen Karel, Web performance consultant since 2009

Why isn't our fix working?

Sometimes the CDN still serves the old HTML, or the fix went into the desktop template and mobile uses another one. Send me your URL and the problem, and I reply with a few times. I check your field data first, then we spend one hour on your site, and within a day you have my notes in writing. €350 excl. VAT.

Send me your URL

No developer to build the fix? I do the work myself, on a copy of your site.

Arjen KarelCore Web Vitals still red on mobile?Send me your URL