Skip to content

fix(rsc): reuse server css resources element across renders - #1534

Open
james-elicx wants to merge 3 commits into
vitejs:mainfrom
james-elicx:rsc-memoize-css-resources
Open

james-elicx wants to merge 3 commits into
vitejs:mainfrom
james-elicx:rsc-memoize-css-resources

Conversation

@james-elicx

Copy link
Copy Markdown
Contributor

Description

rscCssTransform wraps every exported server component in a CSS-importing module so that each render emits import.meta.viteRsc.loadCss(). The generated Resources component builds new <link> elements on every call, so React Flight serializes the full set of links again for every instance. React dedupes the stylesheets in the DOM, but the RSC payload (and the inlined payload in the SSR HTML) grows linearly with the number of instances.

We hit this in a real app: about 91 kB of a page's payload was the same 7 stylesheet links, repeated 398 times, once per instance of a server component that imports CSS.

deps is static for each virtual:vite-rsc/css? module, so this PR creates the element once and returns the same object from Resources. Flight already serializes an element it has written before as a short back-reference (e.g. "$0:props:children:0:props:children:0"), so every instance after the first costs a few bytes instead of the full link list.

Every instance still carries its links (as a reference), so stylesheet hoisting, precedence ordering and Suspense behaviour are unchanged. Flight tracks written objects per request, so sharing the element across requests is safe. In dev the virtual module is regenerated when its CSS changes, so the cached element can't go stale.

For scale, a standalone Flight render of 398 instances with 7 links each:

payload
before 594 kB
after 50 kB
without any links 19 kB

The new e2e test renders a CSS-importing server component N times (?test-style-server-repeated=N) and checks that the number of data-rsc-css-href entries in the RSC payload is the same for N=1 and N=5, and that every instance is styled after hydration. It fails on main in both dev and build (20 vs 24 and 9 vs 13) and passes with this change.

@james-elicx
james-elicx marked this pull request as ready for review October 28, 2026 17:54

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant