Turbopack can change both the size of your CSS and the stylesheets each route loads. Compare webpack and Turbopack builds from the same commit, then check browser-target rewrites, Sass-generated utilities, and CSS chunking before changing your source.
The optimize-turbopack-css skill guides your coding agent through those measurements and targeted fixes. Its workflow includes checks for browser compatibility, right-to-left (RTL) layouts, and styles used in content management system (CMS) output.
Turbopack is the default bundler in Next.js 16. If you're also upgrading Next.js, follow the upgrade guide and check the known differences from webpack.
Copy link to headingWhich apps are most likely to see larger CSS?
Pay particular attention to apps that generate utility classes with Sass loops, target older browsers, support RTL layouts, or use a shared CMS component registry. These patterns can increase generated CSS or the styles a route loads.
Tailwind CSS generates utilities from detected or explicitly included classes, which can limit the growth seen in Sass loops that emit every combination. This also applies to shadcn/ui projects using Tailwind. Older browser targets can still expand logical utilities such as ms-*, me-*, ps-*, and pe-*.
Tailwind v4 has its own browser requirements. Setting an older Browserslist does not establish support below those versions. CSS chunking can also load unused styles, so measure per-route output even when logical properties stay unchanged.
Copy link to headingWhy does CSS get bigger after moving to Turbopack?
Turbopack handles CSS differently from webpack in four ways. Each one can add bytes on its own or multiply the cost of another.
Copy link to headingTurbopack rewrites modern CSS for older browsers
Turbopack compiles stylesheets with Lightning CSS, which uses your browser targets to decide which supported CSS features to rewrite, or "lower." Next.js's default webpack pipeline uses PostCSS for vendor prefixes and selected syntax transforms. Custom PostCSS plugins or webpack's useLightningcss option can change the comparison.
Logical properties such as margin-inline-start adapt spacing to text direction and can expand into many fallback selectors. With Next.js 15.5's default targets, this 31-byte utility expands to six rules totaling 1,401 bytes. The output excerpt shortens the language list:
CSS excerpt with shortened language list
This logical-property rewrite can change behavior as well as size. The added pseudo-classes increase specificity, so generated rules can override declarations that previously took precedence.
When the targets also lack :dir() support, Lightning CSS approximates direction with :lang() selectors. Changing dir without a corresponding language change can then leave the spacing pointing the wrong way.
Next.js 15.5's default targets include browsers as old as Chrome 64 and Safari 12, which trigger this fallback chain. Next.js 16 raised its default targets, so most apps using those defaults avoid logical-property lowering. Custom Browserslist settings can still trigger it, and the Next.js 16 defaults still lower some newer features, such as light-dark() and oklch() colors.
Copy link to headingSass loops and per-file minification multiply the cost
Sass generators can repeat logical-property rewrites across every breakpoint and size they emit. Even a small loop can therefore produce a large stylesheet.
In this article's reproduction, a 704-byte Sass loop emitted 144 logical spacing utilities. The simplified version below shows how it combines breakpoints, logical properties, and spacer sizes:
With Next.js 15 targets, the webpack output was about 12 KB and the Turbopack output about 205 KB, a 17.7-fold increase. Gzip output grew 5.3-fold. Compression reduces transfer size, but browsers still process the expanded CSS.
| Build | Rules | Raw | Gzip |
|---|---|---|---|
| webpack, Next.js 15 targets | 144 | 11.6 KB | 0.8 KB |
| Turbopack, Next.js 15 targets | 864 | 205.4 KB | 4.1 KB |
Shared Sass partials add a second multiplier. When several CSS Modules load a partial that emits CSS, each separately compiled module includes that output. Webpack minifies the combined output of each CSS chunk and can remove identical rules within it. Turbopack minifies the modules separately before combining them, so repeated rules can survive across modules.
For example, this partial mixes a variable with a rule that emits CSS:
This CSS Module imports the partial for its font variable:
Every CSS Module that loads _fonts.scss emits a copy of the @font-face rule. Move @font-face rules into a stylesheet that loads once. Keep shared partials to variables, functions, and mixin definitions that emit no CSS when loaded.
Copy link to headingClass name length with CSS Modules
Through Next.js 16.3, Turbopack uses longer CSS Module class names than webpack, such as Details-module-scss-module__MGoXJG__label for a Sass module.
These names repeat in your CSS, in every class attribute of server-rendered HTML, and in the React Server Components (RSC) payload, adding raw bytes that compression mostly hides. Next.js 16.4 shortens these names in production builds.
Copy link to headingTurbopack splits and delivers CSS differently
Turbopack preserves stylesheet order when grouping CSS into shared chunks because order can affect the cascade. The grouping makes a trade-off between request count and download size, so routes can receive styles they don't use.
Next.js 16.3 added experimental.cssChunking: 'graph', an experimental Turbopack-only option that changes this grouping. Compare route bytes and request counts before adopting it. From that version, Turbopack rejects the webpack-only values 'strict' and false, so remove either setting if it remains in your configuration.
Statically imported component registries, error boundaries, and not-found pages can also attach their CSS to routes that never render them. Some of these behaviors also happen under webpack, but a migration is often when teams first measure them. As a result, total CSS can shrink after a migration while individual pages get heavier.
Copy link to headingOptimizing Turbopack CSS
The optimize-turbopack-css skill includes a diagnostic workflow, notes on Next.js 15 and 16, and a dependency-free analyzer. Use it to investigate the causes that apply to your app and measure each change separately. You can request analysis only, a plan, or changes one phase at a time.
Copy link to heading1. Establish comparable baselines
Build webpack and Turbopack from the same commit with matching dependencies, environment, browser targets, and production settings. Preserve each build's output separately so the next build doesn't overwrite your baseline.
Compare the CSS each route loads alongside the build totals. Use route manifests to identify associated assets, then check actual CSS requests during cold browser navigations. Manifest entries alone don't establish what the browser downloads.
The analyzer counts selectors and transformation markers to help identify lowering. An isolated compilation of the simplified Sass loop above with Lightning CSS 1.33.0 produced this output:
Copy link to heading2. Isolate browser-target rewrites
Run a temporary build with modern browser targets to test their effect on output. These POSIX shell commands override Browserslist for one build without editing your configuration. Treat the result as diagnostic output until any change to supported browsers has been approved.
Then inspect Sass generators and shared partials, global framework styles, route delivery, and CSS Module class names. Selector markers help distinguish lowering from other repetitive output.
Copy link to heading3. Change one cause at a time
Inventory class names used by CMS content or third-party HTML before removing selectors. Measure each fix against its baseline. Typical changes restrict Sass loops to needed utilities, keep shared partials free of emitted rules, and move feature CSS to the routes that use it.
On Next.js 16.2 and later, experimental.lightningCssFeatures can exclude specific rewrites. The following next.config.ts settings remove the compatibility fallbacks for logical properties and :dir() selectors. Use them only if the affected CSS works in your supported browsers, either natively or through fallbacks you supply.
Copy link to heading4. Validate the rendered result
Check cascade order and the layouts your app supports, including RTL, localized content, and print styles where applicable. Exercise CMS content and runtime-generated class names. Add per-route CSS budgets to continuous integration (CI) to detect later growth.
Copy link to headingCSS reductions in two production apps
We used the workflow with two enterprise teams. The marketing site needed to reduce the CSS delivered to individual routes. The commerce app needed to reduce repetitive fallback rules generated from Sass utilities.
Copy link to headingMarketing site: pages loaded CSS they didn't use
The first project was a CMS-driven marketing site on Next.js 16.3. Total CSS dropped from 462 KiB under webpack to 360 KiB under Turbopack. The median route still loaded about 340 KiB, including styles for blocks it never rendered.
We traced the excess route CSS to a registry that imported every CMS block component statically. The investigation also found a shared Sass partial that emitted @font-face rules into every CSS Module that imported it.
The team made four changes:
- The registry loads blocks through
next/dynamicboundaries with server rendering enabled. In this app, the change reduced block CSS loaded by routes that did not render those blocks. - The
@font-facerules moved from the shared Sass partial into one global stylesheet. - The team enabled
experimental.cssChunking: 'graph'to reduce unused CSS delivered across routes further. - Splitting block styles exposed two components that depended on stylesheet order. Explicit component overrides fixed both.
| Initial route CSS | Turbopack baseline | Optimized Turbopack | Reduction |
|---|---|---|---|
| Homepage, raw | 250.5 KiB | 139.4 KiB | 44% |
| Homepage, gzip | 33.5 KiB | 23.1 KiB | 31% |
| Median of 290 routes, raw | 339.8 KiB | 111.4 KiB | 67% |
| Median of 290 routes, gzip | 51.3 KiB | 18.4 KiB | 64% |
No measured route got larger in raw, gzip, or Brotli bytes. The trade-off was more, smaller requests, with the homepage's initial stylesheets growing from 4 to 9.
Copy link to headingCommerce monorepo: Sass generators multiplied by lowering
The second project was an enterprise commerce monorepo on Next.js 15.5. It combines Bootstrap 5.3 with an in-house utility library and supports many locales with dedicated fonts. It also switches text direction at runtime and renders CMS HTML from outside the repository.
Raw CSS grew 134% on the first Turbopack build, while gzip grew only 8% and Brotli shrank 3%. That gap suggested repetitive output. Inspecting the emitted CSS identified logical-property fallbacks generated from the app's Sass utilities.
The team measured each change separately:
- Moving Browserslist to the Next.js 16 defaults while remaining on Next.js 15.5 reduced raw CSS by 53%. The team treated this as a separate browser-support decision requiring explicit approval.
- The breakpoint-by-size matrix of font utilities became one non-responsive set. The Sass guard now blocks new breakpoint copies.
- Bootstrap's utility API took over spacing, remapped to logical properties for runtime direction switching. The team grouped language selectors and changed components to import token-only Sass entry points.
- The root error boundary became self-contained, and web-component registration moved from the root layout to the localized layout.
| Build (all CSS) | Raw | Gzip | Brotli |
|---|---|---|---|
| webpack baseline | 1,534 KB | 247 KB | 198 KB |
| First Turbopack build | 3,595 KB | 266 KB | 192 KB |
| Turbopack with Next.js 16 browser targets | 1,673 KB | 216 KB | 170 KB |
| Final optimized Turbopack | 1,424 KB | 203 KB | 168 KB |
The final build is 7% smaller than the webpack baseline in raw bytes, 18% smaller in gzip, and 15% smaller in Brotli.
Before pruning utilities, the team created a safelist of class patterns used by CMS HTML. They committed the safelist with per-route CSS budgets and Sass contract tests to detect regressions as the app changes.
Copy link to headingTry the skill on your app
The skill is available on its development branch. With Node.js 22.20 or later, run this command from your project and choose the coding agent to install it for:
For an analysis-only review, give your agent this prompt:
Our CSS grew after switching to Turbopack. Use the optimize-turbopack-css skill to compare webpack and Turbopack builds from the same commit, including total and per-route CSS sizes in raw and gzip bytes. Identify the main causes with evidence, then recommend targeted fixes and note browser-compatibility or layout risks. Flag any measurements you cannot verify. Analyze without changing source files, configuration, or the browsers we support.