Skip to content

Commit 45beb8d

Browse files
Improve prefetch perf for CSS side effects (remix-run#12889)
1 parent 80cf6ff commit 45beb8d

File tree

3 files changed

+41
-14
lines changed

3 files changed

+41
-14
lines changed

.changeset/stupid-mirrors-prove.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+
Improve prefetch performance of CSS side effects in framework mode

packages/react-router/lib/dom/ssr/links.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,26 @@ export function getKeyedLinksForMatches(
3838
return dedupeLinkDescriptors(descriptors, preloads);
3939
}
4040

41+
function getRouteCssDescriptors(route: EntryRoute): HtmlLinkDescriptor[] {
42+
if (!route.css) return [];
43+
return route.css.map((href) => ({ rel: "stylesheet", href }));
44+
}
45+
46+
export async function prefetchRouteCss(route: EntryRoute): Promise<void> {
47+
if (!route.css) return;
48+
let descriptors = getRouteCssDescriptors(route);
49+
await Promise.all(descriptors.map(prefetchStyleLink));
50+
}
51+
4152
export async function prefetchStyleLinks(
4253
route: EntryRoute,
4354
routeModule: RouteModule
4455
): Promise<void> {
4556
if ((!route.css && !routeModule.links) || !isPreloadSupported()) return;
4657

47-
let descriptors = [];
58+
let descriptors: LinkDescriptor[] = [];
4859
if (route.css) {
49-
descriptors.push(...route.css.map((href) => ({ rel: "stylesheet", href })));
60+
descriptors.push(...getRouteCssDescriptors(route));
5061
}
5162
if (routeModule.links) {
5263
descriptors.push(...routeModule.links());
@@ -64,21 +75,24 @@ export async function prefetchStyleLinks(
6475
}
6576
}
6677

67-
// don't block for non-matching media queries, or for stylesheets that are
68-
// already in the DOM (active route revalidations)
69-
let matchingLinks = styleLinks.filter(
70-
(link) =>
71-
(!link.media || window.matchMedia(link.media).matches) &&
72-
!document.querySelector(`link[rel="stylesheet"][href="${link.href}"]`)
73-
);
74-
75-
await Promise.all(matchingLinks.map(prefetchStyleLink));
78+
await Promise.all(styleLinks.map(prefetchStyleLink));
7679
}
7780

7881
async function prefetchStyleLink(
7982
descriptor: HtmlLinkDescriptor
8083
): Promise<void> {
8184
return new Promise((resolve) => {
85+
// don't prefetch non-matching media queries, or stylesheets that are
86+
// already in the DOM (active route revalidations)
87+
if (
88+
(descriptor.media && !window.matchMedia(descriptor.media).matches) ||
89+
document.querySelector(
90+
`link[rel="stylesheet"][href="${descriptor.href}"]`
91+
)
92+
) {
93+
return resolve();
94+
}
95+
8296
let link = document.createElement("link");
8397
Object.assign(link, descriptor);
8498

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ErrorResponseImpl } from "../../router/utils";
1212
import type { RouteModule, RouteModules } from "./routeModules";
1313
import { loadRouteModule } from "./routeModules";
1414
import type { FutureConfig } from "./entry";
15-
import { prefetchStyleLinks } from "./links";
15+
import { prefetchRouteCss, prefetchStyleLinks } from "./links";
1616
import { RemixRootDefaultErrorBoundary } from "./errorBoundaries";
1717
import { RemixRootDefaultHydrateFallback } from "./fallback";
1818
import invariant from "./invariant";
@@ -533,8 +533,16 @@ async function loadRouteModuleWithBlockingLinks(
533533
route: EntryRoute,
534534
routeModules: RouteModules
535535
) {
536-
let routeModule = await loadRouteModule(route, routeModules);
537-
await prefetchStyleLinks(route, routeModule);
536+
// Ensure the route module and its static CSS links are loaded in parallel as
537+
// soon as possible before blocking on the route module
538+
let routeModulePromise = loadRouteModule(route, routeModules);
539+
let prefetchRouteCssPromise = prefetchRouteCss(route);
540+
541+
let routeModule = await routeModulePromise;
542+
await Promise.all([
543+
prefetchRouteCssPromise,
544+
prefetchStyleLinks(route, routeModule),
545+
]);
538546

539547
// Include all `browserSafeRouteExports` fields, except `HydrateFallback`
540548
// since those aren't used on lazily loaded routes

0 commit comments

Comments
 (0)