@tanstack/react-router 1.121.0-alpha.22 → 1.121.0-alpha.28

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 (214) hide show
  1. package/dist/cjs/Asset.cjs +83 -16
  2. package/dist/cjs/Asset.cjs.map +1 -1
  3. package/dist/cjs/Asset.d.cts +2 -1
  4. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  5. package/dist/cjs/ClientOnly.cjs.map +1 -1
  6. package/dist/cjs/ClientOnly.d.cts +1 -1
  7. package/dist/cjs/HeadContent.cjs +19 -17
  8. package/dist/cjs/HeadContent.cjs.map +1 -1
  9. package/dist/cjs/Match.cjs +61 -57
  10. package/dist/cjs/Match.cjs.map +1 -1
  11. package/dist/cjs/Matches.cjs +14 -16
  12. package/dist/cjs/Matches.cjs.map +1 -1
  13. package/dist/cjs/Matches.d.cts +2 -2
  14. package/dist/cjs/RouterProvider.cjs.map +1 -1
  15. package/dist/cjs/SafeFragment.cjs.map +1 -1
  16. package/dist/cjs/ScriptOnce.cjs +3 -10
  17. package/dist/cjs/ScriptOnce.cjs.map +1 -1
  18. package/dist/cjs/ScriptOnce.d.cts +1 -1
  19. package/dist/cjs/Scripts.cjs +7 -11
  20. package/dist/cjs/Scripts.cjs.map +1 -1
  21. package/dist/cjs/ScrollRestoration.cjs +3 -4
  22. package/dist/cjs/ScrollRestoration.cjs.map +1 -1
  23. package/dist/cjs/Transitioner.cjs +16 -15
  24. package/dist/cjs/Transitioner.cjs.map +1 -1
  25. package/dist/cjs/awaited.cjs.map +1 -1
  26. package/dist/cjs/fileRoute.cjs +8 -8
  27. package/dist/cjs/fileRoute.cjs.map +1 -1
  28. package/dist/cjs/index.cjs +0 -12
  29. package/dist/cjs/index.cjs.map +1 -1
  30. package/dist/cjs/index.d.cts +4 -8
  31. package/dist/cjs/lazyRouteComponent.cjs +3 -16
  32. package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
  33. package/dist/cjs/lazyRouteComponent.d.cts +1 -1
  34. package/dist/cjs/link.cjs +106 -74
  35. package/dist/cjs/link.cjs.map +1 -1
  36. package/dist/cjs/link.d.cts +1 -5
  37. package/dist/cjs/matchContext.cjs.map +1 -1
  38. package/dist/cjs/not-found.cjs +2 -4
  39. package/dist/cjs/not-found.cjs.map +1 -1
  40. package/dist/cjs/renderRouteNotFound.cjs.map +1 -1
  41. package/dist/cjs/route.cjs +21 -21
  42. package/dist/cjs/route.cjs.map +1 -1
  43. package/dist/cjs/route.d.cts +14 -6
  44. package/dist/cjs/router.cjs.map +1 -1
  45. package/dist/cjs/routerContext.cjs.map +1 -1
  46. package/dist/cjs/scroll-restoration.cjs +9 -3
  47. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  48. package/dist/cjs/ssr/RouterClient.cjs +25 -0
  49. package/dist/cjs/ssr/RouterClient.cjs.map +1 -0
  50. package/dist/cjs/ssr/RouterClient.d.cts +4 -0
  51. package/dist/cjs/ssr/RouterServer.cjs +9 -0
  52. package/dist/cjs/ssr/RouterServer.cjs.map +1 -0
  53. package/dist/cjs/ssr/RouterServer.d.cts +4 -0
  54. package/dist/cjs/ssr/client.cjs +12 -0
  55. package/dist/cjs/ssr/client.cjs.map +1 -0
  56. package/dist/cjs/ssr/client.d.cts +2 -0
  57. package/dist/cjs/ssr/defaultRenderHandler.cjs +15 -0
  58. package/dist/cjs/ssr/defaultRenderHandler.cjs.map +1 -0
  59. package/dist/cjs/ssr/defaultRenderHandler.d.cts +1 -0
  60. package/dist/cjs/ssr/defaultStreamHandler.cjs +16 -0
  61. package/dist/cjs/ssr/defaultStreamHandler.cjs.map +1 -0
  62. package/dist/cjs/ssr/defaultStreamHandler.d.cts +1 -0
  63. package/dist/cjs/ssr/renderRouterToStream.cjs +63 -0
  64. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -0
  65. package/dist/cjs/ssr/renderRouterToStream.d.cts +8 -0
  66. package/dist/cjs/ssr/renderRouterToString.cjs +28 -0
  67. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -0
  68. package/dist/cjs/ssr/renderRouterToString.d.cts +7 -0
  69. package/dist/cjs/ssr/server.cjs +20 -0
  70. package/dist/cjs/ssr/server.cjs.map +1 -0
  71. package/dist/cjs/ssr/server.d.cts +6 -0
  72. package/dist/cjs/useBlocker.cjs.map +1 -1
  73. package/dist/cjs/useCanGoBack.cjs.map +1 -1
  74. package/dist/cjs/useLoaderData.cjs.map +1 -1
  75. package/dist/cjs/useLoaderDeps.cjs.map +1 -1
  76. package/dist/cjs/useLocation.cjs +1 -1
  77. package/dist/cjs/useLocation.cjs.map +1 -1
  78. package/dist/cjs/useMatch.cjs.map +1 -1
  79. package/dist/cjs/useNavigate.cjs +2 -2
  80. package/dist/cjs/useNavigate.cjs.map +1 -1
  81. package/dist/cjs/useParams.cjs.map +1 -1
  82. package/dist/cjs/useRouter.cjs +1 -1
  83. package/dist/cjs/useRouter.cjs.map +1 -1
  84. package/dist/cjs/useRouterState.cjs +3 -3
  85. package/dist/cjs/useRouterState.cjs.map +1 -1
  86. package/dist/cjs/useSearch.cjs.map +1 -1
  87. package/dist/cjs/utils.cjs +4 -10
  88. package/dist/cjs/utils.cjs.map +1 -1
  89. package/dist/cjs/utils.d.cts +1 -1
  90. package/dist/esm/Asset.d.ts +2 -1
  91. package/dist/esm/Asset.js +66 -16
  92. package/dist/esm/Asset.js.map +1 -1
  93. package/dist/esm/CatchBoundary.js.map +1 -1
  94. package/dist/esm/ClientOnly.d.ts +1 -1
  95. package/dist/esm/ClientOnly.js.map +1 -1
  96. package/dist/esm/HeadContent.js +19 -17
  97. package/dist/esm/HeadContent.js.map +1 -1
  98. package/dist/esm/Match.js +63 -59
  99. package/dist/esm/Match.js.map +1 -1
  100. package/dist/esm/Matches.d.ts +2 -2
  101. package/dist/esm/Matches.js +14 -16
  102. package/dist/esm/Matches.js.map +1 -1
  103. package/dist/esm/RouterProvider.js.map +1 -1
  104. package/dist/esm/SafeFragment.js.map +1 -1
  105. package/dist/esm/ScriptOnce.d.ts +1 -1
  106. package/dist/esm/ScriptOnce.js +3 -10
  107. package/dist/esm/ScriptOnce.js.map +1 -1
  108. package/dist/esm/Scripts.js +7 -11
  109. package/dist/esm/Scripts.js.map +1 -1
  110. package/dist/esm/ScrollRestoration.js +3 -4
  111. package/dist/esm/ScrollRestoration.js.map +1 -1
  112. package/dist/esm/Transitioner.js +16 -15
  113. package/dist/esm/Transitioner.js.map +1 -1
  114. package/dist/esm/awaited.js.map +1 -1
  115. package/dist/esm/fileRoute.js +8 -8
  116. package/dist/esm/fileRoute.js.map +1 -1
  117. package/dist/esm/index.d.ts +4 -8
  118. package/dist/esm/index.js +2 -8
  119. package/dist/esm/index.js.map +1 -1
  120. package/dist/esm/lazyRouteComponent.d.ts +1 -1
  121. package/dist/esm/lazyRouteComponent.js +2 -15
  122. package/dist/esm/lazyRouteComponent.js.map +1 -1
  123. package/dist/esm/link.d.ts +1 -5
  124. package/dist/esm/link.js +107 -75
  125. package/dist/esm/link.js.map +1 -1
  126. package/dist/esm/matchContext.js.map +1 -1
  127. package/dist/esm/not-found.js +2 -4
  128. package/dist/esm/not-found.js.map +1 -1
  129. package/dist/esm/renderRouteNotFound.js.map +1 -1
  130. package/dist/esm/route.d.ts +14 -6
  131. package/dist/esm/route.js +21 -21
  132. package/dist/esm/route.js.map +1 -1
  133. package/dist/esm/router.js.map +1 -1
  134. package/dist/esm/routerContext.js.map +1 -1
  135. package/dist/esm/scroll-restoration.js +9 -3
  136. package/dist/esm/scroll-restoration.js.map +1 -1
  137. package/dist/esm/ssr/RouterClient.d.ts +4 -0
  138. package/dist/esm/ssr/RouterClient.js +25 -0
  139. package/dist/esm/ssr/RouterClient.js.map +1 -0
  140. package/dist/esm/ssr/RouterServer.d.ts +4 -0
  141. package/dist/esm/ssr/RouterServer.js +9 -0
  142. package/dist/esm/ssr/RouterServer.js.map +1 -0
  143. package/dist/esm/ssr/client.d.ts +2 -0
  144. package/dist/esm/ssr/client.js +6 -0
  145. package/dist/esm/ssr/client.js.map +1 -0
  146. package/dist/esm/ssr/defaultRenderHandler.d.ts +1 -0
  147. package/dist/esm/ssr/defaultRenderHandler.js +15 -0
  148. package/dist/esm/ssr/defaultRenderHandler.js.map +1 -0
  149. package/dist/esm/ssr/defaultStreamHandler.d.ts +1 -0
  150. package/dist/esm/ssr/defaultStreamHandler.js +16 -0
  151. package/dist/esm/ssr/defaultStreamHandler.js.map +1 -0
  152. package/dist/esm/ssr/renderRouterToStream.d.ts +8 -0
  153. package/dist/esm/ssr/renderRouterToStream.js +63 -0
  154. package/dist/esm/ssr/renderRouterToStream.js.map +1 -0
  155. package/dist/esm/ssr/renderRouterToString.d.ts +7 -0
  156. package/dist/esm/ssr/renderRouterToString.js +28 -0
  157. package/dist/esm/ssr/renderRouterToString.js.map +1 -0
  158. package/dist/esm/ssr/server.d.ts +6 -0
  159. package/dist/esm/ssr/server.js +14 -0
  160. package/dist/esm/ssr/server.js.map +1 -0
  161. package/dist/esm/useBlocker.js.map +1 -1
  162. package/dist/esm/useCanGoBack.js.map +1 -1
  163. package/dist/esm/useLoaderData.js.map +1 -1
  164. package/dist/esm/useLoaderDeps.js.map +1 -1
  165. package/dist/esm/useLocation.js +1 -1
  166. package/dist/esm/useLocation.js.map +1 -1
  167. package/dist/esm/useMatch.js.map +1 -1
  168. package/dist/esm/useNavigate.js +2 -2
  169. package/dist/esm/useNavigate.js.map +1 -1
  170. package/dist/esm/useParams.js.map +1 -1
  171. package/dist/esm/useRouter.js +1 -1
  172. package/dist/esm/useRouter.js.map +1 -1
  173. package/dist/esm/useRouterState.js +3 -3
  174. package/dist/esm/useRouterState.js.map +1 -1
  175. package/dist/esm/useSearch.js.map +1 -1
  176. package/dist/esm/utils.d.ts +1 -1
  177. package/dist/esm/utils.js +4 -10
  178. package/dist/esm/utils.js.map +1 -1
  179. package/dist/llms/index.d.ts +3 -0
  180. package/dist/llms/index.js +35 -0
  181. package/dist/llms/rules/api.d.ts +2 -0
  182. package/dist/llms/rules/api.js +4326 -0
  183. package/dist/llms/rules/guide.d.ts +2 -0
  184. package/dist/llms/rules/guide.js +7096 -0
  185. package/dist/llms/rules/routing.d.ts +2 -0
  186. package/dist/llms/rules/routing.js +1981 -0
  187. package/dist/llms/rules/setup-and-architecture.d.ts +2 -0
  188. package/dist/llms/rules/setup-and-architecture.js +945 -0
  189. package/package.json +32 -6
  190. package/src/Asset.tsx +95 -16
  191. package/src/ClientOnly.tsx +1 -1
  192. package/src/HeadContent.tsx +16 -0
  193. package/src/Match.tsx +86 -63
  194. package/src/Matches.tsx +24 -17
  195. package/src/ScriptOnce.tsx +2 -14
  196. package/src/Transitioner.tsx +13 -14
  197. package/src/index.tsx +3 -21
  198. package/src/lazyRouteComponent.tsx +6 -31
  199. package/src/link.tsx +130 -99
  200. package/src/not-found.tsx +1 -1
  201. package/src/route.tsx +18 -9
  202. package/src/scroll-restoration.tsx +10 -3
  203. package/src/ssr/RouterClient.tsx +22 -0
  204. package/src/ssr/RouterServer.tsx +9 -0
  205. package/src/ssr/client.ts +2 -0
  206. package/src/ssr/defaultRenderHandler.tsx +12 -0
  207. package/src/ssr/defaultStreamHandler.tsx +13 -0
  208. package/src/ssr/renderRouterToStream.tsx +79 -0
  209. package/src/ssr/renderRouterToString.tsx +31 -0
  210. package/src/ssr/server.ts +6 -0
  211. package/src/utils.ts +6 -14
  212. package/dist/cjs/serializer.d.cts +0 -6
  213. package/dist/esm/serializer.d.ts +0 -6
  214. package/src/serializer.ts +0 -7
