deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Next.js 16 test shows Browserslist targets shape CSS but not modern JavaScript

A three-project experiment found Next.js's SWC compiler treats all modern Browserslist targets identically for JavaScript, while Lightning CSS applies them per feature in CSS output.

Next.js 16 test shows Browserslist targets shape CSS but not modern JavaScript

An experiment posted on dev.to by Alessandro Grosselle set out to answer a question many Next.js developers have likely wondered about: does setting a browserslist target in package. actually change the build? The author created three near-identical Next.js 16 projects, compiled them, and compared the results byte by byte. The short version: browserslist has almost no effect on modern JavaScript output, while it meaningfully shapes CSS.

How the test was run

The three projects differed only in their browserslist field. One targeted Internet Explorer 11, one targeted Chrome 116 (about three years old), and one targeted Chrome 139 (about one year old). Each project compiled the same TypeScript function, deliberately written to exercise a spread of language features: logical assignment from ES2021, Array.prototype.at and Object.hasOwn from ES2022, optional chaining and nullish coalescing from ES2020, structuredClone, Object.groupBy from ES2024, and Error.isError, an ES2026 method that, according to the article, had not shipped in real browsers at the time of writing.

The setups also included eslint-plugin-compat and a PostCSS configuration with postcss-preset-env, and the test code is published on GitHub as ale-grosselle/nextjs-browserlist-test. Grosselle ran next build in each project and compared the emitted bundles.

JavaScript: modern targets produce identical output

The expectation was three outputs scaled to the age of each target. What came out was two. The IE11 build was genuinely downleveled to ES5: no arrow functions, no let or const, for...of rewritten as a manual iterator loop, and optional chaining and logical assignment replaced with older syntax. SWC, in other words, does real syntax-level transpilation here.

The Chrome 116 and Chrome 139 builds, by contrast, came out identical, with every modern construct left untouched. Choosing an older-but-still-modern Chrome version had zero effect on the emitted JavaScript.

The explanation, per the article, is that SWC does not map browserslist onto a continuum of ECMAScript versions the way a Babel preset-env pipeline would. It collapses every target into one of two buckets: legacy, for browsers without native ES module support such as IE11, and modern, for roughly Chrome 61, Safari 11 or Firefox 60 onward. Both Chrome versions tested sit deep inside the modern bucket, and there is no intermediate tier to separate them. In practice, browserslist influences Next.js JavaScript output only when the oldest target predates ES module support.

Even the legacy build is not runtime-safe

A more sobering finding: the IE11 bundle was transpiled but not polyfilled. The output still calls .at(-1), Object.hasOwn, structuredClone and Error.isError directly, none of which exist in IE11, and one of which does not exist in any browser yet. According to the article, SWC lowers syntax only and never injects API polyfills regardless of the browserslist setting. Teams that genuinely need legacy browsers to run without crashes must bring their own polyfill entry point.

CSS follows the actual targets

CSS tells a more encouraging story, with differences appearing between the two Chrome builds and not just between IE11 and everything else. For IE11 only, a system-ui font stack was expanded into a full fallback list. CSS nesting was flattened for all three targets.

The feature that actually split the Chrome builds was color-scheme: light dark. The Chrome 116 build, like the IE11 build, received a fallback built on custom properties plus a prefers-color-scheme media query, while Chrome 139 kept the plain declaration. The article attributes this to the relevant support landing around Chrome 123, squarely between the two targets.

The reason is architectural: Turbopack's CSS pipeline uses Lightning CSS, which carries per-feature browser support data closer to caniuse or Baseline than to SWC's module-support split. CSS has its own gaps, though. Features with no static fallback path, such as color-mix(), :has() and @container, shipped unchanged to every target, IE11 included.

Why it matters

Many teams set browserslist expecting Babel-era behavior: pick a target, get output trimmed to it. This test shows that in Next.js 16, that expectation effectively holds for CSS and fails for JavaScript unless a target drops below the ES-module line, and even then you get syntax downleveling without polyfills. The practical takeaways are to treat browserslist in Next.js primarily as a CSS-facing control, to not assume that slightly older modern targets buy extra compatibility, and to supply explicit polyfills if legacy browser support is a real requirement rather than a config-file ritual.

  • #next-js
  • #browserslist
  • #javascript
  • #css
  • #swc

Related posts