@@ -905,7 +905,7 @@ test.describe("SPA Mode", () => {
905
905
appFixture . close ( ) ;
906
906
} ) ;
907
907
908
- test ( "renders the root HydrateFallback initially with access to the root loader data" , async ( { } ) => {
908
+ test ( "renders the root HydrateFallback initially with access to the root loader data" , async ( ) => {
909
909
let res = await fixture . requestDocument ( "/" ) ;
910
910
let html = await res . text ( ) ;
911
911
expect ( html ) . toMatch ( '<h1 data-loading="true">Loading SPA...</h1>' ) ;
@@ -1067,4 +1067,115 @@ test.describe("SPA Mode", () => {
1067
1067
} ) ;
1068
1068
} ) ;
1069
1069
} ) ;
1070
+
1071
+ test ( "only imports the root route in the server build when SSRing index.html" , async ( {
1072
+ page,
1073
+ } ) => {
1074
+ let fixture = await createFixture ( {
1075
+ spaMode : true ,
1076
+ files : {
1077
+ "react-router.config.ts" : reactRouterConfig ( {
1078
+ ssr : false ,
1079
+ } ) ,
1080
+ "vite.config.ts" : js `
1081
+ import { defineConfig } from "vite";
1082
+ import { reactRouter } from "@react-router/dev/vite";
1083
+
1084
+ export default defineConfig({
1085
+ build: { manifest: true },
1086
+ plugins: [reactRouter()],
1087
+ });
1088
+ ` ,
1089
+ "app/routeImportTracker.ts" : js `
1090
+ // this is kinda silly, but this way we can track imports
1091
+ // that happen during SSR and during CSR
1092
+ export async function logImport(url: string) {
1093
+ try {
1094
+ const fs = await import("node:fs");
1095
+ const path = await import("node:path");
1096
+ fs.appendFileSync(path.join(process.cwd(), "ssr-route-imports.txt"), url + "\n");
1097
+ }
1098
+ catch (e) {
1099
+ (window.csrRouteImports ??= []).push(url);
1100
+ }
1101
+ }
1102
+ ` ,
1103
+ "app/root.tsx" : js `
1104
+ import { Links, Meta, Outlet, Scripts } from "react-router";
1105
+ import { logImport } from "./routeImportTracker";
1106
+ logImport("app/root.tsx");
1107
+
1108
+ export default function Root() {
1109
+ return (
1110
+ <html lang="en">
1111
+ <head>
1112
+ <Meta />
1113
+ <Links />
1114
+ </head>
1115
+ <body>
1116
+ hello world
1117
+ <Outlet />
1118
+ <Scripts />
1119
+ </body>
1120
+ </html>
1121
+ );
1122
+ }
1123
+ ` ,
1124
+ "app/routes/_index.tsx" : js `
1125
+ import { logImport } from "../routeImportTracker";
1126
+ logImport("app/routes/_index.tsx");
1127
+
1128
+ // This should not cause an error on SSR because the module is not loaded
1129
+ console.log(window);
1130
+
1131
+ export default function Component() {
1132
+ return "index";
1133
+ }
1134
+ ` ,
1135
+ "app/routes/about.tsx" : js `
1136
+ import * as React from "react";
1137
+ import { logImport } from "../routeImportTracker";
1138
+ logImport("app/routes/about.tsx");
1139
+
1140
+ // This should not cause an error on SSR because the module is not loaded
1141
+ console.log(window);
1142
+
1143
+ export default function Component() {
1144
+ const [mounted, setMounted] = React.useState(false);
1145
+ React.useEffect(() => setMounted(true), []);
1146
+
1147
+ return (
1148
+ <>
1149
+ {!mounted ? <span>Unmounted</span> : <span data-mounted>Mounted</span>}
1150
+ </>
1151
+ );
1152
+ }
1153
+ ` ,
1154
+ } ,
1155
+ } ) ;
1156
+
1157
+ let importedRoutes = (
1158
+ await fs . promises . readFile (
1159
+ path . join ( fixture . projectDir , "ssr-route-imports.txt" ) ,
1160
+ "utf-8"
1161
+ )
1162
+ )
1163
+ . trim ( )
1164
+ . split ( "\n" ) ;
1165
+ expect ( importedRoutes ) . toStrictEqual ( [
1166
+ "app/root.tsx" ,
1167
+ // we should not have imported app/routes/_index.tsx
1168
+ // we should not have imported app/routes/about.tsx
1169
+ ] ) ;
1170
+
1171
+ appFixture = await createAppFixture ( fixture ) ;
1172
+ let app = new PlaywrightFixture ( appFixture , page ) ;
1173
+ await app . goto ( "/about" ) ;
1174
+ await page . waitForSelector ( "[data-mounted]" ) ;
1175
+ // @ts -expect-error
1176
+ expect ( await page . evaluate ( ( ) => window . csrRouteImports ) ) . toStrictEqual ( [
1177
+ "app/root.tsx" ,
1178
+ "app/routes/about.tsx" ,
1179
+ ] ) ;
1180
+ } ) ;
1070
1181
} ) ;
0 commit comments