@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.99 → 3.4.0-ultramodern.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/cjs/cli/index.js +80 -34
  2. package/dist/cjs/cli/routeSplitting.js +13 -41
  3. package/dist/cjs/cli/tanstackTypes.js +114 -200
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  6. package/dist/cjs/runtime/dataMutation.js +9 -5
  7. package/dist/cjs/runtime/hooks.js +20 -19
  8. package/dist/cjs/runtime/hydrationBoundary.js +12 -7
  9. package/dist/cjs/runtime/index.js +66 -54
  10. package/dist/cjs/runtime/lifecycle.js +21 -91
  11. package/dist/cjs/runtime/loaderBridge.js +173 -0
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +190 -110
  14. package/dist/cjs/runtime/plugin.node.js +25 -40
  15. package/dist/cjs/runtime/plugin.worker.js +9 -5
  16. package/dist/cjs/runtime/pluginCore.js +55 -0
  17. package/dist/cjs/runtime/prefetchLink.js +10 -6
  18. package/dist/cjs/runtime/register.js +56 -0
  19. package/dist/cjs/runtime/routeTree.js +30 -212
  20. package/dist/cjs/runtime/router.js +41 -0
  21. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  22. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  23. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  24. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  25. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  26. package/dist/cjs/runtime/rsc/client.js +9 -5
  27. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  28. package/dist/cjs/runtime/rsc/index.js +9 -5
  29. package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
  30. package/dist/cjs/runtime/rsc/server.js +9 -5
  31. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  32. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  33. package/dist/cjs/runtime/state.js +45 -0
  34. package/dist/cjs/runtime/types.js +13 -9
  35. package/dist/cjs/runtime/utils.js +9 -11
  36. package/dist/cjs/runtime.js +9 -5
  37. package/dist/esm/cli/index.mjs +73 -28
  38. package/dist/esm/cli/routeSplitting.mjs +1 -30
  39. package/dist/esm/cli/tanstackTypes.mjs +100 -190
  40. package/dist/esm/runtime/hooks.mjs +1 -8
  41. package/dist/esm/runtime/hydrationBoundary.mjs +3 -2
  42. package/dist/esm/runtime/index.mjs +4 -2
  43. package/dist/esm/runtime/lifecycle.mjs +1 -82
  44. package/dist/esm/runtime/loaderBridge.mjs +114 -0
  45. package/dist/esm/runtime/outlet.mjs +17 -0
  46. package/dist/esm/runtime/plugin.mjs +183 -107
  47. package/dist/esm/runtime/plugin.node.mjs +19 -38
  48. package/dist/esm/runtime/pluginCore.mjs +14 -0
  49. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  50. package/dist/esm/runtime/register.mjs +18 -0
  51. package/dist/esm/runtime/routeTree.mjs +15 -198
  52. package/dist/esm/runtime/router.mjs +2 -0
  53. package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
  54. package/dist/esm/runtime/state.mjs +7 -0
  55. package/dist/esm/runtime/utils.mjs +0 -6
  56. package/dist/esm-node/cli/index.mjs +73 -28
  57. package/dist/esm-node/cli/routeSplitting.mjs +1 -30
  58. package/dist/esm-node/cli/tanstackTypes.mjs +100 -190
  59. package/dist/esm-node/runtime/hooks.mjs +1 -8
  60. package/dist/esm-node/runtime/hydrationBoundary.mjs +3 -2
  61. package/dist/esm-node/runtime/index.mjs +4 -2
  62. package/dist/esm-node/runtime/lifecycle.mjs +1 -82
  63. package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
  64. package/dist/esm-node/runtime/outlet.mjs +18 -0
  65. package/dist/esm-node/runtime/plugin.mjs +183 -107
  66. package/dist/esm-node/runtime/plugin.node.mjs +19 -38
  67. package/dist/esm-node/runtime/pluginCore.mjs +15 -0
  68. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  69. package/dist/esm-node/runtime/register.mjs +19 -0
  70. package/dist/esm-node/runtime/routeTree.mjs +15 -198
  71. package/dist/esm-node/runtime/router.mjs +3 -0
  72. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
  73. package/dist/esm-node/runtime/state.mjs +8 -0
  74. package/dist/esm-node/runtime/utils.mjs +0 -6
  75. package/dist/types/cli/index.d.ts +11 -2
  76. package/dist/types/cli/routeSplitting.d.ts +6 -15
  77. package/dist/types/cli/tanstackTypes.d.ts +21 -1
  78. package/dist/types/runtime/hooks.d.ts +8 -33
  79. package/dist/types/runtime/index.d.ts +6 -3
  80. package/dist/types/runtime/lifecycle.d.ts +7 -22
  81. package/dist/types/runtime/loaderBridge.d.ts +48 -0
  82. package/dist/types/runtime/outlet.d.ts +2 -0
  83. package/dist/types/runtime/plugin.d.ts +1 -14
  84. package/dist/types/runtime/plugin.node.d.ts +1 -14
  85. package/dist/types/runtime/pluginCore.d.ts +21 -0
  86. package/dist/types/runtime/register.d.ts +9 -0
  87. package/dist/types/runtime/routeTree.d.ts +0 -2
  88. package/dist/types/runtime/router.d.ts +14 -0
  89. package/dist/types/runtime/state.d.ts +16 -0
  90. package/dist/types/runtime/types.d.ts +7 -53
  91. package/package.json +40 -38
  92. package/rstest.config.mts +6 -0
  93. package/src/cli/index.ts +146 -24
  94. package/src/cli/routeSplitting.ts +6 -44
  95. package/src/cli/tanstackTypes.ts +203 -208
  96. package/src/runtime/hooks.ts +10 -27
  97. package/src/runtime/hydrationBoundary.tsx +5 -5
  98. package/src/runtime/index.tsx +12 -14
  99. package/src/runtime/lifecycle.ts +16 -151
  100. package/src/runtime/loaderBridge.ts +257 -0
  101. package/src/runtime/outlet.tsx +48 -0
  102. package/src/runtime/plugin.node.tsx +28 -83
  103. package/src/runtime/plugin.tsx +350 -202
  104. package/src/runtime/pluginCore.ts +48 -0
  105. package/src/runtime/prefetchLink.tsx +1 -1
  106. package/src/runtime/register.ts +58 -0
  107. package/src/runtime/routeTree.ts +41 -365
  108. package/src/runtime/router.ts +15 -0
  109. package/src/runtime/rsc/payloadRouter.ts +45 -2
  110. package/src/runtime/ssr-shim.d.ts +1 -3
  111. package/src/runtime/state.ts +29 -0
  112. package/src/runtime/types.ts +20 -67
  113. package/src/runtime/utils.tsx +3 -7
  114. package/tests/router/cli.test.ts +373 -31
  115. package/tests/router/hooks.test.ts +26 -0
  116. package/tests/router/loaderBridge.test.ts +211 -0
  117. package/tests/router/packageSurface.test.ts +24 -0
  118. package/tests/router/prefetchLink.test.tsx +43 -7
  119. package/tests/router/register.test.ts +46 -0
  120. package/tests/router/routeTree.test.ts +176 -139
  121. package/tests/router/rsc.test.tsx +70 -0
  122. package/tests/router/tanstackTypes.test.ts +395 -7
  123. package/dist/cjs/runtime/DefaultNotFound.js +0 -47
  124. package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
  125. package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
  126. package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
  127. package/src/runtime/DefaultNotFound.tsx +0 -15
