#15401·react-router

Client-side CSS prefetch links missing crossOrigin (cross-origin CDN assets fetched twice)

Author: comp615Created Aug 13, 2026Updated Aug 14, 2026
Labelspkg:react-routerfeat:css

What version of React Router are you using?

8.3.0 (also present on main)

Steps to Reproduce

  1. 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> need crossOrigin="anonymous" to fetch CSS/JS with CORS.
  2. Enable link prefetching on a navigation target, e.g. <Link prefetch="intent" to="/some-route" />, where /some-route pulls in route CSS (route.css) or links()-declared stylesheets.
  3. 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 no crossOrigin
  • prefetchRouteCss / prefetchStyleLinks → re-map those to { rel: "preload", as: "style" }, still with no crossOrigin
  • getKeyedPrefetchLinks → re-maps links() stylesheet descriptors to { rel: "prefetch", as: "style" }, still with no crossOrigin

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.