Serve Modern JavaScript to Modern Browsers: How to Fix It

No Comments
Serve modern javascript to modern browsers: how to fix it

Serve Modern JavaScript to Modern Browsers: How to Fix It

TL;DR — This audit fires when you ship transpiled, polyfilled ES5 to browsers that natively understand ES6+. Every modern browser then downloads and runs legacy shims and verbose down-compiled code it never needed, paying in extra bytes and extra parse-and-execute time for compatibility that stopped mattering years ago.

What this check flags

Lighthouse detects transforms and polyfills in your bundle that only exist to support browsers your real audience isn't using. Down-compiling async/await, arrow functions, classes, and spread syntax to ES5 makes code balloon — a compact modern feature becomes a pile of helper functions and regenerator runtime. Then polyfills for Promise, fetch, and array methods pile on top. Browsers that support all of it natively still pay the full tax.

A real example, and the fix

A default Babel config targeting ancient browsers turns this:

const load = async (url) => (await fetch(url)).json();

into dozens of lines of regenerator runtime and helper wrappers, plus a bundled Promise and fetch polyfill. The fix is the module/nomodule pattern: ship untouched modern code to browsers that speak ES modules, and a legacy build only to those that don't.

<!-- Modern browsers load this, ignore nomodule -->
<script type="module" src="/app.modern.js"></script>

<!-- Legacy browsers load this, ignore type=module -->
<script nomodule src="/app.legacy.js"></script>

Any browser that understands type="module" automatically skips the nomodule script, and vice versa — so modern users get lean ES6+ and no legacy build ever touches their machine.

What legacy transpilation actually costs

The tax isn't just bigger downloads — it lands across bytes, parse time, and execution. Here's what a modern browser pays for a legacy-only build it never needed.

Overhead sourceWhat it addsRemoved by serving modern JS
Syntax down-compilationVerbose ES5 for classes, async, spreadYes — modern syntax ships as-is
Regenerator runtimeHelper library for transpiled async/generatorsYes — native async needs none
Core-js polyfillsShims for Promise, fetch, Array methodsYes — features exist natively
Helper duplication_classCallCheck, _extends, etc.Yes — no transform, no helpers

How to detect it

  1. Lighthouse / PageSpeed Insights: open "Avoid serving legacy JavaScript to modern browsers" under Diagnostics. It names the specific polyfills and transforms it found and estimates the savings from dropping them.
  2. DevTools Coverage tab: record load coverage and look at your main bundle. Big blocks of never-executed helper and polyfill code on a modern browser are exactly the legacy weight this audit is about.
  3. Search your bundle: grep the built output for tells like regeneratorRuntime, _classCallCheck, or core-js. Finding them in the bundle a modern browser downloads confirms you're over-transpiling.

How to fix it

  1. Set a realistic browserslist. Target something like "defaults", "not dead" instead of ancient IE. Babel's preset-env reads browserslist, so a modern target alone strips a huge amount of transpilation.
  2. Ship differential builds. Use the module/nomodule pattern (or a plugin like @vitejs/plugin-legacy) so modern browsers get the ES6+ build and only true legacy clients get the fallback.
  3. Polyfill on demand, not blanket. Configure core-js with useBuiltIns: "usage" so only the polyfills your code actually needs are included — and only in the legacy build.
  4. Audit dependencies. A single library shipping its own polyfills can re-introduce the weight you removed. Prefer packages that publish modern ES-module builds.
  5. Verify after building. Re-run Lighthouse and confirm the legacy warning is gone on a modern user-agent before you call it done.

How this differs from removing unused JavaScript

These sit next to each other but target different waste. Remove unused JavaScript is about code your bundle contains but never executes on this page — a bundling and code-splitting problem. This audit is about code that does run but shouldn't exist for your audience: transpilation and polyfills serving browser versions nobody uses. You can have zero unused JS and still fail this one because every byte you ship is bloated ES5. It also stacks with Minify JavaScript (which compresses what you keep) and with the broader tree shaking discipline. For how Lighthouse scores all of this together, see the guide to Lighthouse performance metrics.

FAQ

Won't dropping legacy support break old browsers?

No — the module/nomodule pattern keeps a legacy build for browsers that need it. Modern browsers just stop downloading it. You lose nothing except the bytes modern users were wasting.

How do I know which browsers to target?

Check your own analytics, not a global default. If real traffic from ancient browsers is a rounding error, target modern defaults and serve the legacy build only as an ignored fallback.

Is this the same as using ES modules?

Related but distinct. ES modules enable the delivery mechanism (module/nomodule and native imports), but the win here is not transpiling and not polyfilling for browsers that don't need it. You can use modules and still over-polyfill if your config is wrong.

Does modern JavaScript run faster, or just weigh less?

Both. Native syntax skips helper wrappers and regenerator runtime, so there's less to parse and execute — not only fewer bytes on the wire but less main-thread work at boot.

My framework build tool controls this — where do I change it?

In your browserslist config and the framework's legacy plugin settings. Vite, Next.js, and similar tools ship modern output by default now; the legacy weight usually creeps back in through an over-broad browserslist or a dependency's own polyfills.

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog