Skip to content

Commit 8155cb7

Browse files
authored
Skip action-only resource routes with prerender:true (remix-run#13004)
1 parent 892468e commit 8155cb7

File tree

4 files changed

+96
-34
lines changed

4 files changed

+96
-34
lines changed

.changeset/late-nails-hear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Skip action-only resource routes when using `prerender:true`

.changeset/rotten-numbers-bathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Limit prerendered resource route `.data` files to only the target route

integration/vite-prerender-test.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,42 @@ test.describe("Prerendering", () => {
344344
expect(html).toMatch('<p data-loader-data="true">About Loader Data</p>');
345345
});
346346

347+
test("Skips action-only resource routes prerender:true", async () => {
348+
let buildStdio = new PassThrough();
349+
fixture = await createFixture({
350+
buildStdio,
351+
files: {
352+
"react-router.config.ts": reactRouterConfig({
353+
prerender: true,
354+
}),
355+
"vite.config.ts": files["vite.config.ts"],
356+
"app/root.tsx": files["app/root.tsx"],
357+
"app/routes/_index.tsx": files["app/routes/_index.tsx"],
358+
"app/routes/action.tsx": js`
359+
export function action() {
360+
return null
361+
}
362+
`,
363+
},
364+
});
365+
366+
let buildOutput: string;
367+
let chunks: Buffer[] = [];
368+
buildOutput = await new Promise<string>((resolve, reject) => {
369+
buildStdio.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
370+
buildStdio.on("error", (err) => reject(err));
371+
buildStdio.on("end", () =>
372+
resolve(Buffer.concat(chunks).toString("utf8"))
373+
);
374+
});
375+
376+
expect(buildOutput).toContain(
377+
"⚠️ Skipping prerendering for resource route without a loader: routes/action"
378+
);
379+
// Only logs once
380+
expect(buildOutput.match(/routes\/action/g)?.length).toBe(1);
381+
});
382+
347383
test("Pre-renders resource routes with file extensions", async () => {
348384
fixture = await createFixture({
349385
prerender: true,
@@ -385,9 +421,6 @@ test.describe("Prerendering", () => {
385421

386422
let dataRes = await fixture.requestSingleFetchData("/json.json.data");
387423
expect(dataRes.data).toEqual({
388-
root: {
389-
data: null,
390-
},
391424
"routes/json[.json]": {
392425
data: {
393426
hello: "world",
@@ -400,9 +433,6 @@ test.describe("Prerendering", () => {
400433

401434
dataRes = await fixture.requestSingleFetchData("/text.txt.data");
402435
expect(dataRes.data).toEqual({
403-
root: {
404-
data: null,
405-
},
406436
"routes/text[.txt]": {
407437
data: "Hello, world",
408438
},

packages/react-router-dev/vite/plugin.ts

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,21 +2317,6 @@ async function handlePrerender(
23172317
matches,
23182318
`Unable to prerender path because it does not match any routes: ${path}`
23192319
);
2320-
let hasLoaders = matches.some(
2321-
(m) => build.assets.routes[m.route.id]?.hasLoader
2322-
);
2323-
let data: string | undefined;
2324-
if (hasLoaders) {
2325-
data = await prerenderData(
2326-
handler,
2327-
path,
2328-
clientBuildDirectory,
2329-
reactRouterConfig,
2330-
viteConfig,
2331-
{ headers }
2332-
);
2333-
}
2334-
23352320
// When prerendering a resource route, we don't want to pass along the
23362321
// `.data` file since we want to prerender the raw Response returned from
23372322
// the loader. Presumably this is for routes where a file extension is
@@ -2340,21 +2325,53 @@ async function handlePrerender(
23402325
let leafRoute = matches ? matches[matches.length - 1].route : null;
23412326
let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
23422327
let isResourceRoute =
2343-
manifestRoute &&
2344-
!manifestRoute.default &&
2345-
!manifestRoute.ErrorBoundary &&
2346-
manifestRoute.loader;
2328+
manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;
23472329

23482330
if (isResourceRoute) {
2349-
await prerenderResourceRoute(
2350-
handler,
2351-
path,
2352-
clientBuildDirectory,
2353-
reactRouterConfig,
2354-
viteConfig,
2355-
{ headers }
2356-
);
2331+
invariant(leafRoute);
2332+
invariant(manifestRoute);
2333+
if (manifestRoute.loader) {
2334+
// Prerender a .data file for turbo-stream consumption
2335+
await prerenderData(
2336+
handler,
2337+
path,
2338+
[leafRoute.id],
2339+
clientBuildDirectory,
2340+
reactRouterConfig,
2341+
viteConfig,
2342+
{ headers }
2343+
);
2344+
// Prerender a raw file for external consumption
2345+
await prerenderResourceRoute(
2346+
handler,
2347+
path,
2348+
clientBuildDirectory,
2349+
reactRouterConfig,
2350+
viteConfig,
2351+
{ headers }
2352+
);
2353+
} else {
2354+
viteConfig.logger.warn(
2355+
`⚠️ Skipping prerendering for resource route without a loader: ${leafRoute?.id}`
2356+
);
2357+
}
23572358
} else {
2359+
let hasLoaders = matches.some(
2360+
(m) => build.assets.routes[m.route.id]?.hasLoader
2361+
);
2362+
let data: string | undefined;
2363+
if (!isResourceRoute && hasLoaders) {
2364+
data = await prerenderData(
2365+
handler,
2366+
path,
2367+
null,
2368+
clientBuildDirectory,
2369+
reactRouterConfig,
2370+
viteConfig,
2371+
{ headers }
2372+
);
2373+
}
2374+
23582375
await prerenderRoute(
23592376
handler,
23602377
path,
@@ -2408,6 +2425,7 @@ function getStaticPrerenderPaths(routes: DataRouteObject[]) {
24082425
async function prerenderData(
24092426
handler: RequestHandler,
24102427
prerenderPath: string,
2428+
onlyRoutes: string[] | null,
24112429
clientBuildDirectory: string,
24122430
reactRouterConfig: ResolvedReactRouterConfig,
24132431
viteConfig: Vite.ResolvedConfig,
@@ -2418,7 +2436,11 @@ async function prerenderData(
24182436
? "/_root.data"
24192437
: `${prerenderPath.replace(/\/$/, "")}.data`
24202438
}`.replace(/\/\/+/g, "/");
2421-
let request = new Request(`http://localhost${normalizedPath}`, requestInit);
2439+
let url = new URL(`http://localhost${normalizedPath}`);
2440+
if (onlyRoutes?.length) {
2441+
url.searchParams.set("_routes", onlyRoutes.join(","));
2442+
}
2443+
let request = new Request(url, requestInit);
24222444
let response = await handler(request);
24232445
let data = await response.text();
24242446

0 commit comments

Comments
 (0)