diff --git a/.changeset/heavy-ways-rule.md b/.changeset/heavy-ways-rule.md new file mode 100644 index 00000000000..f284fd575f3 --- /dev/null +++ b/.changeset/heavy-ways-rule.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": patch +--- + +Don't prefetch server loader data when clientLoader exists diff --git a/integration/client-data-test.ts b/integration/client-data-test.ts index 2269fd41fcb..706670346ea 100644 --- a/integration/client-data-test.ts +++ b/integration/client-data-test.ts @@ -977,6 +977,48 @@ test.describe("Client Data", () => { 'not have a server loader (routeId: "routes/parent.child")' ); }); + + test("does not prefetch server loader if a client loader is present", async ({ + page, + }) => { + appFixture = await createAppFixture( + await createTestFixture({ + files: { + ...getFiles({ + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }), + "app/routes/_index.tsx": js` + import { Link } from '@remix-run/react' + export default function Component() { + return ( + <> + Go to /parent + Go to /parent/child + + ); + } + `, + }, + }) + ); + + let dataUrls: string[] = []; + page.on("request", (request) => { + if (request.url().includes("_data")) { + dataUrls.push(request.url()); + } + }); + + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + // Only prefetch child server loader since parent has a `clientLoader` + expect(dataUrls).toEqual([ + expect.stringMatching(/parent\/child\?_data=routes%2Fparent\.child/), + ]); + }); }); test.describe("clientAction - critical route module", () => { @@ -2212,6 +2254,50 @@ test.describe("single fetch", () => { 'not have a server loader (routeId: "routes/parent.child")' ); }); + + test("does not prefetch server loader if a client loader is present", async ({ + page, + }) => { + appFixture = await createAppFixture( + await createTestFixture({ + files: { + ...getFiles({ + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }), + "app/routes/_index.tsx": js` + import { Link } from '@remix-run/react' + export default function Component() { + return ( + <> + Go to /parent + Go to /parent/child + + ); + } + `, + }, + }) + ); + + let dataUrls: string[] = []; + page.on("request", (request) => { + if (request.url().includes(".data")) { + dataUrls.push(request.url()); + } + }); + + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + // Only prefetch child server loader since parent has a `clientLoader` + expect(dataUrls).toEqual([ + expect.stringMatching( + /parent\/child\.data\?_routes=routes%2Fparent\.child/ + ), + ]); + }); }); test.describe("clientAction - critical route module", () => { diff --git a/packages/remix-react/links.ts b/packages/remix-react/links.ts index d93f420bbfe..c9d3245b739 100644 --- a/packages/remix-react/links.ts +++ b/packages/remix-react/links.ts @@ -425,7 +425,11 @@ export function getDataLinkHrefs( let path = parsePathPatch(page); return dedupeHrefs( matches - .filter((match) => manifest.routes[match.route.id].hasLoader) + .filter( + (match) => + manifest.routes[match.route.id].hasLoader && + !manifest.routes[match.route.id].hasClientLoader + ) .map((match) => { let { pathname, search } = path; let searchParams = new URLSearchParams(search);