After #7157, in production builds, navigating client-side between two
routes that share a CSS chunk removes that shared <link rel="stylesheet">
from <head> and never restores it. The destination route renders unstyled.
Reproduction
https://github.com/ZacharyL2/tsr-unmatched-assets-repro
git clone https://github.com/ZacharyL2/tsr-unmatched-assets-repro
cd tsr-unmatched-assets-repro
# remove pnpm.patchedDependencies in package.json to see the bug
pnpm install && pnpm build && pnpm start
Open /a, click the /b link, see the shared <Card> lose its style.
Cause
Asset.tsx renders <link rel="stylesheet"> without a precedence prop,
so React 19 doesn't treat it as a hoistable resource. On client-side nav,
React unmounts the previous route's <link> during reconciliation, and
Vite's preload helper (which has an in-memory dep cache) refuses to
re-insert it because it considers the module already loaded.
#7157's includeUnmatchedRouteAssets: false is what makes this visible:
the dehydrated manifest no longer carries the destination route's assets,
so useTags() has nothing to render after navigation.
Full diagnosis (3 layers, with file:line refs) in the repro README.
Proposed fix
// packages/react-router/src/Asset.tsx
case 'link':
- return <link {...attrs} nonce={nonce} suppressHydrationWarning />
+ return (
+ <link
+ {...attrs}
+ precedence={
+ attrs?.precedence ??
+ (attrs?.rel === 'stylesheet' ? 'default' : undefined)
+ }
+ nonce={nonce}
+ suppressHydrationWarning
+ />
+ )
Verified in the repro: this single change fully fixes the bug while
keeping #7157's payload optimization. User-set precedence is respected;
non-stylesheet links unaffected.
After #7157, in production builds, navigating client-side between two
routes that share a CSS chunk removes that shared
<link rel="stylesheet">from
<head>and never restores it. The destination route renders unstyled.Reproduction
https://github.com/ZacharyL2/tsr-unmatched-assets-repro
Open
/a, click the/blink, see the shared<Card>lose its style.Cause
Asset.tsxrenders<link rel="stylesheet">without aprecedenceprop,so React 19 doesn't treat it as a hoistable resource. On client-side nav,
React unmounts the previous route's
<link>during reconciliation, andVite's preload helper (which has an in-memory dep cache) refuses to
re-insert it because it considers the module already loaded.
#7157'sincludeUnmatchedRouteAssets: falseis what makes this visible:the dehydrated manifest no longer carries the destination route's assets,
so
useTags()has nothing to render after navigation.Full diagnosis (3 layers, with file:line refs) in the repro README.
Proposed fix
Verified in the repro: this single change fully fixes the bug while
keeping #7157's payload optimization. User-set
precedenceis respected;non-stylesheet links unaffected.