Skip to content

Commit c979bcb

Browse files
authored
Disable FOW for all ssr:false and tighten up spa mode logic (remix-run#12894)
1 parent 04f92e0 commit c979bcb

File tree

13 files changed

+59
-47
lines changed

13 files changed

+59
-47
lines changed

.changeset/unlucky-dolphins-chew.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@react-router/dev": patch
3+
"react-router": patch
4+
---
5+
6+
Disable Lazy Route Discovery for all `ssr:false` apps and not just "SPA Mode" because there is no runtime server to serve the search-param-configured `__manifest` requests
7+
8+
- We previously only disabled this for "SPA Mode" which is `ssr:false` and no `prerender` config but we realized it should apply to all `ssr:false` apps, including those prerendering multiple pages
9+
- In those `prerender` scenarios we would prerender the `/__manifest` file assuming the static file server would serve it but that makes some unneccesary assumptions about the static file server behaviors

integration/vite-prerender-test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ test.describe("Prerendering", () => {
232232

233233
let clientDir = path.join(fixture.projectDir, "build", "client");
234234
expect(listAllFiles(clientDir).sort()).toEqual([
235-
"__manifest",
236235
"_root.data",
237236
"about.data",
238237
"about/index.html",
@@ -288,7 +287,6 @@ test.describe("Prerendering", () => {
288287

289288
let clientDir = path.join(fixture.projectDir, "build", "client");
290289
expect(listAllFiles(clientDir).sort()).toEqual([
291-
"__manifest",
292290
"_root.data",
293291
"about.data",
294292
"about/index.html",
@@ -345,7 +343,6 @@ test.describe("Prerendering", () => {
345343

346344
let clientDir = path.join(fixture.projectDir, "build", "client");
347345
expect(listAllFiles(clientDir).sort()).toEqual([
348-
"__manifest",
349346
"_root.data",
350347
"a.data",
351348
"a/index.html",
@@ -397,7 +394,6 @@ test.describe("Prerendering", () => {
397394

398395
let clientDir = path.join(fixture.projectDir, "build", "client");
399396
expect(listAllFiles(clientDir).sort()).toEqual([
400-
"__manifest",
401397
"_root.data",
402398
"about.data",
403399
"about/index.html",
@@ -468,7 +464,6 @@ test.describe("Prerendering", () => {
468464

469465
let clientDir = path.join(fixture.projectDir, "build", "client");
470466
expect(listAllFiles(clientDir).sort()).toEqual([
471-
"__manifest",
472467
"_root.data",
473468
"about.data",
474469
"about/index.html",
@@ -494,7 +489,13 @@ test.describe("Prerendering", () => {
494489
test("Hydrates into a navigable app", async ({ page }) => {
495490
fixture = await createFixture({
496491
prerender: true,
497-
files,
492+
files: {
493+
...files,
494+
"react-router.config.ts": reactRouterConfig({
495+
ssr: false,
496+
prerender: true,
497+
}),
498+
},
498499
});
499500
appFixture = await createAppFixture(fixture);
500501

@@ -511,14 +512,14 @@ test.describe("Prerendering", () => {
511512
await page.waitForSelector("[data-mounted]");
512513
await app.clickLink("/about");
513514
await page.waitForSelector("[data-route]:has-text('About')");
514-
expect(requests).toEqual(["/__manifest", "/about.data"]);
515+
expect(requests).toEqual(["/about.data"]);
515516
});
516517

517518
test("Serves the prerendered HTML file alongside runtime routes", async ({
518519
page,
519520
}) => {
520521
fixture = await createFixture({
521-
// Even thogh we are prerendering, we want a running server so we can
522+
// Even though we are prerendering, we want a running server so we can
522523
// hit the pre-rendered HTML file and a non-prerendered route
523524
prerender: false,
524525
files: {
@@ -783,6 +784,10 @@ test.describe("Prerendering", () => {
783784
prerender: true,
784785
files: {
785786
...files,
787+
"react-router.config.ts": reactRouterConfig({
788+
ssr: false,
789+
prerender: true,
790+
}),
786791
"app/routes/$slug.tsx": js`
787792
import * as React from "react";
788793
import { useLoaderData } from "react-router";

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

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,8 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
557557
)};
558558
export const basename = ${JSON.stringify(ctx.reactRouterConfig.basename)};
559559
export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
560-
export const isSpaMode = ${
561-
!ctx.reactRouterConfig.ssr && ctx.reactRouterConfig.prerender == null
562-
};
560+
export const ssr = ${ctx.reactRouterConfig.ssr};
561+
export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
563562
export const publicPath = ${JSON.stringify(ctx.publicPath)};
564563
export const entry = { module: entryServer };
565564
export const routes = {
@@ -1743,7 +1742,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
17431742
let route = getRoute(ctx.reactRouterConfig, id);
17441743
if (!route) return;
17451744

1746-
if (!options?.ssr && !ctx.reactRouterConfig.ssr) {
1745+
if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
17471746
let exportNames = getExportNames(code);
17481747
let serverOnlyExports = exportNames.filter((exp) =>
17491748
SERVER_ONLY_ROUTE_EXPORTS.includes(exp)
@@ -2150,6 +2149,19 @@ async function getRouteMetadata(
21502149
return info;
21512150
}
21522151

2152+
function isSpaModeEnabled(
2153+
reactRouterConfig: ReactRouterPluginContext["reactRouterConfig"]
2154+
) {
2155+
return (
2156+
reactRouterConfig.ssr === false &&
2157+
(reactRouterConfig.prerender == null ||
2158+
reactRouterConfig.prerender === false ||
2159+
(Array.isArray(reactRouterConfig.prerender) &&
2160+
reactRouterConfig.prerender.length === 1 &&
2161+
reactRouterConfig.prerender[0] === "/"))
2162+
);
2163+
}
2164+
21532165
async function getPrerenderBuildAndHandler(
21542166
viteConfig: Vite.ResolvedConfig,
21552167
serverBuildDirectory: string,
@@ -2284,13 +2296,6 @@ async function handlePrerender(
22842296
);
22852297
}
22862298
}
2287-
2288-
await prerenderManifest(
2289-
build,
2290-
clientBuildDirectory,
2291-
reactRouterConfig,
2292-
viteConfig
2293-
);
22942299
}
22952300

22962301
function determineStaticPrerenderRoutes(
@@ -2418,24 +2423,6 @@ async function prerenderResourceRoute(
24182423
viteConfig.logger.info(`Prerender: Generated ${colors.bold(outfile)}`);
24192424
}
24202425

2421-
async function prerenderManifest(
2422-
build: ServerBuild,
2423-
clientBuildDirectory: string,
2424-
reactRouterConfig: ResolvedReactRouterConfig,
2425-
viteConfig: Vite.ResolvedConfig
2426-
) {
2427-
let normalizedPath = `${reactRouterConfig.basename}/__manifest`.replace(
2428-
/\/\/+/g,
2429-
"/"
2430-
);
2431-
let outdir = path.relative(process.cwd(), clientBuildDirectory);
2432-
let outfile = path.join(outdir, ...normalizedPath.split("/"));
2433-
await fse.ensureDir(path.dirname(outfile));
2434-
let manifestData = JSON.stringify(build.assets.routes);
2435-
await fse.outputFile(outfile, manifestData);
2436-
viteConfig.logger.info(`Prerender: Generated ${colors.bold(outfile)}`);
2437-
}
2438-
24392426
function validatePrerenderedResponse(
24402427
response: Response,
24412428
html: string,

packages/react-router/lib/dom-export/hydrated-router.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ function createHydratedRouter(): DataRouter {
176176
patchRoutesOnNavigation: getPatchRoutesOnNavigationFunction(
177177
ssrInfo.manifest,
178178
ssrInfo.routeModules,
179+
ssrInfo.context.ssr,
179180
ssrInfo.context.isSpaMode,
180181
ssrInfo.context.basename
181182
),
@@ -247,6 +248,7 @@ export function HydratedRouter() {
247248
router,
248249
ssrInfo.manifest,
249250
ssrInfo.routeModules,
251+
ssrInfo.context.ssr,
250252
ssrInfo.context.isSpaMode
251253
);
252254

@@ -264,6 +266,7 @@ export function HydratedRouter() {
264266
routeModules: ssrInfo.routeModules,
265267
future: ssrInfo.context.future,
266268
criticalCss,
269+
ssr: ssrInfo.context.ssr,
267270
isSpaMode: ssrInfo.context.isSpaMode,
268271
}}
269272
>

packages/react-router/lib/dom/global.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type WindowReactRouterContext = {
77
state: HydrationState;
88
criticalCss?: string;
99
future: FutureConfig;
10+
ssr: boolean;
1011
isSpaMode: boolean;
1112
stream: ReadableStream<Uint8Array> | undefined;
1213
streamController: ReadableStreamDefaultController<Uint8Array>;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,11 +638,11 @@ export type ScriptsProps = Omit<
638638
@category Components
639639
*/
640640
export function Scripts(props: ScriptsProps) {
641-
let { manifest, serverHandoffString, isSpaMode, renderMeta } =
641+
let { manifest, serverHandoffString, isSpaMode, ssr, renderMeta } =
642642
useFrameworkContext();
643643
let { router, static: isStatic, staticContext } = useDataRouterContext();
644644
let { matches: routerMatches } = useDataRouterStateContext();
645-
let enableFogOfWar = isFogOfWarEnabled(isSpaMode);
645+
let enableFogOfWar = isFogOfWarEnabled(ssr);
646646

647647
// Let <ServerRouter> know that we hydrated and we should render the single
648648
// fetch streaming scripts

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface FrameworkContextObject {
1616
criticalCss?: string;
1717
serverHandoffString?: string;
1818
future: FutureConfig;
19+
ssr: boolean;
1920
isSpaMode: boolean;
2021
serializeError?(error: Error): SerializedError;
2122
renderMeta?: {

packages/react-router/lib/dom/ssr/fog-of-war.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ const discoveredPaths = new Set<string>();
2626
// https://stackoverflow.com/a/417184
2727
const URL_LIMIT = 7680;
2828

29-
export function isFogOfWarEnabled(isSpaMode: boolean) {
30-
return !isSpaMode;
29+
export function isFogOfWarEnabled(ssr: boolean) {
30+
return ssr === true;
3131
}
3232

3333
export function getPartialManifest(
@@ -70,10 +70,11 @@ export function getPartialManifest(
7070
export function getPatchRoutesOnNavigationFunction(
7171
manifest: AssetsManifest,
7272
routeModules: RouteModules,
73+
ssr: boolean,
7374
isSpaMode: boolean,
7475
basename: string | undefined
7576
): PatchRoutesOnNavigationFunction | undefined {
76-
if (!isFogOfWarEnabled(isSpaMode)) {
77+
if (!isFogOfWarEnabled(ssr)) {
7778
return undefined;
7879
}
7980

@@ -97,14 +98,12 @@ export function useFogOFWarDiscovery(
9798
router: DataRouter,
9899
manifest: AssetsManifest,
99100
routeModules: RouteModules,
101+
ssr: boolean,
100102
isSpaMode: boolean
101103
) {
102104
React.useEffect(() => {
103105
// Don't prefetch if not enabled or if the user has `saveData` enabled
104-
if (
105-
!isFogOfWarEnabled(isSpaMode) ||
106-
navigator.connection?.saveData === true
107-
) {
106+
if (!isFogOfWarEnabled(ssr) || navigator.connection?.saveData === true) {
108107
return;
109108
}
110109

@@ -177,7 +176,7 @@ export function useFogOFWarDiscovery(
177176
});
178177

179178
return () => observer.disconnect();
180-
}, [isSpaMode, manifest, routeModules, router]);
179+
}, [ssr, isSpaMode, manifest, routeModules, router]);
181180
}
182181

183182
export async function fetchAndApplyManifestPatches(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export function createRoutesStub(
112112
version: "",
113113
},
114114
routeModules: {},
115+
ssr: false,
115116
isSpaMode: false,
116117
};
117118

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function ServerRouter({
7575
criticalCss,
7676
serverHandoffString,
7777
future: context.future,
78+
ssr: context.ssr,
7879
isSpaMode: context.isSpaMode,
7980
serializeError: context.serializeError,
8081
renderMeta: context.renderMeta,

0 commit comments

Comments
 (0)