@@ -1,16 +1,11 @@
1
1
  // @effect-diagnostics globalConsole:off strictBooleanExpressions:off
2
2
  /// <reference path="./ssr-shim.d.ts" />
3
3
 
4
- import type { Plugin, RuntimePluginExtends } from '@modern-js/plugin';
5
- import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
6
4
  import {
7
5
  getGlobalEnableRsc,
8
- getGlobalLayoutApp,
9
- getGlobalRoutes,
10
6
  InternalRuntimeContext,
11
7
  type TInternalRuntimeContext,
12
8
  } from '@modern-js/runtime/context';
13
- import { merge } from '@modern-js/runtime-utils/merge';
14
9
  import type { RouteObject } from '@modern-js/runtime-utils/router';
15
10
  import { normalizePathname } from '@modern-js/runtime-utils/url';
16
11
  import {
@@ -24,23 +19,21 @@ import {
24
19
  useNavigate,
25
20
  useRouter,
26
21
  } from '@tanstack/react-router';
27
- import { RouterClient } from '@tanstack/react-router/ssr/client';
28
22
  import { useContext, useMemo } from 'react';
29
23
  import { createModernBasepathRewrite } from './basepathRewrite';
30
- import {
31
- modifyRoutes as modifyRoutesHook,
32
- onAfterCreateRouter as onAfterCreateRouterHook,
33
- onAfterHydrateRouter as onAfterHydrateRouterHook,
34
- onBeforeCreateRouter as onBeforeCreateRouterHook,
35
- onBeforeCreateRoutes as onBeforeCreateRoutesHook,
36
- onBeforeHydrateRouter as onBeforeHydrateRouterHook,
37
- type RouterExtendsHooks,
38
- } from './hooks';
24
+ import { routerProviderRegistryHooks } from './hooks';
39
25
  import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
40
26
  import {
41
27
  applyRouterRuntimeState,
42
28
  type RouterLifecycleContext,
43
29
  } from './lifecycle';
30
+ import { withModernRouteMatchContext } from './outlet';
31
+ import {
32
+ getFinalRouteConfig,
33
+ getMergedRouterConfig,
34
+ type TanstackRouterPluginAPI,
35
+ type TanstackRouterRuntimePlugin,
36
+ } from './pluginCore';
44
37
  import { Link } from './prefetchLink';
45
38
  import { createRouteTreeFromRouteObjects } from './routeTree';
46
39
  import { getTanstackRscSerializationAdapters } from './rsc/client';
@@ -56,7 +49,6 @@ const BLOCKING_SUBSCRIBE_SYMBOL = Symbol.for(
56
49
  const BLOCKING_STATE_SYMBOL = Symbol.for(
57
50
  '@modern-js/plugin-tanstack:blocking-state',
58
51
  );
59
-
60
52
  type TanstackRouterWithSubscribe = {
61
53
  [BLOCKING_STATE_SYMBOL]?: () => boolean;
62
54
  [BLOCKING_SUBSCRIBE_SYMBOL]?: boolean;
@@ -70,24 +62,29 @@ type WindowWithTanstackSsr = Window & {
70
62
  $_TSR?: unknown;
71
63
  };
72
64
 
73
- type TanstackRouterRuntimeConfig = {
74
- plugins?: TanstackRouterRuntimePlugin[];
75
- router?: Partial<RouterConfig>;
76
- [key: string]: unknown;
65
+ type RouteComponentPreloadable = {
66
+ load?: () => Promise<unknown>;
67
+ preload?: () => Promise<unknown>;
77
68
  };
78
69
 
79
- type TanstackRouterRuntimeExtends = Required<
80
- RuntimePluginExtends<TanstackRouterRuntimeConfig, TInternalRuntimeContext>
81
- > & {
82
- extendHooks: RouterExtendsHooks;
70
+ type ModernRouteModule = {
71
+ Component?: unknown;
72
+ default?: unknown;
83
73
  };
84
74
 
85
- type TanstackRouterPluginAPI = RuntimePluginAPI<TanstackRouterRuntimeExtends>;
86
-
87
- type TanstackRouterRuntimePlugin = Plugin<
88
- TanstackRouterPluginAPI,
89
- TInternalRuntimeContext
90
- >;
75
+ type RouterWithPreloadableRoutes = AnyRouter & {
76
+ routesById?: Record<
77
+ string,
78
+ {
79
+ options?: {
80
+ component?: unknown;
81
+ staticData?: {
82
+ modernRouteId?: string;
83
+ };
84
+ };
85
+ }
86
+ >;
87
+ };
91
88
 
92
89
  function normalizeBase(b: string) {
93
90
  if (b.length > 1 && b.endsWith('/')) {
@@ -133,18 +130,155 @@ function wrapRouterSubscribeWithBlockState(
133
130
  target[BLOCKING_SUBSCRIBE_SYMBOL] = true;
134
131
  }
135
132
 
136
- function stripSyntheticNotFoundRoute(routes: RouteObject[]): RouteObject[] {
137
- return routes
138
- .filter(route => !(route.path === '*' && !route.id && !route.loader))
139
- .map(route => {
140
- if (!route.children?.length) {
141
- return route;
133
+ type RouterHydrationRecord = {
134
+ error?: unknown;
135
+ promise: Promise<unknown>;
136
+ status: 'pending' | 'fulfilled' | 'rejected';
137
+ };
138
+
139
+ const routerHydrationRecords = new WeakMap<AnyRouter, RouterHydrationRecord>();
140
+ const routeModulesKey = '_routeModules';
141
+
142
+ function pickRouteModuleComponent(
143
+ routeModule: unknown,
144
+ seen: Set<unknown> = new Set(),
145
+ ): unknown {
146
+ if (
147
+ typeof routeModule === 'function' ||
148
+ (routeModule &&
149
+ typeof routeModule === 'object' &&
150
+ '$$typeof' in routeModule)
151
+ ) {
152
+ return routeModule;
153
+ }
154
+
155
+ if (!routeModule || typeof routeModule !== 'object') {
156
+ return undefined;
157
+ }
158
+ if (seen.has(routeModule)) {
159
+ return undefined;
160
+ }
161
+ seen.add(routeModule);
162
+
163
+ const module = routeModule as ModernRouteModule;
164
+ for (const candidate of [module.default, module.Component]) {
165
+ const component = pickRouteModuleComponent(candidate, seen);
166
+ if (component) {
167
+ return component;
168
+ }
169
+ }
170
+
171
+ return undefined;
172
+ }
173
+
174
+ function getCachedRouteModule(routeId: string) {
175
+ if (typeof window === 'undefined') {
176
+ return undefined;
177
+ }
178
+
179
+ return (window as unknown as Record<string, Record<string, unknown>>)[
180
+ routeModulesKey
181
+ ]?.[routeId];
182
+ }
183
+
184
+ function preloadHydratedRouteComponents(router: AnyRouter): Promise<void> {
185
+ const preloadableRouter = router as RouterWithPreloadableRoutes;
186
+ const routesById = preloadableRouter.routesById || {};
187
+ const matches = preloadableRouter.stores.matches.get() as Array<{
188
+ routeId?: string;
189
+ }>;
190
+
191
+ return Promise.all(
192
+ matches.map(match => {
193
+ if (match.routeId === undefined || match.routeId === '') {
194
+ return undefined;
142
195
  }
143
- return {
144
- ...route,
145
- children: stripSyntheticNotFoundRoute(route.children),
146
- };
147
- });
196
+
197
+ const route = routesById[match.routeId];
198
+ const component = route?.options?.component as RouteComponentPreloadable;
199
+ const preload = component?.load || component?.preload;
200
+ if (typeof preload !== 'function') {
201
+ return undefined;
202
+ }
203
+
204
+ return Promise.resolve(preload.call(component)).then(routeModule => {
205
+ const modernRouteId = route?.options?.staticData?.modernRouteId;
206
+ const cachedRouteModule =
207
+ typeof modernRouteId === 'string' && modernRouteId !== ''
208
+ ? getCachedRouteModule(modernRouteId)
209
+ : undefined;
210
+ const resolvedComponent = pickRouteModuleComponent(
211
+ cachedRouteModule ?? routeModule,
212
+ );
213
+ if (
214
+ resolvedComponent !== undefined &&
215
+ typeof modernRouteId === 'string' &&
216
+ modernRouteId !== ''
217
+ ) {
218
+ route.options.component = withModernRouteMatchContext(
219
+ resolvedComponent,
220
+ modernRouteId,
221
+ );
222
+ }
223
+ });
224
+ }),
225
+ ).then(() => undefined);
226
+ }
227
+
228
+ function hydrateTanstackRouter(router: AnyRouter) {
229
+ return import('@tanstack/react-router/ssr/client').then(({ hydrate }) =>
230
+ hydrate(router),
231
+ );
232
+ }
233
+
234
+ function getTanstackSsrHydrationRecord(router: AnyRouter) {
235
+ const existingHydrationRecord = routerHydrationRecords.get(router);
236
+ if (existingHydrationRecord !== undefined) {
237
+ return existingHydrationRecord;
238
+ }
239
+
240
+ const hydrationRecord: RouterHydrationRecord = {
241
+ promise: Promise.resolve(),
242
+ status: 'pending',
243
+ };
244
+ routerHydrationRecords.set(router, hydrationRecord);
245
+ try {
246
+ hydrationRecord.promise = hydrateTanstackRouter(router)
247
+ .then(value => preloadHydratedRouteComponents(router).then(() => value))
248
+ .then(
249
+ value => {
250
+ hydrationRecord.status = 'fulfilled';
251
+ return value;
252
+ },
253
+ error => {
254
+ hydrationRecord.status = 'rejected';
255
+ hydrationRecord.error = error;
256
+ throw error;
257
+ },
258
+ );
259
+ } catch (error) {
260
+ hydrationRecord.status = 'rejected';
261
+ hydrationRecord.error = error;
262
+ hydrationRecord.promise = Promise.reject(error);
263
+ hydrationRecord.promise.catch(() => {});
264
+ }
265
+ return hydrationRecord;
266
+ }
267
+
268
+ function getTanstackSsrHydrationPromise(router: AnyRouter) {
269
+ return getTanstackSsrHydrationRecord(router).promise;
270
+ }
271
+
272
+ function hasTanstackSsrHydrationRecord(router: AnyRouter) {
273
+ return routerHydrationRecords.has(router);
274
+ }
275
+
276
+ function ModernRouterClient({ router }: { router: AnyRouter }) {
277
+ const hydrationRecord = getTanstackSsrHydrationRecord(router);
278
+ if (hydrationRecord.status === 'rejected') {
279
+ throw hydrationRecord.error;
280
+ }
281
+ return <RouterProvider router={router} />;
148
282
  }
149
283
 
150
284
  export const tanstackRouterPlugin = (
@@ -152,35 +286,157 @@ export const tanstackRouterPlugin = (
152
286
  ): TanstackRouterRuntimePlugin => {
153
287
  const plugin: TanstackRouterRuntimePlugin = {
154
288
  name: '@modern-js/plugin-router-tanstack',
155
- registryHooks: {
156
- modifyRoutes: modifyRoutesHook,
157
- onAfterCreateRouter: onAfterCreateRouterHook,
158
- onAfterHydrateRouter: onAfterHydrateRouterHook,
159
- onBeforeCreateRouter: onBeforeCreateRouterHook,
160
- onBeforeCreateRoutes: onBeforeCreateRoutesHook,
161
- onBeforeHydrateRouter: onBeforeHydrateRouterHook,
162
- },
289
+ registryHooks: routerProviderRegistryHooks,
163
290
  setup: (api: TanstackRouterPluginAPI) => {
164
- api.onBeforeRender(context => {
165
- const pluginConfig = api.getRuntimeConfig() as {
166
- router?: Partial<RouterConfig>;
291
+ const hooks = api.getHooks();
292
+ let cachedRouteObjects: RouteObject[] | undefined;
293
+ let cachedRouteTree: ReturnType<
294
+ typeof createRouteTreeFromRouteObjects
295
+ > | null = null;
296
+ let cachedRouter: AnyRouter | null = null;
297
+ let cachedRouterBasepath: string | null = null;
298
+
299
+ const getMergedConfig = () => getMergedRouterConfig(api, userConfig);
300
+
301
+ const getRouteObjects = () => {
302
+ if (typeof cachedRouteObjects !== 'undefined') {
303
+ return cachedRouteObjects;
304
+ }
305
+
306
+ const mergedConfig = getMergedConfig();
307
+ const { createRoutes } = mergedConfig;
308
+ const finalRouteConfig = getFinalRouteConfig(mergedConfig);
309
+
310
+ const routeObjects = createRoutes
311
+ ? createRoutes()
312
+ : createRouteObjectsFromConfig({
313
+ routesConfig: finalRouteConfig,
314
+ }) || [];
315
+
316
+ cachedRouteObjects = hooks.modifyRoutes.call(
317
+ routeObjects,
318
+ ) as RouteObject[];
319
+ return cachedRouteObjects;
320
+ };
321
+
322
+ const getRouteTree = () => {
323
+ if (cachedRouteTree) {
324
+ return cachedRouteTree;
325
+ }
326
+
327
+ const routeObjects = getRouteObjects();
328
+ if (!routeObjects.length) {
329
+ return null;
330
+ }
331
+
332
+ cachedRouteTree = createRouteTreeFromRouteObjects(routeObjects, {
333
+ rscPayloadRouter: getGlobalEnableRsc(),
334
+ });
335
+ return cachedRouteTree;
336
+ };
337
+
338
+ const selectBasePath = (pathname: string) => {
339
+ const { serverBase = [] } = getMergedConfig();
340
+ const match = serverBase.find(baseUrl =>
341
+ isSegmentPrefix(pathname, baseUrl),
342
+ );
343
+ return match || '/';
344
+ };
345
+
346
+ const getClientBasename = (runtimeContext: TInternalRuntimeContext) => {
347
+ const { basename = '' } = getMergedConfig();
348
+ const baseUrl = selectBasePath(location.pathname).replace(/^\/*/, '/');
349
+ return baseUrl === '/'
350
+ ? urlJoin(
351
+ baseUrl,
352
+ runtimeContext._internalRouterBaseName || basename || '',
353
+ )
354
+ : baseUrl;
355
+ };
356
+
357
+ const getRouter = (
358
+ runtimeContext: TInternalRuntimeContext,
359
+ _basename: string,
360
+ ) => {
361
+ const routeTree = getRouteTree();
362
+ if (!routeTree) {
363
+ return null;
364
+ }
365
+
366
+ const lifecycleContext: RouterLifecycleContext = {
367
+ framework: 'tanstack',
368
+ phase: 'client-create',
369
+ routes: getRouteObjects(),
370
+ runtimeContext,
371
+ basename: _basename,
167
372
  };
168
- const mergedConfig = merge(
169
- pluginConfig.router || {},
170
- userConfig,
171
- ) as RouterConfig;
373
+ hooks.onBeforeCreateRouter.call(lifecycleContext);
374
+
375
+ if (cachedRouter && cachedRouterBasepath === _basename) {
376
+ wrapRouterSubscribeWithBlockState(
377
+ cachedRouter,
378
+ runtimeContext.unstable_getBlockNavState,
379
+ );
380
+ hooks.onAfterCreateRouter.call({
381
+ ...lifecycleContext,
382
+ router: cachedRouter,
383
+ runtimeContext,
384
+ });
385
+ return cachedRouter;
386
+ }
387
+
388
+ const mergedConfig = getMergedConfig();
389
+ const { supportHtml5History = true } = mergedConfig;
390
+ const history = supportHtml5History
391
+ ? createBrowserHistory()
392
+ : createHashHistory();
393
+ const rewrite = createModernBasepathRewrite(_basename);
394
+ const serializationAdapters = getGlobalEnableRsc()
395
+ ? getTanstackRscSerializationAdapters()
396
+ : undefined;
397
+
398
+ cachedRouter = createRouter({
399
+ ...getModernTanstackRouterFastDefaults(mergedConfig),
400
+ routeTree,
401
+ basepath: '/',
402
+ rewrite,
403
+ history,
404
+ context: {},
405
+ ...(serializationAdapters ? { serializationAdapters } : {}),
406
+ });
407
+ cachedRouterBasepath = _basename;
408
+ wrapRouterSubscribeWithBlockState(
409
+ cachedRouter,
410
+ runtimeContext.unstable_getBlockNavState,
411
+ );
412
+ hooks.onAfterCreateRouter.call({
413
+ ...lifecycleContext,
414
+ router: cachedRouter,
415
+ runtimeContext,
416
+ });
417
+
418
+ return cachedRouter;
419
+ };
420
+
421
+ api.onBeforeRender(context => {
422
+ const mergedConfig = getMergedConfig();
172
423
  if (
173
424
  typeof window !== 'undefined' &&
174
- (window as { _SSR_DATA?: unknown })._SSR_DATA &&
425
+ (window as { _SSR_DATA?: unknown })._SSR_DATA !== undefined &&
175
426
  mergedConfig.unstable_reloadOnURLMismatch
176
427
  ) {
177
428
  const { ssrContext } = context;
178
429
  const currentPathname = normalizePathname(window.location.pathname);
179
430
  const initialPathname =
180
- ssrContext?.request?.pathname &&
181
- normalizePathname(ssrContext.request.pathname);
431
+ typeof ssrContext?.request?.pathname === 'string'
432
+ ? normalizePathname(ssrContext.request.pathname)
433
+ : undefined;
182
434
 
183
- if (initialPathname && initialPathname !== currentPathname) {
435
+ if (
436
+ initialPathname !== undefined &&
437
+ initialPathname !== '' &&
438
+ initialPathname !== currentPathname
439
+ ) {
184
440
  const errorMsg = `The initial URL ${initialPathname} and the URL ${currentPathname} to be hydrated do not match, reload.`;
185
441
  console.error(errorMsg);
186
442
  window.location.reload();
@@ -194,159 +450,49 @@ export const tanstackRouterPlugin = (
194
450
  useNavigate,
195
451
  useRouter,
196
452
  };
453
+
454
+ const hasSSRBootstrap =
455
+ typeof window !== 'undefined' &&
456
+ Boolean((window as WindowWithTanstackSsr).$_TSR);
457
+ if (hasSSRBootstrap && getRouteObjects().length > 0) {
458
+ const runtimeContext = context as TInternalRuntimeContext;
459
+ const router = getRouter(
460
+ runtimeContext,
461
+ getClientBasename(runtimeContext),
462
+ );
463
+ if (router !== undefined && router !== null) {
464
+ return getTanstackSsrHydrationPromise(router).then(() => undefined);
465
+ }
466
+ }
467
+
468
+ return;
197
469
  });
198
470
 
199
471
  api.wrapRoot(App => {
200
- const mergedConfig = merge(
201
- api.getRuntimeConfig().router || {},
202
- userConfig,
203
- ) as RouterConfig;
204
-
205
- const {
206
- serverBase = [],
207
- supportHtml5History = true,
208
- basename = '',
209
- routesConfig,
210
- createRoutes,
211
- } = mergedConfig;
212
-
213
- const finalRouteConfig = {
214
- routes: getGlobalRoutes(),
215
- globalApp: getGlobalLayoutApp(),
216
- ...routesConfig,
217
- };
218
-
219
- if (!finalRouteConfig.routes && !createRoutes) {
472
+ if (getRouteObjects().length === 0) {
220
473
  return App;
221
474
  }
222
475
 
223
- const hooks = api.getHooks();
224
-
225
- let cachedRouteObjects: RouteObject[] | undefined;
226
-
227
- const getRouteObjects = () => {
228
- if (typeof cachedRouteObjects !== 'undefined') {
229
- return cachedRouteObjects;
230
- }
231
-
232
- const routeObjects = createRoutes
233
- ? createRoutes()
234
- : createRouteObjectsFromConfig({
235
- routesConfig: finalRouteConfig,
236
- }) || [];
237
-
238
- const normalizedRouteObjects = createRoutes
239
- ? routeObjects
240
- : stripSyntheticNotFoundRoute(routeObjects);
241
-
242
- cachedRouteObjects = hooks.modifyRoutes.call(
243
- normalizedRouteObjects,
244
- ) as RouteObject[];
245
- return cachedRouteObjects;
246
- };
247
-
248
- const selectBasePath = (pathname: string) => {
249
- const match = serverBase.find(baseUrl =>
250
- isSegmentPrefix(pathname, baseUrl),
251
- );
252
- return match || '/';
253
- };
254
-
255
- let cachedRouteTree: ReturnType<
256
- typeof createRouteTreeFromRouteObjects
257
- > | null = null;
258
- let cachedRouter: AnyRouter | null = null;
259
- let cachedRouterBasepath: string | null = null;
260
-
261
476
  const RouterWrapper = () => {
262
477
  const runtimeContext = useContext(
263
478
  InternalRuntimeContext,
264
479
  ) as TInternalRuntimeContext;
265
480
 
266
- const baseUrl = selectBasePath(location.pathname).replace(
267
- /^\/*/,
268
- '/',
269
- );
270
- const _basename =
271
- baseUrl === '/'
272
- ? urlJoin(
273
- baseUrl,
274
- runtimeContext._internalRouterBaseName || basename || '',
275
- )
276
- : baseUrl;
277
-
278
- const routeTree = useMemo(() => {
279
- if (cachedRouteTree) {
280
- return cachedRouteTree;
281
- }
282
- const routeObjects = getRouteObjects();
283
- if (!routeObjects.length) {
284
- return null;
285
- }
286
- cachedRouteTree = createRouteTreeFromRouteObjects(routeObjects, {
287
- rscPayloadRouter: getGlobalEnableRsc(),
288
- });
289
- return cachedRouteTree;
290
- }, []);
481
+ const _basename = getClientBasename(runtimeContext);
482
+
483
+ const routeTree = useMemo(() => getRouteTree(), []);
291
484
 
292
485
  if (!routeTree) {
293
486
  return App ? <App /> : null;
294
487
  }
295
488
 
296
- const router = useMemo(() => {
297
- const lifecycleContext: RouterLifecycleContext = {
298
- framework: 'tanstack',
299
- phase: 'client-create',
300
- routes: getRouteObjects(),
301
- runtimeContext,
302
- basename: _basename,
303
- };
304
- hooks.onBeforeCreateRouter.call(lifecycleContext);
305
-
306
- if (cachedRouter && cachedRouterBasepath === _basename) {
307
- wrapRouterSubscribeWithBlockState(
308
- cachedRouter,
309
- runtimeContext.unstable_getBlockNavState,
310
- );
311
- hooks.onAfterCreateRouter.call({
312
- ...lifecycleContext,
313
- router: cachedRouter,
314
- runtimeContext,
315
- });
316
- return cachedRouter;
317
- }
318
-
319
- const history = supportHtml5History
320
- ? createBrowserHistory()
321
- : createHashHistory();
322
-
323
- const rewrite = createModernBasepathRewrite(_basename);
324
- const serializationAdapters = getGlobalEnableRsc()
325
- ? getTanstackRscSerializationAdapters()
326
- : undefined;
327
-
328
- cachedRouter = createRouter({
329
- ...getModernTanstackRouterFastDefaults(mergedConfig),
330
- routeTree,
331
- basepath: '/',
332
- rewrite,
333
- history,
334
- context: {},
335
- ...(serializationAdapters ? { serializationAdapters } : {}),
336
- });
337
- cachedRouterBasepath = _basename;
338
- wrapRouterSubscribeWithBlockState(
339
- cachedRouter,
340
- runtimeContext.unstable_getBlockNavState,
341
- );
342
- hooks.onAfterCreateRouter.call({
343
- ...lifecycleContext,
344
- router: cachedRouter,
345
- runtimeContext,
346
- });
347
-
348
- return cachedRouter;
349
- }, [_basename, routeTree, supportHtml5History, runtimeContext]);
489
+ const router = useMemo(
490
+ () => getRouter(runtimeContext, _basename),
491
+ [_basename, routeTree, runtimeContext],
492
+ );
493
+ if (!router) {
494
+ return App ? <App /> : null;
495
+ }
350
496
  const runtimeState = applyRouterRuntimeState(runtimeContext, {
351
497
  framework: 'tanstack',
352
498
  basename: _basename,
@@ -363,8 +509,10 @@ export const tanstackRouterPlugin = (
363
509
 
364
510
  const hasSSRBootstrap =
365
511
  typeof window !== 'undefined' &&
366
- Boolean((window as WindowWithTanstackSsr).$_TSR);
367
- if (hasSSRBootstrap) {
512
+ (Boolean((window as WindowWithTanstackSsr).$_TSR) ||
513
+ hasTanstackSsrHydrationRecord(router));
514
+ const needsRouterClient = hasSSRBootstrap;
515
+ if (needsRouterClient) {
368
516
  hooks.onBeforeHydrateRouter.call({
369
517
  ...lifecycleContext,
370
518
  phase: 'hydrate',
@@ -373,8 +521,8 @@ export const tanstackRouterPlugin = (
373
521
  });
374
522
  }
375
523
 
376
- const RouterContent = hasSSRBootstrap ? (
377
- <RouterClient router={router} />
524
+ const RouterContent = needsRouterClient ? (
525
+ <ModernRouterClient router={router} />
378
526
  ) : (
379
527
  <RouterProvider router={router} />
380
528
  );
@@ -382,7 +530,7 @@ export const tanstackRouterPlugin = (
382
530
  RouterContent,
383
531
  hasSSRBootstrap,
384
532
  );
385
- if (hasSSRBootstrap) {
533
+ if (needsRouterClient) {
386
534
  hooks.onAfterHydrateRouter.call({
387
535
  ...lifecycleContext,
388
536
  phase: 'hydrate',