Skip to content

experimental.inlineCss duplicates the full CSS bundle into every RSC/segment payload (standalone build-output bloat) #95141

Description

@mikaelh

Link to the code that reproduces this issue

https://github.com/mikaelh/next-inlinecss-rsc-dup-repro

To Reproduce

  1. npm install (pulls next@canary) then npm run build on the linked repo. It is a minimal App Router app: output: "standalone", experimental.inlineCss: true, a 155 KB global stylesheet imported in the root layout, and one [slug] route with generateStaticParams → 60 tiny static pages.
  2. Inspect .next/standalone/.next/server/app, e.g. grep -rl '\.u1000{' .next/standalone/.next/server/app | wc -l and count occurrences per file.

Current vs. Expected behavior

Current (verified on next@16.3.0-canary.66): with inlineCss, the whole stylesheet is inlined into each page's HTML <style> and also serialized into the page's RSC payloads — the inline flight inside the .html, the page.rsc, and each .segments/*.segment.rsc.

Observed in the repro for one trivial static page (a1.html, 411 KB for "tiny content"): 1 <style> block, but the CSS marker rule appears (<style> + flight). Across the build the 155 KB CSS is embedded in 62 .html, 186 .rsc (of which 124 are *.segments/*.segment.rsc) → .next/server/app = 69 MB for 60 pages of "tiny content". So each prerendered page stores the stylesheet ~3–4× (HTML <style> + HTML flight + page.rsc + each segment.rsc); build output scales with CSS_size × pages × ~4.

Amplifier: moving the @import from the root layout into a nested layout (route group) makes the per-segment copies multiply (~2× → ~5× per page in a larger app), so per-route CSS scoping is counter-productive under inlineCss. On a real content site (~700 prerendered pages, 172 KB CSS) the duplicated CSS is ~205 MB of a 453 MB server/app; moving CSS to a nested layout took server/app from 453 MB to 868 MB.

Expected: inlined CSS referenced/deduplicated once per prerendered page rather than copied into every RSC/segment payload. The segment-cache / client-navigation RSC payloads don't need the full stylesheet embedded — on client navigation the browser already has the styles applied. Keep inlining into the initial HTML (the FCP/LCP win) without duplicating across the RSC.

Provide environment information

Operating System: Linux
Binaries:
  Node: 24.x
Relevant Packages:
  next: 16.3.0-canary.66
  react: 19.2.0
  react-dom: 19.2.0
Next.js Config:
  output: "standalone"
  experimental.inlineCss: true

Which area(s) are affected? (Select all that apply)

Output (Standalone), Partial Prerendering (PPR)

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

inlineCss is otherwise a real FCP/LCP win, so disabling it is an unwanted trade-off — the request is to keep the initial-HTML inline but stop duplicating the bundle across the RSC/segment payloads. Measured via du + counting the inlined CSS chunk across .html / .rsc / .segments/*.segment.rsc. (Supersedes #95140, which was auto-closed for a parsing issue — the reproduction link is now in the correct field.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    OutputRelated to the the output configuration option.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions