afaqrashid.com  ·  portfolio · 2026 ed.

Next.js Legacy JavaScript in Lighthouse: What I Tested

Run Lighthouse on a Next.js site and the performance report lists "Legacy JavaScript" with an estimated saving of about 14 KiB. You get it on a fresh app with one button. Adding a browserslist doesn't remove it, and rewriting your own code doesn't either, because the code it flags isn't yours.

I tested this on Next.js 16.4.4 with next build and next start, using Lighthouse 12.8.2 from the command line, and read the audit from the JSON report.

What Lighthouse reports

legacy-javascript-insight   Est savings of 14 KiB
  /_next/static/chunks/34-s9nvef5sdp.js
    Array.prototype.at
    Array.prototype.flat
    Array.prototype.flatMap
    Object.fromEntries
    Object.hasOwn
    String.prototype.trimEnd
    String.prototype.trimStart

My first test page used .at(), .flat(), Object.fromEntries and replaceAll in a client component. I removed all of them and rebuilt. The report was the same, the flagged chunk had the same file name, and the estimate went from 13 KiB to 14 KiB.

Where it comes from

The flagged chunk starts with a set of checks like "trimStart" in String.prototype || (String.prototype.trimStart = ...). That is Next.js's own polyfill module. In node_modules/next/dist/client/app-globals.js the App Router imports it with no condition:

// imports polyfill from `@next/polyfill-module` after build.
import '../build/polyfills/polyfill-module';

Each polyfill only runs if the browser lacks the method, so in a current browser it does nothing except get downloaded and parsed.

browserslist doesn't remove it

"browserslist": ["chrome 120", "firefox 120", "safari 17"]

With this in package.json, every method in the list is supported natively by every target browser. I rebuilt, and Lighthouse reported the same seven polyfills and the same 14 KiB. browserslist changes how your code is compiled. The Next.js import above is not compiled for your targets, it is always included.

The real size is 614 bytes

polyfill-module.js            1,380 bytes
polyfill-module.js, gzipped     614 bytes
Lighthouse estimate          13,909 bytes

The 14 KiB is Lighthouse's estimate for those polyfills, not the bytes in your build. The whole file is 1,380 bytes, or 614 bytes gzipped.

It doesn't change your score

In the same report, both legacy-javascript and legacy-javascript-insight had a weight of 0 in the performance category. The score is computed from five metrics: First Contentful Paint (10), Largest Contentful Paint (25), Total Blocking Time (30), Cumulative Layout Shift (25) and Speed Index (10). The test app scored 100 with the warning showing.

This site shows the same warning. On the RSS feed post, Lighthouse estimated 14 KiB of legacy JavaScript, and the page scored 97 with LCP 2.1 s, TBT 20 ms and CLS 0.

Can you remove it?

Yes, with an alias. This replaced the module with an empty file under Turbopack:

// next.config.ts
const nextConfig: NextConfig = {
  turbopack: {
    resolveAlias: { "../build/polyfills/polyfill-module": "./empty-polyfill.js" },
  },
};

After a rebuild, no chunk contained the polyfills, Lighthouse showed no legacy JavaScript, the page still worked, and the score was still 100.

I don't use it. It saves 614 bytes, it depends on a relative import path inside Next.js that can change in any release, and it removes polyfills Next.js expects to be there. If an older browser still visits, a missing method breaks the page instead of just loading a little slower.

What to do instead

Leave the warning, and check that the flagged chunk is the one with the polyfill module. If the audit lists a different chunk, or polyfills for features like Promise or Symbol in your own code or a dependency, those come from your build and browserslist is the place to fix them. Spend the time on what the score is made of: LCP, TBT and CLS.

npx lighthouse https://your-site.com --only-categories=performance \
  --output=json --output-path=report.json --chrome-flags="--headless=new"
node -e 'const r=require("./report.json");
const a=r.audits["legacy-javascript-insight"];
console.log(a.displayValue, a.details.items.map(i => i.url))'

Related

Sources