diff --git a/.changeset/lucky-hotels-complain.md b/.changeset/lucky-hotels-complain.md new file mode 100644 index 0000000000..62cc0e44b7 --- /dev/null +++ b/.changeset/lucky-hotels-complain.md @@ -0,0 +1,5 @@ +--- +"react-router": patch +--- + +[REMOVE] (continuation of #13872) In Framework Mode, remove leftover critical CSS elements in development after initial render if there's a hydration mismatch diff --git a/packages/react-router/lib/dom-export/hydrated-router.tsx b/packages/react-router/lib/dom-export/hydrated-router.tsx index eb2ede42e7..949af934b2 100644 --- a/packages/react-router/lib/dom-export/hydrated-router.tsx +++ b/packages/react-router/lib/dom-export/hydrated-router.tsx @@ -24,6 +24,7 @@ import { UNSAFE_hydrationRouteProperties as hydrationRouteProperties, UNSAFE_createClientRoutesWithHMRRevalidationOptOut as createClientRoutesWithHMRRevalidationOptOut, } from "react-router"; +import { CRITICAL_CSS_DATA_ATTRIBUTE } from "../dom/ssr/components"; import { RouterProvider } from "./dom-router-provider"; type SSRInfo = { @@ -242,6 +243,22 @@ export function HydratedRouter(props: HydratedRouterProps) { setCriticalCss(undefined); } }, []); + React.useEffect(() => { + if (process.env.NODE_ENV === "development" && criticalCss === undefined) { + // When there's a hydration mismatch, React 19 ignores the server HTML and + // re-renders from the root, but it doesn't remove any head tags that + // aren't present in the virtual DOM. This means that the original + // critical CSS elements are still in the document even though we cleared + // them in the effect above. To fix this, this effect is designed to clean + // up any leftover elements. If `criticalCss` is undefined in this effect, + // this means that React is no longer managing the critical CSS elements, + // so if there are any left in the document, these are stale elements from + // the original SSR pass and we can safely remove them. + document + .querySelectorAll(`[${CRITICAL_CSS_DATA_ATTRIBUTE}]`) + .forEach((element) => element.remove()); + } + }, [criticalCss]); let [location, setLocation] = React.useState(router.state.location); diff --git a/packages/react-router/lib/dom/ssr/components.tsx b/packages/react-router/lib/dom/ssr/components.tsx index 44aac97e7b..76c4ee747a 100644 --- a/packages/react-router/lib/dom/ssr/components.tsx +++ b/packages/react-router/lib/dom/ssr/components.tsx @@ -212,6 +212,8 @@ function getActiveMatches( return matches; } +export const CRITICAL_CSS_DATA_ATTRIBUTE = "data-react-router-critical-css"; + /** Renders all of the `` tags created by route module {@link LinksFunction} export. You should render it inside the `` of your document. @@ -247,10 +249,17 @@ export function Links() { return ( <> {typeof criticalCss === "string" ? ( -