Development CSS preload can revive an edited stylesheet after exact restoration
Problem
A stylesheet can return to its original appearance after CSS HMR, then silently revert to the edited appearance when a route mounts its canonical stylesheet URL again. The source and server's current CSS are already restored. The page does not reload, the draft remains intact, and there is no browser exception.
The trigger is a temporary <link rel="preload" as="style"> for the canonical URL while that URL serves the edited CSS. Removing the preload element does not discard its reusable response in the document. A later stylesheet link can consume those earlier bytes without requesting the restored CSS.
Reproduction sequence
- Mount
/style.css, initially red with no outline, and type in the draft input.
- Edit CSS to blue with a dashed outline. CSS HMR updates the active link to
/style.css?<timestamp>.
- Insert a temporary preload for
/style.css while it serves the edited bytes; remove the preload on load, as Router's prefetchStyleLink does.
- Restore the exact original source and apply CSS HMR. Assert that the active stylesheet is red with no outline.
- Mount another stylesheet link using
/style.css.
- Actual: blue and dashed return. Expected: the restored red stylesheet remains active.
In the failing browser captures the remount issues no new canonical CSS request. The active timestamped stylesheet contains the restored rule; the newly mounted canonical stylesheet contains the old edited rule.
Results and scope
| CSS URL strategy |
ETag without Cache-Control |
Cache-Control: no-store |
| Canonical URL reused by Router preload/remount |
Fails at remount |
Fails at remount |
| Content-addressed retained alias behind a version query |
Passes |
Passes |
All four measured native runs retain one document and the input draft, with no page, console, or React errors. The smaller browser-only reduction also reproduced both baseline failures and passed both alias controls, with one document, the draft retained, and no page errors. Ordinary stylesheet insertion without the temporary preload passed both policies. no-store alone did not prevent this document's preload reuse.
The runnable reproduction and source materializer use public packages: Rsbuild 2.2.8, Rspack 2.2.6, React 19.2.7, and Playwright 1.61.1, on Node 24.19.0. The measured native runs use a local Rspack binding with the separate React-owned stylesheet-node fix, so its earlier removeChild failure does not interrupt this test. This is not a claim that the unmodified official native binding passes the preceding ownership steps. The full native source patch and binary hash are included; no binary is distributed.
README.md includes the measured native fixture commands, sanitized evidence, and a smaller browser-only protocol reduction that needs no native build. That smaller reduction has now been browser-run under both cache-header policies for baseline and candidate. It models the stylesheet URL and DOM behavior, not the full Router server.
Router integration
React Router 7.18.1 prefetchStyleLinks creates temporary canonical stylesheet preloads. Its exact-href check does not match an active link that CSS HMR has changed to a timestamped URL. Router 0.8.1 development manifests currently reuse canonical CSS URLs, so navigation/HDR can revisit this lifecycle.
This is distinct from closed #147, which concerned committing an old server loader during a paired rebuild, and #148, which concerned a stale browser manifest and pre-hydration input. Here the restored stylesheet is visibly correct before a subsequent preload-backed mount revives stale CSS.
Candidate
The included patch against official 0.8.1:
- Emits a retained, same-directory CSS alias keyed by the actual CSS Source bytes at manifest generation.
- Gives entry and route CSS canonical URLs a reserved version query and rewrites that query to the alias before normal asset serving. Native HMR's canonical filename matching still works.
- Keeps old URL bytes stable even if an old manifest preloads A while the current asset is B. A query token without immutable serving would not provide that guarantee.
- Normalizes only the reserved token when comparing CSS ownership; genuine removals retain the existing reload policy.
- Publishes committed entry and route CSS through the existing HMR-idle queue, and replays the last committed manifest on reconnect. Failed builds do not publish candidate CSS; paired Node changes still evaluate the new loader build.
The source build and export smoke test and 98 focused tests passed. Tests use the existing upstream Rstest sources with a Vitest adapter; the full upstream framework suite was not run. The browser fixture validates the alias behavior with native HMR and React. Full application repeat validation remains in progress; a later diagnostic reported aborted asset requests and is under investigation. That application is not a reproduction dependency, and no product code is included. Production behavior is unchanged. Development retains one alias per unique CSS byte sequence until output cleanup/restart.
Development CSS preload can revive an edited stylesheet after exact restoration
Problem
A stylesheet can return to its original appearance after CSS HMR, then silently revert to the edited appearance when a route mounts its canonical stylesheet URL again. The source and server's current CSS are already restored. The page does not reload, the draft remains intact, and there is no browser exception.
The trigger is a temporary
<link rel="preload" as="style">for the canonical URL while that URL serves the edited CSS. Removing the preload element does not discard its reusable response in the document. A later stylesheet link can consume those earlier bytes without requesting the restored CSS.Reproduction sequence
/style.css, initially red with no outline, and type in the draft input./style.css?<timestamp>./style.csswhile it serves the edited bytes; remove the preload on load, as Router'sprefetchStyleLinkdoes./style.css.In the failing browser captures the remount issues no new canonical CSS request. The active timestamped stylesheet contains the restored rule; the newly mounted canonical stylesheet contains the old edited rule.
Results and scope
All four measured native runs retain one document and the input draft, with no page, console, or React errors. The smaller browser-only reduction also reproduced both baseline failures and passed both alias controls, with one document, the draft retained, and no page errors. Ordinary stylesheet insertion without the temporary preload passed both policies.
no-storealone did not prevent this document's preload reuse.The runnable reproduction and source materializer use public packages: Rsbuild 2.2.8, Rspack 2.2.6, React 19.2.7, and Playwright 1.61.1, on Node 24.19.0. The measured native runs use a local Rspack binding with the separate React-owned stylesheet-node fix, so its earlier
removeChildfailure does not interrupt this test. This is not a claim that the unmodified official native binding passes the preceding ownership steps. The full native source patch and binary hash are included; no binary is distributed.README.mdincludes the measured native fixture commands, sanitized evidence, and a smaller browser-only protocol reduction that needs no native build. That smaller reduction has now been browser-run under both cache-header policies for baseline and candidate. It models the stylesheet URL and DOM behavior, not the full Router server.Router integration
React Router 7.18.1
prefetchStyleLinkscreates temporary canonical stylesheet preloads. Its exact-href check does not match an active link that CSS HMR has changed to a timestamped URL. Router 0.8.1 development manifests currently reuse canonical CSS URLs, so navigation/HDR can revisit this lifecycle.This is distinct from closed #147, which concerned committing an old server loader during a paired rebuild, and #148, which concerned a stale browser manifest and pre-hydration input. Here the restored stylesheet is visibly correct before a subsequent preload-backed mount revives stale CSS.
Candidate
The included patch against official 0.8.1:
The source build and export smoke test and 98 focused tests passed. Tests use the existing upstream Rstest sources with a Vitest adapter; the full upstream framework suite was not run. The browser fixture validates the alias behavior with native HMR and React. Full application repeat validation remains in progress; a later diagnostic reported aborted asset requests and is under investigation. That application is not a reproduction dependency, and no product code is included. Production behavior is unchanged. Development retains one alias per unique CSS byte sequence until output cleanup/restart.