diff --git a/.changeset/stylesheet-re-prefetch.md b/.changeset/stylesheet-re-prefetch.md
new file mode 100644
index 00000000000..3310ddc8f28
--- /dev/null
+++ b/.changeset/stylesheet-re-prefetch.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/react": patch
+---
+
+Avoid re-prefetching stylesheets for active routes during a revalidation
diff --git a/integration/prefetch-test.ts b/integration/prefetch-test.ts
index c07b00abc6e..a70c714e10c 100644
--- a/integration/prefetch-test.ts
+++ b/integration/prefetch-test.ts
@@ -342,3 +342,95 @@ test.describe("prefetch=viewport", () => {
await expect(page.locator("div link")).toHaveCount(0);
});
});
+
+test.describe("other scenarios", () => {
+ let fixture: Fixture;
+ let appFixture: AppFixture;
+
+ test.afterAll(() => {
+ appFixture?.close();
+ });
+
+ test("does not add prefetch links for stylesheets already in the DOM (active routes)", async ({
+ page,
+ }) => {
+ fixture = await createFixture({
+ config: {
+ future: { v2_routeConvention: true },
+ },
+ files: {
+ "app/root.jsx": js`
+ import { Links, Meta, Scripts, useFetcher } from "@remix-run/react";
+ import globalCss from "./global.css";
+
+ export function links() {
+ return [{ rel: "stylesheet", href: globalCss }];
+ }
+
+ export async function action() {
+ return null;
+ }
+
+ export async function loader() {
+ return null;
+ }
+
+ export default function Root() {
+ let fetcher = useFetcher();
+
+ return (
+
+
+
+
+
+
+
+ {fetcher.state}
+
+
+
+ );
+ }
+ `,
+
+ "app/global.css": `
+ body {
+ background-color: black;
+ color: white;
+ }
+ `,
+
+ "app/routes/_index.jsx": js`
+ export default function() {
+ return Index
;
+ }
+ `,
+ },
+ });
+ appFixture = await createAppFixture(fixture);
+ let requests: { type: string; url: string }[] = [];
+
+ page.on("request", (req) => {
+ requests.push({
+ type: req.resourceType(),
+ url: req.url(),
+ });
+ });
+
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await page.click("#submit-fetcher");
+ await page.waitForSelector("#fetcher-state--idle");
+ // We should not send a second request for this root stylesheet that's
+ // already been rendered in the DOM
+ let stylesheets = requests.filter(
+ (r) => r.type === "stylesheet" && /\/global-[a-z0-9]+\.css/i.test(r.url)
+ );
+ expect(stylesheets.length).toBe(1);
+ });
+});
diff --git a/packages/remix-react/links.ts b/packages/remix-react/links.ts
index 6c070d5746d..1edcd6c5841 100644
--- a/packages/remix-react/links.ts
+++ b/packages/remix-react/links.ts
@@ -254,9 +254,12 @@ export async function prefetchStyleLinks(
}
}
- // don't block for non-matching media queries
+ // don't block for non-matching media queries, or for stylesheets that are
+ // already in the DOM (active route revalidations)
let matchingLinks = styleLinks.filter(
- (link) => !link.media || window.matchMedia(link.media).matches
+ (link) =>
+ (!link.media || window.matchMedia(link.media).matches) &&
+ !document.querySelector(`link[rel="stylesheet"][href="${link.href}"]`)
);
await Promise.all(matchingLinks.map(prefetchStyleLink));