Client-side CSS prefetch links missing crossOrigin (cross-origin CDN assets fetched twice)
What version of React Router are you using?
8.3.0 (also present on main)
Steps to Reproduce
- Build a framework-mode app whose assets are served from a cross-origin host (a CDN on a different origin than the document), so the manifest/
<Links>needcrossOrigin="anonymous"to fetch CSS/JS with CORS. - Enable link prefetching on a navigation target, e.g.
<Link prefetch="intent" to="/some-route" />, where/some-routepulls in route CSS (route.css) orlinks()-declared stylesheets. - Hover/focus the link to trigger a prefetch and watch the Network panel +
<head>.
Expected Behavior
The imperatively-created preload/prefetch <link> elements for CSS should carry the same crossOrigin as the eventual stylesheet fetch (and as the SSR <Links crossOrigin> output), so the preload is actually reused by the stylesheet load.
Actual Behavior
The client-side CSS prefetch path emits <link rel="preload" as="style"> / <link rel="prefetch" as="style"> with no crossOrigin, so its CORS mode does not match the cross-origin stylesheet fetch. The browser discards the preload as unused and fetches the CSS a second time — wasted bandwidth, a "preloaded but not used" console warning, and a brief flash of unstyled content on navigation.
Background — how this differs from #14678 / #14687
This is the client-side counterpart to #14678, which #14687 fixed only for the SSR path.
#14687 added a crossOrigin prop to the <Links> component (components.tsx), so the server-rendered <link rel="stylesheet"> and module preloads now match a cross-origin CDN. But the client-side prefetch helpers in packages/react-router/lib/dom/ssr/links.ts build their <link> elements imperatively, outside that component, and were not updated:
getRouteCssDescriptors(route)→{ rel: "stylesheet", href }with nocrossOriginprefetchRouteCss/prefetchStyleLinks→ re-map those to{ rel: "preload", as: "style" }, still with nocrossOrigingetKeyedPrefetchLinks→ re-mapslinks()stylesheet descriptors to{ rel: "prefetch", as: "style" }, still with nocrossOrigin
Because <Links crossOrigin> is a per-render React prop, it never reaches these imperative helpers, so there is no way for an app to make prefetched CSS match its cross-origin config today. Consumers currently have to patch links.js to inject crossOrigin: "anonymous".
Suggested resolution
Thread a crossOrigin value into the CSS prefetch descriptors so they match the manifest/<Links> CORS mode — i.e. add it in getRouteCssDescriptors and to the preload/prefetch descriptors produced by prefetchStyleLinks and getKeyedPrefetchLinks. A per-descriptor crossOrigin should still win over the app-wide value. I have a proposed implementation (with unit tests mirroring the crossOrigin coverage added in #14687) and will open a PR shortly.
Filed by an AI agent on behalf of @comp615.
Source: remix-run/react-router