You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-rsc: cross-origin stylesheet resources omit crossOrigin and can poison browser CORS cache #1464
With plugin-rsc 0.5.34, both generated Resources stylesheet links and SSR ReactDOM.preinit(href, { as: 'style' }) omit crossOrigin. Cross-origin CSS can consequently be cached from a non-CORS request and fail a later CORS fetch of the same URL.
The omission also appears in main at df9f995 (src/plugin.ts generateResourcesCode and src/ssr/shared.ts preloadDeps). I did not find a supported option controlling these attributes; cssLinkPrecedence does not address request mode.
Node 26, Vinext 1.0.0-beta.9, plugin-rsc 0.5.34, Vite 8.3.0, React 19.3.0. The fixture builds a tiny real App Router application and serves its generated assets from a second local origin. No service worker or application recovery code is present. It models an asset origin that returns ACAO and Vary: Origin only when an Origin header is sent (observed with R2), with immutable cache headers.
Initial server-imported and client-component stylesheets load through links without crossorigin.
Fetch those same URLs from JavaScript with default CORS mode.
Chromium reuses the non-CORS response and rejects it because ACAO is missing.
A cache-reload CORS fetch succeeds. This is not a missing allowlist or missing asset.
Expected / proposed correction
Consistent anonymous CORS for CSS resource generation, matching Vite's ordinary HTML stylesheet generation and RSC's adjacent module preload behavior:
crossOrigin: 'anonymous' in the generated Resources link props.
crossOrigin: 'anonymous' in SSR CSS preinit options.
A version-checked local patch to these paths passes initial rendering, navigation and warm-cache checks without a worker. The fixture also uncovered a separate Vinext next/dynamic preload omission, which is being reported there independently.
Happy to adjust the proposed correction if the intended contract is a configurable crossOrigin policy rather than a default. The reproduction intentionally distinguishes origin behavior from framework request consistency.
It sounds reasonable to have crossOrigin: 'anonymous' but I thought React may not support it (or it was so at that time). I think aligning crossOrigin is good if possible.
On second thought, I have no idea how Vite/RSC app would realistically try to fetch 2nd stylesheet. From what I understand, Vite client doesn't try to fetch if link is already there regardless of crossOrigin.
I'm inclined to fix regardless, but would be nice to have realistic repro other than surgically crafting fetch(link).
Hello @jaden-chen. Please provide a minimal reproduction using a GitHub repository or StackBlitz. Issues marked with need reproduction will be closed if they have no activity within 3 days.
Thanks — you're right that the original gist's explicit fetch(link.href) did not demonstrate a normal application trigger. I reduced this to a full-document navigation followed by a click-triggered lazy import:
/ server-renders a client component that imports card.css. The generated stylesheet link has no crossorigin; Chromium caches the non-CORS response.
Follow a plain <a href="/second"> link. This creates a new document, initially without that component or a stylesheet link.
Click Show card, which renders React.lazy(() => import('../card')). Vite's dynamic-import helper creates an anonymous-CORS stylesheet link for the same CSS URL. The previous document's link is gone, so the existing-link check cannot deduplicate it.
Chromium reuses the cached response without ACAO, reports a CSS CORS failure, and the page error boundary receives Unable to preload CSS.
No application CSS fetch(), service worker, next/dynamic, or manual stylesheet insertion/removal is involved. The asset fixture retains the original report's condition: immutable caching, ACAO + Vary: Origin only when the request carries Origin.
Verified with the pinned original versions in Chromium:
Fresh context → /second → Show card: passes.
Fresh context → / → Second page → Show card: fails unpatched.
The automated check asserts matching CSS URLs and only one CSS request reaching the asset server in the warm sequence. npm test asserts the unpatched failure; npm run test:fixed asserts success with those two changes. Manual steps are in the README.
This establishes a realistic second request across documents; I haven't established that this exact sequence caused the original application incident, and I'm not claiming same-document navigation with an existing link fails.
Clarifying the navigation condition: this reproduction crosses a document boundary, rather than using ordinary same-document client-side navigation. Your point about Vite skipping a stylesheet link that already exists in the current document applies; this example deliberately exercises a new document where that link is absent, while the browser HTTP cache remains available.
I've updated the repository to keep two tested variants:
/second: <Link href="/second" target="_blank" prefetch={false}> from next/link, opening a new tab. The first tab remains open with its original stylesheet.
/third: a plain <a href="/third">, loading a new document in the same tab.
Both destinations initially omit the card and use the same click-triggered React.lazy loader. Both variants reproduce the CSS CORS failure on the unpatched versions and pass with only #1469's two runtime changes. Cold direct visits to either destination pass. The tests also assert an actual document request for each navigation and that the second CSS attempt does not reach the asset server.
So bypassing same-document client-side navigation is a prerequisite of these demonstrated scenarios. This is not evidence that ordinary client-side navigation with an existing stylesheet link fails, nor a claim that these are the only possible triggers. Opening a standard framework Link in a new tab provides a normal user action that reaches the same condition without requiring a plain-anchor-only application.
Activity
hi-ogawa commentedon Sep 24, 2026
It sounds reasonable to have
crossOrigin: 'anonymous'but I thought React may not support it (or it was so at that time). I think aligningcrossOriginis good if possible.hi-ogawa commentedon Sep 24, 2026
On second thought, I have no idea how Vite/RSC app would realistically try to fetch 2nd stylesheet. From what I understand, Vite client doesn't try to fetch if link is already there regardless of
crossOrigin.I'm inclined to fix regardless, but would be nice to have realistic repro other than surgically crafting
fetch(link).github-actions commentedon Sep 24, 2026
Hello @jaden-chen. Please provide a minimal reproduction using a GitHub repository or StackBlitz. Issues marked with
need reproductionwill be closed if they have no activity within 3 days.jaden-chen commentedon Sep 24, 2026
Thanks — you're right that the original gist's explicit
fetch(link.href)did not demonstrate a normal application trigger. I reduced this to a full-document navigation followed by a click-triggered lazy import:https://github.com/jaden-chen/vite-rsc-css-cors-reproduction
/server-renders a client component that importscard.css. The generated stylesheet link has nocrossorigin; Chromium caches the non-CORS response.<a href="/second">link. This creates a new document, initially without that component or a stylesheet link.React.lazy(() => import('../card')). Vite's dynamic-import helper creates an anonymous-CORS stylesheet link for the same CSS URL. The previous document's link is gone, so the existing-link check cannot deduplicate it.Unable to preload CSS.No application CSS
fetch(), service worker,next/dynamic, or manual stylesheet insertion/removal is involved. The asset fixture retains the original report's condition: immutable caching, ACAO +Vary: Originonly when the request carries Origin.Verified with the pinned original versions in Chromium:
/second→ Show card: passes./→ Second page → Show card: fails unpatched.crossOrigin: anonymousfor css #1469: passes.The automated check asserts matching CSS URLs and only one CSS request reaching the asset server in the warm sequence.
npm testasserts the unpatched failure;npm run test:fixedasserts success with those two changes. Manual steps are in the README.This establishes a realistic second request across documents; I haven't established that this exact sequence caused the original application incident, and I'm not claiming same-document navigation with an existing link fails.
jaden-chen commentedon Sep 24, 2026
Clarifying the navigation condition: this reproduction crosses a document boundary, rather than using ordinary same-document client-side navigation. Your point about Vite skipping a stylesheet link that already exists in the current document applies; this example deliberately exercises a new document where that link is absent, while the browser HTTP cache remains available.
I've updated the repository to keep two tested variants:
/second:<Link href="/second" target="_blank" prefetch={false}>fromnext/link, opening a new tab. The first tab remains open with its original stylesheet./third: a plain<a href="/third">, loading a new document in the same tab.Both destinations initially omit the card and use the same click-triggered React.lazy loader. Both variants reproduce the CSS CORS failure on the unpatched versions and pass with only #1469's two runtime changes. Cold direct visits to either destination pass. The tests also assert an actual document request for each navigation and that the second CSS attempt does not reach the asset server.
So bypassing same-document client-side navigation is a prerequisite of these demonstrated scenarios. This is not evidence that ordinary client-side navigation with an existing stylesheet link fails, nor a claim that these are the only possible triggers. Opening a standard framework Link in a new tab provides a normal user action that reaches the same condition without requiring a plain-anchor-only application.
Updated reproduction: jaden-chen/vite-rsc-css-cors-reproduction@a688ed5