Skip to content

Commit b6c5168

Browse files
Remove leftover critical CSS on hydration mismatch (#13995)
* Remove leftover critical CSS on hydration mismatch * Update .changeset/lucky-hotels-complain.md --------- Co-authored-by: Matt Brophy <[email protected]>
1 parent 932b447 commit b6c5168

File tree

3 files changed

+33
-2
lines changed

3 files changed

+33
-2
lines changed

.changeset/lucky-hotels-complain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
[REMOVE] (continuation of #13872) In Framework Mode, remove leftover critical CSS elements in development after initial render if there's a hydration mismatch

packages/react-router/lib/dom-export/hydrated-router.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
UNSAFE_hydrationRouteProperties as hydrationRouteProperties,
2525
UNSAFE_createClientRoutesWithHMRRevalidationOptOut as createClientRoutesWithHMRRevalidationOptOut,
2626
} from "react-router";
27+
import { CRITICAL_CSS_DATA_ATTRIBUTE } from "../dom/ssr/components";
2728
import { RouterProvider } from "./dom-router-provider";
2829

2930
type SSRInfo = {
@@ -242,6 +243,22 @@ export function HydratedRouter(props: HydratedRouterProps) {
242243
setCriticalCss(undefined);
243244
}
244245
}, []);
246+
React.useEffect(() => {
247+
if (process.env.NODE_ENV === "development" && criticalCss === undefined) {
248+
// When there's a hydration mismatch, React 19 ignores the server HTML and
249+
// re-renders from the root, but it doesn't remove any head tags that
250+
// aren't present in the virtual DOM. This means that the original
251+
// critical CSS elements are still in the document even though we cleared
252+
// them in the effect above. To fix this, this effect is designed to clean
253+
// up any leftover elements. If `criticalCss` is undefined in this effect,
254+
// this means that React is no longer managing the critical CSS elements,
255+
// so if there are any left in the document, these are stale elements from
256+
// the original SSR pass and we can safely remove them.
257+
document
258+
.querySelectorAll(`[${CRITICAL_CSS_DATA_ATTRIBUTE}]`)
259+
.forEach((element) => element.remove());
260+
}
261+
}, [criticalCss]);
245262

246263
let [location, setLocation] = React.useState(router.state.location);
247264

packages/react-router/lib/dom/ssr/components.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ function getActiveMatches(
212212
return matches;
213213
}
214214

215+
export const CRITICAL_CSS_DATA_ATTRIBUTE = "data-react-router-critical-css";
216+
215217
/**
216218
Renders all of the `<link>` tags created by route module {@link LinksFunction} export. You should render it inside the `<head>` of your document.
217219
@@ -247,10 +249,17 @@ export function Links() {
247249
return (
248250
<>
249251
{typeof criticalCss === "string" ? (
250-
<style dangerouslySetInnerHTML={{ __html: criticalCss }} />
252+
<style
253+
{...{ [CRITICAL_CSS_DATA_ATTRIBUTE]: "" }}
254+
dangerouslySetInnerHTML={{ __html: criticalCss }}
255+
/>
251256
) : null}
252257
{typeof criticalCss === "object" ? (
253-
<link rel="stylesheet" href={criticalCss.href} />
258+
<link
259+
{...{ [CRITICAL_CSS_DATA_ATTRIBUTE]: "" }}
260+
rel="stylesheet"
261+
href={criticalCss.href}
262+
/>
254263
) : null}
255264
{keyedLinks.map(({ key, link }) =>
256265
isPageLinkDescriptor(link) ? (

0 commit comments

Comments
 (0)