@rangojs/router 0.0.0-experimental.002d056c

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 (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,454 @@
1
+ import * as React from "react";
2
+ import { createElement, type ReactNode, type ComponentType } from "react";
3
+ import { OutletProvider } from "./client.js";
4
+ import { MountContextProvider } from "./browser/react/mount-context.js";
5
+ import type {
6
+ ResolvedSegment,
7
+ LoaderDataResult,
8
+ RootLayoutProps,
9
+ } from "./types.js";
10
+ import { isLoaderDataResult } from "./types.js";
11
+ import { invariant } from "./errors.js";
12
+ import {
13
+ RouteContentWrapper,
14
+ LoaderBoundary,
15
+ } from "./route-content-wrapper.js";
16
+ import { RootErrorBoundary } from "./root-error-boundary.js";
17
+
18
+ // ViewTransition is only available in React experimental.
19
+ // Access via namespace import to avoid compile-time errors on stable React.
20
+ const ReactViewTransition: any =
21
+ "ViewTransition" in React ? (React as any).ViewTransition : null;
22
+
23
+ /**
24
+ * Resolve loader data from raw results, unwrapping LoaderDataResult wrappers
25
+ */
26
+ function resolveLoaderData(
27
+ resolvedData: any[],
28
+ loaderIds: string[],
29
+ ): { loaderData: Record<string, any>; errorFallback: ReactNode } {
30
+ const loaderData: Record<string, any> = {};
31
+ let errorFallback: ReactNode = null;
32
+
33
+ for (let i = 0; i < loaderIds.length; i++) {
34
+ const id = loaderIds[i];
35
+ const result = resolvedData[i];
36
+
37
+ if (!isLoaderDataResult(result)) {
38
+ // Legacy format - direct data
39
+ loaderData[id] = result;
40
+ continue;
41
+ }
42
+
43
+ if (result.ok) {
44
+ loaderData[id] = result.data;
45
+ continue;
46
+ }
47
+
48
+ // Error case
49
+ if (result.fallback) {
50
+ errorFallback = result.fallback;
51
+ } else {
52
+ throw new Error(result.error.message);
53
+ }
54
+ }
55
+
56
+ return { loaderData, errorFallback };
57
+ }
58
+
59
+ /**
60
+ * Options for renderSegments
61
+ */
62
+ export interface RenderSegmentsOptions {
63
+ /**
64
+ * If true, this render is for a server action response.
65
+ * In browser during actions, we await component promises to prevent
66
+ * UI flickering/suspense during optimistic updates.
67
+ */
68
+ isAction?: boolean;
69
+
70
+ /**
71
+ * If true, force awaiting all loaders instead of streaming with Suspense.
72
+ * Used for popstate (back/forward) navigation where we want instant rendering
73
+ * from cache without showing loading skeletons.
74
+ */
75
+ forceAwait?: boolean;
76
+
77
+ /**
78
+ * Intercept segments to inject into the tree.
79
+ * These are parallel segments from intercept routes that need to be
80
+ * associated with their parent layout's named outlet.
81
+ *
82
+ * Passed separately for explicit handling - makes the flow clearer
83
+ * and easier to debug than relying on ID pattern matching.
84
+ */
85
+ interceptSegments?: ResolvedSegment[];
86
+
87
+ /**
88
+ * Root layout component that wraps the entire application.
89
+ * When provided, wraps both route content and the error boundary,
90
+ * preventing the app shell from unmounting during errors (avoids FOUC).
91
+ */
92
+ rootLayout?: ComponentType<RootLayoutProps>;
93
+ }
94
+
95
+ /**
96
+ * Render segments into a React tree with proper layout nesting
97
+ *
98
+ * Layouts nest using OutletProvider, while route + parallel + error + notFound segments
99
+ * render as siblings in a Fragment.
100
+ *
101
+ * Error segments are treated like route segments - they render their fallback
102
+ * component in place of the failed segment. When an error occurs in a handler,
103
+ * loader, or middleware, the router creates an error segment with the nearest
104
+ * error boundary's fallback component.
105
+ *
106
+ * NotFound segments are similar to error segments but are triggered by
107
+ * DataNotFoundError (thrown via notFound()). They render the nearest
108
+ * notFoundBoundary's fallback component.
109
+ *
110
+ * @param segments - Array of resolved segments to render
111
+ * @returns ReactNode representing the component tree
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const segments = [
116
+ * { id: 'L0.0', type: 'layout', component: <RootLayout /> },
117
+ * { id: 'L1.0', type: 'layout', component: <BlogLayout /> },
118
+ * { id: 'R2.0', type: 'route', component: <BlogPost /> },
119
+ * { id: 'P3.0', type: 'parallel', component: <Sidebar />, slot: '@sidebar' }
120
+ * ];
121
+ *
122
+ * const tree = renderSegments(segments);
123
+ * // Results in:
124
+ * // <OutletProvider><RootLayout>
125
+ * // <OutletProvider><BlogLayout>
126
+ * // <><BlogPost /><Sidebar /></>
127
+ * // </BlogLayout></OutletProvider>
128
+ * // </RootLayout></OutletProvider>
129
+ *
130
+ * // For server actions, pass isAction to await components:
131
+ * const tree = renderSegments(segments, { isAction: true });
132
+ * ```
133
+ */
134
+ export async function renderSegments(
135
+ segments: ResolvedSegment[],
136
+ options?: RenderSegmentsOptions,
137
+ ): Promise<ReactNode> {
138
+ const {
139
+ isAction,
140
+ interceptSegments,
141
+ forceAwait,
142
+ rootLayout: RootLayout,
143
+ } = options || {};
144
+
145
+ const temporalLazyRefs: Promise<any>[] = [];
146
+
147
+ /**
148
+ * Registers promises from lazy/async components for awaiting.
149
+ * Handles both direct promises and React lazy components (which store promise in _payload).
150
+ */
151
+ const registerLazyRef = <T,>(node: T): T => {
152
+ if (node instanceof Promise) {
153
+ temporalLazyRefs.push(node);
154
+ } else if (isLazyComponent(node)) {
155
+ temporalLazyRefs.push(node._payload);
156
+ }
157
+ return node;
158
+ };
159
+
160
+ // Type guard for React lazy component internals
161
+ function isLazyComponent(node: unknown): node is { _payload: Promise<any> } {
162
+ return (
163
+ node != null &&
164
+ typeof node === "object" &&
165
+ "_payload" in node &&
166
+ (node as any)._payload instanceof Promise
167
+ );
168
+ }
169
+ // Separate segments by type, passing intercept segments for explicit injection
170
+ const tree = segmentTreeWalk(segments, interceptSegments);
171
+ // Render content segments as siblings
172
+ let content: ReactNode = null;
173
+ for (const node of tree) {
174
+ invariant(
175
+ node.segment.type === "layout" ||
176
+ node.segment.type === "route" ||
177
+ node.segment.type === "error" ||
178
+ node.segment.type === "notFound",
179
+ `Expected layout, route, error, or notFound segment, got ${node.segment.type}`,
180
+ );
181
+ const { component, id, params, loading } = node.segment;
182
+
183
+ // Only include params in key for segments that belong to the route
184
+ // - Routes: always include params (they render param-specific content)
185
+ // - Error/notFound segments: always include params (they replace failed route content)
186
+ // - Route's layouts (orphans): include params (children of parameterized route)
187
+ // - Parent chain layouts: exclude params (shared across routes, param-agnostic)
188
+ // This prevents unnecessary unmounting when params change
189
+ const includeParams =
190
+ node.segment.type === "route" ||
191
+ node.segment.type === "error" ||
192
+ node.segment.type === "notFound" ||
193
+ (node.segment.type === "layout" && node.segment.belongsToRoute);
194
+
195
+ const paramStr =
196
+ includeParams && params && Object.keys(params).length > 0
197
+ ? Object.entries(params)
198
+ .sort(([a], [b]) => a.localeCompare(b))
199
+ .map(([k, v]) => `${k}=${v}`)
200
+ .join(",")
201
+ : "";
202
+ const key = `${paramStr ? `${id}-${paramStr}` : id}`;
203
+
204
+ // Get loader entries for this node
205
+ const loaderEntries = node.loaders.filter(
206
+ (loader) => loader.loaderId && loader.loaderData !== undefined,
207
+ );
208
+
209
+ // Determine the component content (with or without Suspense wrapper)
210
+ // Wrap when loading skeleton defined OR component is Promise (needs Suspense)
211
+ // During actions, await component Promise to prevent Suspense from triggering
212
+ // This keeps existing content visible instead of showing loading skeleton
213
+ let resolvedComponent = component;
214
+ if (isAction && component instanceof Promise) {
215
+ resolvedComponent = await component;
216
+ }
217
+
218
+ let nodeContent: ReactNode =
219
+ loading !== null && loading !== undefined && loading !== false
220
+ ? createElement(RouteContentWrapper, {
221
+ key: `suspense-loading-${id}`,
222
+ content:
223
+ resolvedComponent instanceof Promise
224
+ ? resolvedComponent
225
+ : Promise.resolve(resolvedComponent),
226
+ fallback: loading,
227
+ segmentId: id,
228
+ })
229
+ : registerLazyRef(resolvedComponent);
230
+
231
+ // Wrap with <ViewTransition> if transition config exists (React experimental only).
232
+ // An empty config ({}) creates a bare <ViewTransition> boundary that participates
233
+ // in transitions without adding custom animation classes. Named element-level
234
+ // <ViewTransition> components inside (with name/share props) morph independently
235
+ // from the parent's default cross-fade.
236
+ if (ReactViewTransition && node.segment.transition) {
237
+ nodeContent = createElement(ReactViewTransition, {
238
+ ...node.segment.transition,
239
+ children: nodeContent,
240
+ });
241
+ }
242
+
243
+ // Common props for OutletProvider
244
+ const outletContent: ReactNode =
245
+ node.segment.type === "layout" ? content : null;
246
+
247
+ // Prepare loader data if there are loaders
248
+ const loaderIds = loaderEntries.map((loader) => loader.loaderId!);
249
+ const loaderDataPromise =
250
+ loaderEntries.length > 0
251
+ ? Promise.all(
252
+ loaderEntries.map((loader) =>
253
+ loader.loaderData instanceof Promise
254
+ ? loader.loaderData
255
+ : Promise.resolve(loader.loaderData),
256
+ ),
257
+ )
258
+ : Promise.resolve([]);
259
+
260
+ // Use LoaderBoundary when loading is defined to maintain consistent tree structure
261
+ // This ensures cached segments (which may not have loader segments) have the same
262
+ // tree structure as fresh segments, preventing React remounts
263
+ // If forceAwait or isAction is set, pre-resolve promises so LoaderBoundary won't suspend
264
+ if (loading !== undefined && loading !== null) {
265
+ content = createElement(LoaderBoundary, {
266
+ key: `loader-boundary-${key}`,
267
+ loaderDataPromise:
268
+ forceAwait || isAction ? await loaderDataPromise : loaderDataPromise,
269
+ loaderIds,
270
+ fallback: loading,
271
+ outletKey: key,
272
+ outletContent,
273
+ segment: node.segment,
274
+ parallel: node.parallel,
275
+ children: nodeContent,
276
+ });
277
+ } else if (loaderEntries.length === 0) {
278
+ // No loaders, no loading - simple OutletProvider
279
+ content = createElement(OutletProvider, {
280
+ key,
281
+ content: outletContent,
282
+ segment: node.segment,
283
+ parallel: node.parallel,
284
+ children: nodeContent,
285
+ });
286
+ } else {
287
+ // Has loaders but no loading skeleton - await loaders and render directly
288
+ const resolvedData = await loaderDataPromise;
289
+ const { loaderData, errorFallback } = resolveLoaderData(
290
+ resolvedData,
291
+ loaderIds,
292
+ );
293
+
294
+ content = createElement(OutletProvider, {
295
+ key,
296
+ content: outletContent,
297
+ segment: node.segment,
298
+ parallel: node.parallel,
299
+ loaderData: Object.keys(loaderData).length > 0 ? loaderData : undefined,
300
+ children: errorFallback ?? nodeContent,
301
+ });
302
+ }
303
+
304
+ // Wrap with MountContextProvider for include() scoped components.
305
+ // Must use MountContextProvider (a proper "use client" export) instead of
306
+ // MountContext.Provider directly, because .Provider is a property on the
307
+ // context object and resolves to undefined through RSC client reference proxies.
308
+ if (node.segment.mountPath) {
309
+ content = createElement(MountContextProvider, {
310
+ value: node.segment.mountPath,
311
+ children: content,
312
+ });
313
+ }
314
+ }
315
+
316
+ // Always wrap with root error boundary to prevent white screens
317
+ // This catches any unhandled errors that bubble up from the segment tree
318
+ const errorBoundaryWrapped = createElement(RootErrorBoundary, {
319
+ children: content,
320
+ });
321
+ if (typeof window === "object") {
322
+ await Promise.allSettled(temporalLazyRefs);
323
+ }
324
+
325
+ // Build the final result, optionally wrapped with root layout
326
+ let result: ReactNode = errorBoundaryWrapped;
327
+
328
+ // If rootLayout is provided, wrap the error boundary with it
329
+ // This ensures the app shell stays mounted even during errors (prevents FOUC)
330
+ if (RootLayout) {
331
+ result = createElement(RootLayout, {
332
+ children: errorBoundaryWrapped,
333
+ });
334
+ }
335
+
336
+ return result;
337
+ }
338
+
339
+ /**
340
+ * Walk segments in bottom-to-top order for React nesting
341
+ *
342
+ * Segments from match() are in top-to-bottom order (root → leaf):
343
+ * Example: [L0, L0L0, L0R1L0.@sidebar, L0R1L0, L0R1]
344
+ *
345
+ * For proper React rendering, we need bottom-to-top (leaf → root):
346
+ * - Innermost content (route) wraps inside layouts
347
+ * - Each layer provides context via OutletProvider
348
+ * - Outer layouts receive inner content via <Outlet />
349
+ *
350
+ * Parallel segments must be matched to their parent by ID prefix:
351
+ * - "L0R1L0.@sidebar" belongs to "L0R1L0"
352
+ * - Pre-grouping prevents parallels from attaching to wrong parents
353
+ * during the reversed iteration (which would cause "L0R1L0.@sidebar"
354
+ * to incorrectly attach to "L0L0" instead of "L0R1L0")
355
+ *
356
+ * Loader segments are also grouped by parent:
357
+ * - "L0D0.cart" belongs to "L0"
358
+ * - Loaders don't render directly, their data is passed to context
359
+ *
360
+ * Intercept segments are passed separately for explicit handling:
361
+ * - They are injected into the correct parent's parallel array
362
+ * - This makes the flow clearer than relying on ID pattern matching
363
+ *
364
+ * @param segments - Main segments from the route tree
365
+ * @param interceptSegments - Optional intercept segments to inject
366
+ */
367
+ function* segmentTreeWalk(
368
+ segments: ResolvedSegment[],
369
+ interceptSegments?: ResolvedSegment[],
370
+ ): Generator<{
371
+ segment: ResolvedSegment;
372
+ parallel: ResolvedSegment[];
373
+ loaders: ResolvedSegment[];
374
+ }> {
375
+ // Pre-group parallel and loader segments by their parent ID using prefix matching
376
+ // This ensures each parallel/loader is associated with the correct parent segment
377
+ // regardless of the iteration order
378
+ const parallelsByParent = new Map<string, ResolvedSegment[]>();
379
+ const loadersByParent = new Map<string, ResolvedSegment[]>();
380
+ const nonParallels: ResolvedSegment[] = [];
381
+
382
+ for (const segment of segments) {
383
+ if (segment.type === "parallel") {
384
+ // Extract parent ID from parallel ID
385
+ // Example: "L0R1L0.@sidebar" → "L0R1L0"
386
+ const parentId = segment.id.split(".")[0];
387
+ if (!parallelsByParent.has(parentId)) {
388
+ parallelsByParent.set(parentId, []);
389
+ }
390
+ parallelsByParent.get(parentId)!.push(segment);
391
+ } else if (segment.type === "loader") {
392
+ // Extract parent ID from loader ID
393
+ // Example: "L0D0.cart" → "L0"
394
+ // Loader ID format: {parentShortCode}D{index}.{loaderId}
395
+ const parentId = segment.id.split("D")[0];
396
+ if (!loadersByParent.has(parentId)) {
397
+ loadersByParent.set(parentId, []);
398
+ }
399
+ loadersByParent.get(parentId)!.push(segment);
400
+ } else {
401
+ // Layout, route, error, and notFound segments are all rendered in the tree
402
+ // Error/notFound segments replace the failed segment with fallback UI
403
+ nonParallels.push(segment);
404
+ }
405
+ }
406
+
407
+ // INTERCEPT SEGMENTS: Explicitly inject into parent's parallel array
408
+ // Intercept segments are passed separately for explicit handling
409
+ if (interceptSegments && interceptSegments.length > 0) {
410
+ for (const intercept of interceptSegments) {
411
+ if (intercept.type === "parallel" && intercept.slot) {
412
+ // Extract parent ID from intercept ID (e.g., "M4L0L0L2.@modal" → "M4L0L0L2")
413
+ const parentId = intercept.id.split(".")[0];
414
+ if (!parallelsByParent.has(parentId)) {
415
+ parallelsByParent.set(parentId, []);
416
+ }
417
+ parallelsByParent.get(parentId)!.push(intercept);
418
+ } else if (intercept.type === "loader") {
419
+ // Intercept loaders - extract parent from loader ID
420
+ const parentId = intercept.id.split("D")[0];
421
+ if (!loadersByParent.has(parentId)) {
422
+ loadersByParent.set(parentId, []);
423
+ }
424
+ loadersByParent.get(parentId)!.push(intercept);
425
+ }
426
+ }
427
+ }
428
+
429
+ // Segments arrive in root-to-leaf order from the server (resolveSegment
430
+ // and resolveSegmentWithRevalidation push segments in this order).
431
+ // All consumers (reconcileSegments, cache) preserve this order.
432
+ // No sorting needed — iterate bottom-to-top to process leaf segments first.
433
+ // This processes route/leaf layouts first, then parent layouts
434
+ // Note: We reverse the array to iterate from end to start (bottom-to-top)
435
+
436
+ for (let i = nonParallels.length - 1; i >= 0; i--) {
437
+ const segment = nonParallels[i];
438
+
439
+ // Lookup parallels and loaders that belong to this segment by ID prefix
440
+ const parallel = parallelsByParent.get(segment.id) || [];
441
+ const loaders = loadersByParent.get(segment.id) || [];
442
+
443
+ // Also include loaders from parallel segments (e.g., intercept loaders)
444
+ // These have parent IDs like "M9L0L1.@modal" which match the parallel segment ID
445
+ for (const p of parallel) {
446
+ const parallelLoaders = loadersByParent.get(p.id);
447
+ if (parallelLoaders) {
448
+ loaders.push(...parallelLoaders);
449
+ }
450
+ }
451
+
452
+ yield { segment, parallel, loaders };
453
+ }
454
+ }