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));