package/src/index.tsx CHANGED
@@ -1,6 +1,3 @@
1
- export { default as invariant } from 'tiny-invariant'
2
- export { default as warning } from 'tiny-warning'
3
-
4
1
  export {
5
2
  defer,
6
3
  TSR_DEFERRED_PROMISE,
@@ -24,14 +21,12 @@ export {
24
21
  defaultStringifySearch,
25
22
  parseSearchWith,
26
23
  stringifySearchWith,
27
- escapeJSON, // SSR
28
24
  pick,
29
25
  functionalUpdate,
30
26
  replaceEqualDeep,
31
27
  isPlainObject,
32
28
  isPlainArray,
33
29
  deepEqual,
34
- shallow,
35
30
  createControlledPromise,
36
31
  retainSearchParams,
37
32
  stripSearchParams,
@@ -39,12 +34,6 @@ export {
39
34
 
40
35
  export type {
41
36
  AnyRoute,
42
- StartSerializer,
43
- Serializable,
44
- SerializerParse,
45
- SerializerParseBy,
46
- SerializerStringify,
47
- SerializerStringifyBy,
48
37
  DeferredPromiseState,
49
38
  DeferredPromise,
50
39
  ParsedLocation,
@@ -92,10 +81,6 @@ export type {
92
81
  SearchSerializer,
93
82
  SearchParser,
94
83
  TrailingSlashOption,
95
- ExtractedEntry,
96
- ExtractedStream,
97
- ExtractedPromise,
98
- StreamState,
99
84
  Manifest,
100
85
  RouterManagedTag,
101
86
  ControlledPromise,
@@ -124,12 +109,9 @@ export type {
124
109
  FileRoutesByPath,
125
110
  RouteById,
126
111
  RootRouteOptions,
127
- SerializerExtensions,
128
112
  CreateFileRoute,
129
113
  } from '@tanstack/router-core'
130
114
 
131
- export type * from './serializer'
132
-
133
115
  export {
134
116
  createHistory,
135
117
  createBrowserHistory,
@@ -203,6 +185,7 @@ export type {
203
185
  UseNavigateResult,
204
186
  AnyRedirect,
205
187
  Redirect,
188
+ RedirectOptions,
206
189
  ResolvedRedirect,
207
190
  MakeRouteMatch,
208
191
  MakeRouteMatchUnion,
@@ -226,7 +209,6 @@ export type {
226
209
  ControllablePromise,
227
210
  InjectedHtmlEntry,
228
211
  RouterOptions,
229
- RouterErrorSerializer,
230
212
  RouterState,
231
213
  ListenerFn,
232
214
  BuildNextOptions,
@@ -282,12 +264,12 @@ export {
282
264
  } from './route'
283
265
  export type {
284
266
  AnyRootRoute,
285
- ReactNode,
286
- SyncRouteComponent,
287
267
  AsyncRouteComponent,
288
268
  RouteComponent,
289
269
  ErrorRouteComponent,
290
270
  NotFoundRouteComponent,
271
+ DefaultRouteTypes,
272
+ RouteTypes,
291
273
  } from './route'
292
274
 
293
275
  export { createRouter, Router } from './router'
@@ -1,32 +1,13 @@
1
1
  import * as React from 'react'
2
- import { Outlet } from './Match'
3
- import { ClientOnly } from './ClientOnly'
2
+ import { isModuleNotFoundError } from '@tanstack/router-core'
4
3
  import type { AsyncRouteComponent } from './route'
5
4
 
6
- // If the load fails due to module not found, it may mean a new version of
7
- // the build was deployed and the user's browser is still using an old version.
8
- // If this happens, the old version in the user's browser would have an outdated
9
- // URL to the lazy module.
10
- // In that case, we want to attempt one window refresh to get the latest.
11
- function isModuleNotFoundError(error: any): boolean {
12
- // chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
13
- // firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
14
- // safari: "Importing a module script failed."
15
- if (typeof error?.message !== 'string') return false
16
- return (
17
- error.message.startsWith('Failed to fetch dynamically imported module') ||
18
- error.message.startsWith('error loading dynamically imported module') ||
19
- error.message.startsWith('Importing a module script failed')
20
- )
21
- }
22
-
23
5
  export function lazyRouteComponent<
24
6
  T extends Record<string, any>,
25
7
  TKey extends keyof T = 'default',
26
8
  >(
27
9
  importer: () => Promise<T>,
28
10
  exportName?: TKey,
29
- ssr?: () => boolean,
30
11
  ): T[TKey] extends (props: infer TProps) => any
31
12
  ? AsyncRouteComponent<TProps>
32
13
  : never {
@@ -36,10 +17,6 @@ export function lazyRouteComponent<
36
17
  let reload: boolean
37
18
 
38
19
  const load = () => {
39
- if (typeof document === 'undefined' && ssr?.() === false) {
40
- comp = (() => null) as any
41
- return Promise.resolve()
42
- }
43
20
  if (!loadPromise) {
44
21
  loadPromise = importer()
45
22
  .then((res) => {
@@ -51,6 +28,11 @@ export function lazyRouteComponent<
51
28
  // there's nothing we want to do about module not found during preload.
52
29
  // Record the error, the rest is handled during the render path.
53
30
  error = err
31
+ // If the load fails due to module not found, it may mean a new version of
32
+ // the build was deployed and the user's browser is still using an old version.
33
+ // If this happens, the old version in the user's browser would have an outdated
34
+ // URL to the lazy module.
35
+ // In that case, we want to attempt one window refresh to get the latest.
54
36
  if (isModuleNotFoundError(error)) {
55
37
  if (
56
38
  error instanceof Error &&
@@ -91,13 +73,6 @@ export function lazyRouteComponent<
91
73
  throw load()
92
74
  }
93
75
 
94
- if (ssr?.() === false) {
95
- return (
96
- <ClientOnly fallback={<Outlet />}>
97
- {React.createElement(comp, props)}
98
- </ClientOnly>
99
- )
100
- }
101
76
  return React.createElement(comp, props)
102
77
  }
103
78
 
package/src/link.tsx CHANGED
@@ -10,17 +10,12 @@ import {
10
10
  import { useRouterState } from './useRouterState'
11
11
  import { useRouter } from './useRouter'
12
12
 
13
- import {
14
- useForwardedRef,
15
- useIntersectionObserver,
16
- useLayoutEffect,
17
- } from './utils'
13
+ import { useForwardedRef, useIntersectionObserver } from './utils'
18
14
 
19
15
  import { useMatch } from './useMatch'
20
16
  import type {
21
17
  AnyRouter,
22
18
  Constrain,
23
- LinkCurrentTargetElement,
24
19
  LinkOptions,
25
20
  RegisteredRouter,
26
21
  RoutePaths,
@@ -48,8 +43,8 @@ export function useLinkProps<
48
43
 
49
44
  const {
50
45
  // custom props
51
- activeProps = () => ({ className: 'active' }),
52
- inactiveProps = () => ({}),
46
+ activeProps,
47
+ inactiveProps,
53
48
  activeOptions,
54
49
  to,
55
50
  preload: userPreload,
@@ -71,10 +66,6 @@ export function useLinkProps<
71
66
  onMouseLeave,
72
67
  onTouchStart,
73
68
  ignoreBlocker,
74
- ...rest
75
- } = options
76
-
77
- const {
78
69
  // prevent these from being returned
79
70
  params: _params,
80
71
  search: _search,
@@ -82,8 +73,11 @@ export function useLinkProps<
82
73
  state: _state,
83
74
  mask: _mask,
84
75
  reloadDocument: _reloadDocument,
76
+ unsafeRelative: _unsafeRelative,
77
+ from: _from,
78
+ _fromLocation,
85
79
  ...propsSafeToSpread
86
- } = rest
80
+ } = options
87
81
 
88
82
  // If this link simply reloads the current route,
89
83
  // make sure it has a new key so it will trigger a data refresh
@@ -93,7 +87,7 @@ export function useLinkProps<
93
87
 
94
88
  const type: 'internal' | 'external' = React.useMemo(() => {
95
89
  try {
96
- new URL(`${to}`)
90
+ new URL(to as any)
97
91
  return 'external'
98
92
  } catch {}
99
93
  return 'internal'
@@ -105,33 +99,41 @@ export function useLinkProps<
105
99
  structuralSharing: true as any,
106
100
  })
107
101
 
108
- const nearestFrom = useMatch({
102
+ const from = useMatch({
109
103
  strict: false,
110
- select: (match) => match.fullPath,
104
+ select: (match) => options.from ?? match.fullPath,
111
105
  })
112
106
 
113
- const from = options.from ?? nearestFrom
114
-
115
- // Use it as the default `from` location
116
- options = { ...options, from }
117
-
118
107
  const next = React.useMemo(
119
- () => router.buildLocation(options as any),
108
+ () => router.buildLocation({ ...options, from } as any),
120
109
  // eslint-disable-next-line react-hooks/exhaustive-deps
121
- [router, options, currentSearch],
110
+ [
111
+ router,
112
+ currentSearch,
113
+ options._fromLocation,
114
+ from,
115
+ options.hash,
116
+ options.to,
117
+ options.search,
118
+ options.params,
119
+ options.state,
120
+ options.mask,
121
+ options.unsafeRelative,
122
+ ],
122
123
  )
123
124
 
124
- const preload = React.useMemo(() => {
125
- if (options.reloadDocument) {
126
- return false
127
- }
128
- return userPreload ?? router.options.defaultPreload
129
- }, [router.options.defaultPreload, userPreload, options.reloadDocument])
125
+ const isExternal = type === 'external'
126
+
127
+ const preload =
128
+ options.reloadDocument || isExternal
129
+ ? false
130
+ : (userPreload ?? router.options.defaultPreload)
130
131
  const preloadDelay =
131
132
  userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
132
133
 
133
134
  const isActive = useRouterState({
134
135
  select: (s) => {
136
+ if (isExternal) return false
135
137
  if (activeOptions?.exact) {
136
138
  const testExact = exactPathTest(
137
139
  s.location.pathname,
@@ -145,15 +147,17 @@ export function useLinkProps<
145
147
  const currentPathSplit = removeTrailingSlash(
146
148
  s.location.pathname,
147
149
  router.basepath,
148
- ).split('/')
150
+ )
149
151
  const nextPathSplit = removeTrailingSlash(
150
152
  next.pathname,
151
153
  router.basepath,
152
- ).split('/')
153
-
154
- const pathIsFuzzyEqual = nextPathSplit.every(
155
- (d, i) => d === currentPathSplit[i],
156
154
  )
155
+
156
+ const pathIsFuzzyEqual =
157
+ currentPathSplit.startsWith(nextPathSplit) &&
158
+ (currentPathSplit.length === nextPathSplit.length ||
159
+ currentPathSplit[nextPathSplit.length] === '/')
160
+
157
161
  if (!pathIsFuzzyEqual) {
158
162
  return false
159
163
  }
@@ -176,12 +180,34 @@ export function useLinkProps<
176
180
  },
177
181
  })
178
182
 
179
- const doPreload = React.useCallback(() => {
180
- router.preloadRoute(options as any).catch((err) => {
181
- console.warn(err)
182
- console.warn(preloadWarning)
183
- })
184
- }, [options, router])
183
+ const doPreload = React.useCallback(
184
+ () => {
185
+ router.preloadRoute({ ...options, from } as any).catch((err) => {
186
+ console.warn(err)
187
+ console.warn(preloadWarning)
188
+ })
189
+ },
190
+ // eslint-disable-next-line react-hooks/exhaustive-deps
191
+ [
192
+ router,
193
+ options.to,
194
+ options._fromLocation,
195
+ from,
196
+ options.search,
197
+ options.hash,
198
+ options.params,
199
+ options.state,
200
+ options.mask,
201
+ options.unsafeRelative,
202
+ options.hashScrollIntoView,
203
+ options.href,
204
+ options.ignoreBlocker,
205
+ options.reloadDocument,
206
+ options.replace,
207
+ options.resetScroll,
208
+ options.viewTransition,
209
+ ],
210
+ )
185
211
 
186
212
  const preloadViewportIoCallback = React.useCallback(
187
213
  (entry: IntersectionObserverEntry | undefined) => {
@@ -195,11 +221,11 @@ export function useLinkProps<
195
221
  useIntersectionObserver(
196
222
  innerRef,
197
223
  preloadViewportIoCallback,
198
- { rootMargin: '100px' },
224
+ intersectionObserverOptions,
199
225
  { disabled: !!disabled || !(preload === 'viewport') },
200
226
  )
201
227
 
202
- useLayoutEffect(() => {
228
+ React.useEffect(() => {
203
229
  if (hasRenderFetched.current) {
204
230
  return
205
231
  }
@@ -209,7 +235,7 @@ export function useLinkProps<
209
235
  }
210
236
  }, [disabled, doPreload, preload])
211
237
 
212
- if (type === 'external') {
238
+ if (isExternal) {
213
239
  return {
214
240
  ...propsSafeToSpread,
215
241
  ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
@@ -229,7 +255,7 @@ export function useLinkProps<
229
255
  }
230
256
 
231
257
  // The click handler
232
- const handleClick = (e: MouseEvent) => {
258
+ const handleClick = (e: React.MouseEvent) => {
233
259
  if (
234
260
  !disabled &&
235
261
  !isCtrlEvent(e) &&
@@ -250,8 +276,9 @@ export function useLinkProps<
250
276
 
251
277
  // All is well? Navigate!
252
278
  // N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
253
- return router.navigate({
279
+ router.navigate({
254
280
  ...options,
281
+ from,
255
282
  replace,
256
283
  resetScroll,
257
284
  hashScrollIntoView,
@@ -263,7 +290,7 @@ export function useLinkProps<
263
290
  }
264
291
 
265
292
  // The click handler
266
- const handleFocus = (_: MouseEvent) => {
293
+ const handleFocus = (_: React.MouseEvent) => {
267
294
  if (disabled) return
268
295
  if (preload) {
269
296
  doPreload()
@@ -272,54 +299,44 @@ export function useLinkProps<
272
299
 
273
300
  const handleTouchStart = handleFocus
274
301
 
275
- const handleEnter = (e: MouseEvent) => {
276
- if (disabled) return
277
- const eventTarget = (e.target || {}) as LinkCurrentTargetElement
302
+ const handleEnter = (e: React.MouseEvent) => {
303
+ if (disabled || !preload) return
278
304
 
279
- if (preload) {
280
- if (eventTarget.preloadTimeout) {
305
+ if (!preloadDelay) {
306
+ doPreload()
307
+ } else {
308
+ const eventTarget = e.target
309
+ if (timeoutMap.has(eventTarget)) {
281
310
  return
282
311
  }
283
-
284
- if (!preloadDelay) {
312
+ const id = setTimeout(() => {
313
+ timeoutMap.delete(eventTarget)
285
314
  doPreload()
286
- } else {
287
- eventTarget.preloadTimeout = setTimeout(() => {
288
- eventTarget.preloadTimeout = null
289
- doPreload()
290
- }, preloadDelay)
291
- }
315
+ }, preloadDelay)
316
+ timeoutMap.set(eventTarget, id)
292
317
  }
293
318
  }
294
319
 
295
- const handleLeave = (e: MouseEvent) => {
296
- if (disabled) return
297
- const eventTarget = (e.target || {}) as LinkCurrentTargetElement
298
-
299
- if (eventTarget.preloadTimeout) {
300
- clearTimeout(eventTarget.preloadTimeout)
301
- eventTarget.preloadTimeout = null
320
+ const handleLeave = (e: React.MouseEvent) => {
321
+ if (disabled || !preload || !preloadDelay) return
322
+ const eventTarget = e.target
323
+ const id = timeoutMap.get(eventTarget)
324
+ if (id) {
325
+ clearTimeout(id)
326
+ timeoutMap.delete(eventTarget)
302
327
  }
303
328
  }
304
329
 
305
- const composeHandlers =
306
- (handlers: Array<undefined | ((e: any) => void)>) =>
307
- (e: { persist?: () => void; defaultPrevented: boolean }) => {
308
- e.persist?.()
309
- handlers.filter(Boolean).forEach((handler) => {
310
- if (e.defaultPrevented) return
311
- handler!(e)
312
- })
313
- }
314
-
315
330
  // Get the active props
316
331
  const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
317
- ? (functionalUpdate(activeProps as any, {}) ?? {})
318
- : {}
332
+ ? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
333
+ : STATIC_EMPTY_OBJECT
319
334
 
320
335
  // Get the inactive props
321
336
  const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
322
- isActive ? {} : functionalUpdate(inactiveProps, {})
337
+ isActive
338
+ ? STATIC_EMPTY_OBJECT
339
+ : (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
323
340
 
324
341
  const resolvedClassName = [
325
342
  className,
@@ -329,7 +346,9 @@ export function useLinkProps<
329
346
  .filter(Boolean)
330
347
  .join(' ')
331
348
 
332
- const resolvedStyle = {
349
+ const resolvedStyle = (style ||
350
+ resolvedActiveProps.style ||
351
+ resolvedInactiveProps.style) && {
333
352
  ...style,
334
353
  ...resolvedActiveProps.style,
335
354
  ...resolvedInactiveProps.style,
@@ -352,29 +371,41 @@ export function useLinkProps<
352
371
  onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
353
372
  disabled: !!disabled,
354
373
  target,
355
- ...(Object.keys(resolvedStyle).length && { style: resolvedStyle }),
374
+ ...(resolvedStyle && { style: resolvedStyle }),
356
375
  ...(resolvedClassName && { className: resolvedClassName }),
357
- ...(disabled && {
358
- role: 'link',
359
- 'aria-disabled': true,
360
- }),
361
- ...(isActive && { 'data-status': 'active', 'aria-current': 'page' }),
362
- ...(isTransitioning && { 'data-transitioning': 'transitioning' }),
376
+ ...(disabled && STATIC_DISABLED_PROPS),
377
+ ...(isActive && STATIC_ACTIVE_PROPS),
378
+ ...(isTransitioning && STATIC_TRANSITIONING_PROPS),
363
379
  }
364
380
  }
365
381
 
382
+ const STATIC_EMPTY_OBJECT = {}
383
+ const STATIC_ACTIVE_OBJECT = { className: 'active' }
384
+ const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
385
+ const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
386
+ const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }
387
+
388
+ const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()
389
+
390
+ const intersectionObserverOptions: IntersectionObserverInit = {
391
+ rootMargin: '100px',
392
+ }
393
+
394
+ const composeHandlers =
395
+ (handlers: Array<undefined | React.EventHandler<any>>) =>
396
+ (e: React.SyntheticEvent) => {
397
+ handlers.filter(Boolean).forEach((handler) => {
398
+ if (e.defaultPrevented) return
399
+ handler!(e)
400
+ })
401
+ }
402
+
366
403
  type UseLinkReactProps<TComp> = TComp extends keyof React.JSX.IntrinsicElements
367
404
  ? React.JSX.IntrinsicElements[TComp]
368
- : React.PropsWithoutRef<
369
- TComp extends React.ComponentType<infer TProps> ? TProps : never
370
- > &
371
- React.RefAttributes<
372
- TComp extends
373
- | React.FC<{ ref: React.Ref<infer TRef> }>
374
- | React.Component<{ ref: React.Ref<infer TRef> }>
375
- ? TRef
376
- : never
377
- >
405
+ : TComp extends React.ComponentType<any>
406
+ ? React.ComponentPropsWithoutRef<TComp> &
407
+ React.RefAttributes<React.ComponentRef<TComp>>
408
+ : never
378
409
 
379
410
  export type UseLinkPropsOptions<
380
411
  TRouter extends AnyRouter = RegisteredRouter,
@@ -515,7 +546,7 @@ export const Link: LinkComponent<'a'> = React.forwardRef<Element, any>(
515
546
  })
516
547
  : rest.children
517
548
 
518
- if (typeof _asChild === 'undefined') {
549
+ if (_asChild === undefined) {
519
550
  // the ReturnType of useLinkProps returns the correct type for a <a> element, not a general component that has a disabled prop
520
551
  // @ts-expect-error
521
552
  delete linkProps.disabled
@@ -532,7 +563,7 @@ export const Link: LinkComponent<'a'> = React.forwardRef<Element, any>(
532
563
  },
533
564
  ) as any
534
565
 
535
- function isCtrlEvent(e: MouseEvent) {
566
+ function isCtrlEvent(e: React.MouseEvent) {
536
567
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
537
568
  }
538
569
 
package/src/not-found.tsx CHANGED
@@ -25,7 +25,7 @@ export function CatchNotFound(props: {
25
25
  throw error
26
26
  }
27
27
  }}
28
- errorComponent={({ error }: { error: Error }) => {
28
+ errorComponent={({ error }) => {
29
29
  if (isNotFound(error)) {
30
30
  return props.fallback?.(error)
31
31
  } else {
package/src/route.tsx CHANGED
@@ -54,6 +54,14 @@ declare module '@tanstack/router-core' {
54
54
  pendingComponent?: RouteComponent
55
55
  }
56
56
 
57
+ export interface RootRouteOptionsExtensions {
58
+ shellComponent?: ({
59
+ children,
60
+ }: {
61
+ children: React.ReactNode
62
+ }) => React.ReactNode
63
+ }
64
+
57
65
  export interface RouteExtensions<
58
66
  in out TId extends string,
59
67
  in out TFullPath extends string,
@@ -534,21 +542,22 @@ export function createRouteMask<
534
542
  return opts as any
535
543
  }
536
544
 
537
- export type ReactNode = any
538
-
539
- export type SyncRouteComponent<TProps> =
540
- | ((props: TProps) => ReactNode)
541
- | React.LazyExoticComponent<(props: TProps) => ReactNode>
545
+ export interface DefaultRouteTypes<TProps> {
546
+ component:
547
+ | ((props: TProps) => any)
548
+ | React.LazyExoticComponent<(props: TProps) => any>
549
+ }
550
+ export interface RouteTypes<TProps> extends DefaultRouteTypes<TProps> {}
542
551
 
543
- export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
552
+ export type AsyncRouteComponent<TProps> = RouteTypes<TProps>['component'] & {
544
553
  preload?: () => Promise<void>
545
554
  }
546
555
 
547
- export type RouteComponent<TProps = any> = AsyncRouteComponent<TProps>
556
+ export type RouteComponent = AsyncRouteComponent<{}>
548
557
 
549
- export type ErrorRouteComponent = RouteComponent<ErrorComponentProps>
558
+ export type ErrorRouteComponent = AsyncRouteComponent<ErrorComponentProps>
550
559
 
551
- export type NotFoundRouteComponent = SyncRouteComponent<NotFoundRouteProps>
560
+ export type NotFoundRouteComponent = RouteTypes<NotFoundRouteProps>['component']
552
561
 
553
562
  export class NotFoundRoute<
554
563
  TParentRoute extends AnyRootRoute,
@@ -14,16 +14,23 @@ export function ScrollRestoration() {
14
14
  const resolvedKey =
15
15
  userKey !== defaultGetScrollRestorationKey(router.latestLocation)
16
16
  ? userKey
17
- : null
17
+ : undefined
18
18
 
19
19
  if (!router.isScrollRestoring || !router.isServer) {
20
20
  return null
21
21
  }
22
22
 
23
+ const restoreScrollOptions: Parameters<typeof restoreScroll>[0] = {
24
+ storageKey,
25
+ shouldScrollRestoration: true,
26
+ }
27
+ if (resolvedKey) {
28
+ restoreScrollOptions.key = resolvedKey
29
+ }
30
+
23
31
  return (
24
32
  <ScriptOnce
25
- children={`(${restoreScroll.toString()})(${JSON.stringify(storageKey)},${JSON.stringify(resolvedKey)}, undefined, true)`}
26
- log={false}
33
+ children={`(${restoreScroll.toString()})(${JSON.stringify(restoreScrollOptions)})`}
27
34
  />
28
35
  )
29
36
  }
@@ -0,0 +1,22 @@
1
+ import { hydrate } from '@tanstack/router-core/ssr/client'
2
+ import { Await } from '../awaited'
3
+ import { RouterProvider } from '../RouterProvider'
4
+ import type { AnyRouter } from '@tanstack/router-core'
5
+
6
+ let hydrationPromise: Promise<void | Array<Array<void>>> | undefined
7
+
8
+ export function RouterClient(props: { router: AnyRouter }) {
9
+ if (!hydrationPromise) {
10
+ if (!props.router.state.matches.length) {
11
+ hydrationPromise = hydrate(props.router)
12
+ } else {
13
+ hydrationPromise = Promise.resolve()
14
+ }
15
+ }
16
+ return (
17
+ <Await
18
+ promise={hydrationPromise}
19
+ children={() => <RouterProvider router={props.router} />}
20
+ />
21
+ )
22
+ }
@@ -0,0 +1,9 @@
1
+ import * as React from 'react'
2
+ import { RouterProvider } from '../RouterProvider'
3
+ import type { AnyRouter } from '@tanstack/router-core'
4
+
5
+ export function RouterServer<TRouter extends AnyRouter>(props: {
6
+ router: TRouter
7
+ }) {
8
+ return <RouterProvider router={props.router} />
9
+ }
@@ -0,0 +1,2 @@
1
+ export { RouterClient } from './RouterClient'
2
+ export * from '@tanstack/router-core/ssr/client'
@@ -0,0 +1,12 @@
1
+ import { defineHandlerCallback } from '@tanstack/router-core/ssr/server'
2
+ import { renderRouterToString } from './renderRouterToString'
3
+ import { RouterServer } from './RouterServer'
4
+
5
+ export const defaultRenderHandler = defineHandlerCallback(
6
+ ({ router, responseHeaders }) =>
7
+ renderRouterToString({
8
+ router,
9
+ responseHeaders,
10
+ children: <RouterServer router={router} />,
11
+ }),
12
+ )