@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81

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 (316) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +5091 -941
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +61 -52
  7. package/skills/breadcrumbs/SKILL.md +250 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +167 -0
  14. package/skills/handler-use/SKILL.md +362 -0
  15. package/skills/hooks/SKILL.md +340 -72
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/intercept/SKILL.md +151 -8
  18. package/skills/layout/SKILL.md +122 -3
  19. package/skills/links/SKILL.md +92 -31
  20. package/skills/loader/SKILL.md +404 -44
  21. package/skills/middleware/SKILL.md +205 -37
  22. package/skills/migrate-nextjs/SKILL.md +560 -0
  23. package/skills/migrate-react-router/SKILL.md +765 -0
  24. package/skills/mime-routes/SKILL.md +128 -0
  25. package/skills/parallel/SKILL.md +263 -1
  26. package/skills/prerender/SKILL.md +685 -0
  27. package/skills/rango/SKILL.md +87 -16
  28. package/skills/response-routes/SKILL.md +411 -0
  29. package/skills/route/SKILL.md +281 -14
  30. package/skills/router-setup/SKILL.md +210 -32
  31. package/skills/tailwind/SKILL.md +129 -0
  32. package/skills/theme/SKILL.md +9 -8
  33. package/skills/typesafety/SKILL.md +328 -89
  34. package/skills/use-cache/SKILL.md +324 -0
  35. package/src/__internal.ts +102 -4
  36. package/src/bin/rango.ts +321 -0
  37. package/src/browser/action-coordinator.ts +97 -0
  38. package/src/browser/action-response-classifier.ts +99 -0
  39. package/src/browser/app-version.ts +14 -0
  40. package/src/browser/event-controller.ts +92 -64
  41. package/src/browser/history-state.ts +80 -0
  42. package/src/browser/intercept-utils.ts +52 -0
  43. package/src/browser/link-interceptor.ts +24 -4
  44. package/src/browser/logging.ts +55 -0
  45. package/src/browser/merge-segment-loaders.ts +20 -12
  46. package/src/browser/navigation-bridge.ts +317 -560
  47. package/src/browser/navigation-client.ts +206 -68
  48. package/src/browser/navigation-store.ts +73 -55
  49. package/src/browser/navigation-transaction.ts +297 -0
  50. package/src/browser/network-error-handler.ts +61 -0
  51. package/src/browser/partial-update.ts +343 -316
  52. package/src/browser/prefetch/cache.ts +216 -0
  53. package/src/browser/prefetch/fetch.ts +206 -0
  54. package/src/browser/prefetch/observer.ts +65 -0
  55. package/src/browser/prefetch/policy.ts +48 -0
  56. package/src/browser/prefetch/queue.ts +160 -0
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +112 -0
  59. package/src/browser/react/Link.tsx +253 -74
  60. package/src/browser/react/NavigationProvider.tsx +91 -11
  61. package/src/browser/react/context.ts +11 -0
  62. package/src/browser/react/filter-segment-order.ts +11 -0
  63. package/src/browser/react/index.ts +12 -12
  64. package/src/browser/react/location-state-shared.ts +95 -53
  65. package/src/browser/react/location-state.ts +60 -15
  66. package/src/browser/react/mount-context.ts +6 -1
  67. package/src/browser/react/nonce-context.ts +23 -0
  68. package/src/browser/react/shallow-equal.ts +27 -0
  69. package/src/browser/react/use-action.ts +29 -51
  70. package/src/browser/react/use-client-cache.ts +5 -3
  71. package/src/browser/react/use-handle.ts +30 -126
  72. package/src/browser/react/use-href.tsx +2 -2
  73. package/src/browser/react/use-link-status.ts +6 -5
  74. package/src/browser/react/use-navigation.ts +44 -65
  75. package/src/browser/react/use-params.ts +75 -0
  76. package/src/browser/react/use-pathname.ts +47 -0
  77. package/src/browser/react/use-router.ts +76 -0
  78. package/src/browser/react/use-search-params.ts +56 -0
  79. package/src/browser/react/use-segments.ts +80 -97
  80. package/src/browser/response-adapter.ts +73 -0
  81. package/src/browser/rsc-router.tsx +214 -58
  82. package/src/browser/scroll-restoration.ts +127 -52
  83. package/src/browser/segment-reconciler.ts +243 -0
  84. package/src/browser/segment-structure-assert.ts +16 -0
  85. package/src/browser/server-action-bridge.ts +510 -603
  86. package/src/browser/shallow.ts +6 -1
  87. package/src/browser/types.ts +141 -48
  88. package/src/browser/validate-redirect-origin.ts +29 -0
  89. package/src/build/generate-manifest.ts +235 -24
  90. package/src/build/generate-route-types.ts +39 -0
  91. package/src/build/index.ts +13 -0
  92. package/src/build/route-trie.ts +291 -0
  93. package/src/build/route-types/ast-helpers.ts +25 -0
  94. package/src/build/route-types/ast-route-extraction.ts +98 -0
  95. package/src/build/route-types/codegen.ts +102 -0
  96. package/src/build/route-types/include-resolution.ts +418 -0
  97. package/src/build/route-types/param-extraction.ts +48 -0
  98. package/src/build/route-types/per-module-writer.ts +128 -0
  99. package/src/build/route-types/router-processing.ts +618 -0
  100. package/src/build/route-types/scan-filter.ts +85 -0
  101. package/src/build/runtime-discovery.ts +231 -0
  102. package/src/cache/background-task.ts +34 -0
  103. package/src/cache/cache-key-utils.ts +44 -0
  104. package/src/cache/cache-policy.ts +125 -0
  105. package/src/cache/cache-runtime.ts +342 -0
  106. package/src/cache/cache-scope.ts +167 -309
  107. package/src/cache/cf/cf-cache-store.ts +571 -17
  108. package/src/cache/cf/index.ts +13 -3
  109. package/src/cache/document-cache.ts +116 -77
  110. package/src/cache/handle-capture.ts +81 -0
  111. package/src/cache/handle-snapshot.ts +41 -0
  112. package/src/cache/index.ts +1 -15
  113. package/src/cache/memory-segment-store.ts +191 -13
  114. package/src/cache/profile-registry.ts +73 -0
  115. package/src/cache/read-through-swr.ts +134 -0
  116. package/src/cache/segment-codec.ts +256 -0
  117. package/src/cache/taint.ts +153 -0
  118. package/src/cache/types.ts +72 -122
  119. package/src/client.rsc.tsx +3 -1
  120. package/src/client.tsx +135 -301
  121. package/src/component-utils.ts +4 -4
  122. package/src/components/DefaultDocument.tsx +5 -1
  123. package/src/context-var.ts +156 -0
  124. package/src/debug.ts +19 -9
  125. package/src/errors.ts +108 -2
  126. package/src/handle.ts +55 -29
  127. package/src/handles/MetaTags.tsx +73 -20
  128. package/src/handles/breadcrumbs.ts +66 -0
  129. package/src/handles/index.ts +1 -0
  130. package/src/handles/meta.ts +30 -13
  131. package/src/host/cookie-handler.ts +21 -15
  132. package/src/host/errors.ts +8 -8
  133. package/src/host/index.ts +4 -7
  134. package/src/host/pattern-matcher.ts +27 -27
  135. package/src/host/router.ts +61 -39
  136. package/src/host/testing.ts +8 -8
  137. package/src/host/types.ts +15 -7
  138. package/src/host/utils.ts +1 -1
  139. package/src/href-client.ts +119 -29
  140. package/src/index.rsc.ts +155 -19
  141. package/src/index.ts +251 -30
  142. package/src/internal-debug.ts +11 -0
  143. package/src/loader.rsc.ts +26 -157
  144. package/src/loader.ts +27 -10
  145. package/src/network-error-thrower.tsx +3 -1
  146. package/src/outlet-provider.tsx +45 -0
  147. package/src/prerender/param-hash.ts +37 -0
  148. package/src/prerender/store.ts +186 -0
  149. package/src/prerender.ts +524 -0
  150. package/src/reverse.ts +354 -0
  151. package/src/root-error-boundary.tsx +41 -29
  152. package/src/route-content-wrapper.tsx +7 -4
  153. package/src/route-definition/dsl-helpers.ts +1121 -0
  154. package/src/route-definition/helper-factories.ts +200 -0
  155. package/src/route-definition/helpers-types.ts +478 -0
  156. package/src/route-definition/index.ts +55 -0
  157. package/src/route-definition/redirect.ts +101 -0
  158. package/src/route-definition/resolve-handler-use.ts +149 -0
  159. package/src/route-definition.ts +1 -1428
  160. package/src/route-map-builder.ts +217 -123
  161. package/src/route-name.ts +53 -0
  162. package/src/route-types.ts +77 -8
  163. package/src/router/content-negotiation.ts +215 -0
  164. package/src/router/debug-manifest.ts +72 -0
  165. package/src/router/error-handling.ts +9 -9
  166. package/src/router/find-match.ts +160 -0
  167. package/src/router/handler-context.ts +438 -86
  168. package/src/router/intercept-resolution.ts +402 -0
  169. package/src/router/lazy-includes.ts +237 -0
  170. package/src/router/loader-resolution.ts +356 -128
  171. package/src/router/logging.ts +251 -0
  172. package/src/router/manifest.ts +163 -35
  173. package/src/router/match-api.ts +555 -0
  174. package/src/router/match-context.ts +5 -3
  175. package/src/router/match-handlers.ts +440 -0
  176. package/src/router/match-middleware/background-revalidation.ts +108 -93
  177. package/src/router/match-middleware/cache-lookup.ts +460 -10
  178. package/src/router/match-middleware/cache-store.ts +98 -26
  179. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  180. package/src/router/match-middleware/segment-resolution.ts +80 -6
  181. package/src/router/match-pipelines.ts +10 -45
  182. package/src/router/match-result.ts +135 -35
  183. package/src/router/metrics.ts +240 -15
  184. package/src/router/middleware-cookies.ts +55 -0
  185. package/src/router/middleware-types.ts +220 -0
  186. package/src/router/middleware.ts +324 -369
  187. package/src/router/navigation-snapshot.ts +182 -0
  188. package/src/router/pattern-matching.ts +211 -43
  189. package/src/router/prerender-match.ts +502 -0
  190. package/src/router/preview-match.ts +98 -0
  191. package/src/router/request-classification.ts +310 -0
  192. package/src/router/revalidation.ts +137 -38
  193. package/src/router/route-snapshot.ts +245 -0
  194. package/src/router/router-context.ts +41 -21
  195. package/src/router/router-interfaces.ts +484 -0
  196. package/src/router/router-options.ts +618 -0
  197. package/src/router/router-registry.ts +24 -0
  198. package/src/router/segment-resolution/fresh.ts +748 -0
  199. package/src/router/segment-resolution/helpers.ts +268 -0
  200. package/src/router/segment-resolution/loader-cache.ts +199 -0
  201. package/src/router/segment-resolution/revalidation.ts +1379 -0
  202. package/src/router/segment-resolution/static-store.ts +67 -0
  203. package/src/router/segment-resolution.ts +21 -0
  204. package/src/router/segment-wrappers.ts +291 -0
  205. package/src/router/telemetry-otel.ts +299 -0
  206. package/src/router/telemetry.ts +300 -0
  207. package/src/router/timeout.ts +148 -0
  208. package/src/router/trie-matching.ts +239 -0
  209. package/src/router/types.ts +78 -3
  210. package/src/router.ts +740 -4252
  211. package/src/rsc/handler-context.ts +45 -0
  212. package/src/rsc/handler.ts +907 -797
  213. package/src/rsc/helpers.ts +140 -6
  214. package/src/rsc/index.ts +0 -20
  215. package/src/rsc/loader-fetch.ts +229 -0
  216. package/src/rsc/manifest-init.ts +90 -0
  217. package/src/rsc/nonce.ts +14 -0
  218. package/src/rsc/origin-guard.ts +141 -0
  219. package/src/rsc/progressive-enhancement.ts +393 -0
  220. package/src/rsc/response-error.ts +37 -0
  221. package/src/rsc/response-route-handler.ts +347 -0
  222. package/src/rsc/rsc-rendering.ts +246 -0
  223. package/src/rsc/runtime-warnings.ts +42 -0
  224. package/src/rsc/server-action.ts +358 -0
  225. package/src/rsc/ssr-setup.ts +128 -0
  226. package/src/rsc/types.ts +46 -11
  227. package/src/search-params.ts +230 -0
  228. package/src/segment-content-promise.ts +67 -0
  229. package/src/segment-loader-promise.ts +122 -0
  230. package/src/segment-system.tsx +134 -36
  231. package/src/server/context.ts +341 -61
  232. package/src/server/cookie-store.ts +190 -0
  233. package/src/server/fetchable-loader-store.ts +37 -0
  234. package/src/server/handle-store.ts +113 -15
  235. package/src/server/loader-registry.ts +24 -64
  236. package/src/server/request-context.ts +607 -81
  237. package/src/server.ts +35 -130
  238. package/src/ssr/index.tsx +103 -30
  239. package/src/static-handler.ts +126 -0
  240. package/src/theme/ThemeProvider.tsx +21 -15
  241. package/src/theme/ThemeScript.tsx +5 -5
  242. package/src/theme/constants.ts +5 -2
  243. package/src/theme/index.ts +4 -14
  244. package/src/theme/theme-context.ts +4 -30
  245. package/src/theme/theme-script.ts +21 -18
  246. package/src/types/boundaries.ts +158 -0
  247. package/src/types/cache-types.ts +198 -0
  248. package/src/types/error-types.ts +192 -0
  249. package/src/types/global-namespace.ts +100 -0
  250. package/src/types/handler-context.ts +791 -0
  251. package/src/types/index.ts +88 -0
  252. package/src/types/loader-types.ts +210 -0
  253. package/src/types/route-config.ts +170 -0
  254. package/src/types/route-entry.ts +120 -0
  255. package/src/types/segments.ts +150 -0
  256. package/src/types.ts +1 -1623
  257. package/src/urls/include-helper.ts +207 -0
  258. package/src/urls/index.ts +53 -0
  259. package/src/urls/path-helper-types.ts +372 -0
  260. package/src/urls/path-helper.ts +364 -0
  261. package/src/urls/pattern-types.ts +107 -0
  262. package/src/urls/response-types.ts +116 -0
  263. package/src/urls/type-extraction.ts +372 -0
  264. package/src/urls/urls-function.ts +98 -0
  265. package/src/urls.ts +1 -802
  266. package/src/use-loader.tsx +161 -81
  267. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  268. package/src/vite/discovery/discover-routers.ts +348 -0
  269. package/src/vite/discovery/prerender-collection.ts +439 -0
  270. package/src/vite/discovery/route-types-writer.ts +258 -0
  271. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  272. package/src/vite/discovery/state.ts +117 -0
  273. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  274. package/src/vite/index.ts +15 -1133
  275. package/src/vite/plugin-types.ts +103 -0
  276. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  277. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  278. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  279. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  280. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  281. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  282. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  283. package/src/vite/plugins/expose-id-utils.ts +299 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -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 +786 -0
  290. package/src/vite/plugins/performance-tracks.ts +88 -0
  291. package/src/vite/plugins/refresh-cmd.ts +127 -0
  292. package/src/vite/plugins/use-cache-transform.ts +323 -0
  293. package/src/vite/plugins/version-injector.ts +83 -0
  294. package/src/vite/plugins/version-plugin.ts +266 -0
  295. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +462 -0
  298. package/src/vite/router-discovery.ts +977 -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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  304. package/src/vite/utils/prerender-utils.ts +221 -0
  305. package/src/vite/utils/shared-utils.ts +170 -0
  306. package/CLAUDE.md +0 -43
  307. package/src/browser/lru-cache.ts +0 -69
  308. package/src/browser/request-controller.ts +0 -164
  309. package/src/cache/memory-store.ts +0 -253
  310. package/src/href-context.ts +0 -33
  311. package/src/href.ts +0 -255
  312. package/src/server/route-manifest-cache.ts +0 -173
  313. package/src/vite/expose-handle-id.ts +0 -209
  314. package/src/vite/expose-loader-id.ts +0 -426
  315. package/src/vite/expose-location-state-id.ts +0 -177
  316. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,748 @@
1
+ /**
2
+ * Fresh Path Segment Resolution
3
+ *
4
+ * Functions for resolving segments during a full (non-revalidation) request.
5
+ * Handles loaders, layouts, routes, parallels, orphan layouts, and error boundaries.
6
+ */
7
+
8
+ import type { ReactNode } from "react";
9
+ import { invariant } from "../../errors";
10
+ import {
11
+ getParallelEntries,
12
+ getParallelSlotEntries,
13
+ type EntryData,
14
+ } from "../../server/context";
15
+ import type {
16
+ HandlerContext,
17
+ InternalHandlerContext,
18
+ ResolvedSegment,
19
+ } from "../../types";
20
+ import type { SegmentResolutionDeps } from "../types.js";
21
+ import { resolveLoaderData } from "./loader-cache.js";
22
+ import { _getRequestContext } from "../../server/request-context.js";
23
+ import { appendMetric } from "../metrics.js";
24
+ import {
25
+ handleHandlerResult,
26
+ tryStaticHandler,
27
+ tryStaticSlot,
28
+ resolveLayoutComponent,
29
+ resolveWithErrorBoundary,
30
+ } from "./helpers.js";
31
+ import { getRouterContext } from "../router-context.js";
32
+ import { resolveSink, safeEmit } from "../telemetry.js";
33
+ import {
34
+ track,
35
+ RSCRouterContext,
36
+ runInsideLoaderScope,
37
+ } from "../../server/context.js";
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Streamed handler telemetry
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Attach a fire-and-forget rejection observer to a streamed handler promise.
45
+ * React catches the actual error via its error boundary; this only emits
46
+ * the handler.error telemetry event.
47
+ */
48
+ function observeStreamedHandler(
49
+ promise: Promise<ReactNode>,
50
+ segmentId: string,
51
+ segmentType: string,
52
+ pathname?: string,
53
+ routeKey?: string,
54
+ params?: Record<string, string>,
55
+ ): void {
56
+ let routerCtx;
57
+ try {
58
+ routerCtx = getRouterContext();
59
+ } catch {
60
+ return;
61
+ }
62
+ if (!routerCtx?.telemetry) return;
63
+ const sink = resolveSink(routerCtx.telemetry);
64
+ const reqId = routerCtx.requestId;
65
+ promise.catch((err: unknown) => {
66
+ const errorObj = err instanceof Error ? err : new Error(String(err));
67
+ safeEmit(sink, {
68
+ type: "handler.error",
69
+ timestamp: performance.now(),
70
+ requestId: reqId,
71
+ segmentId,
72
+ segmentType,
73
+ error: errorObj,
74
+ handledByBoundary: true,
75
+ pathname,
76
+ routeKey,
77
+ params,
78
+ });
79
+ });
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Fresh path (full match, no revalidation)
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Resolve loaders for an entry and emit segments.
88
+ * Loaders are run lazily via ctx.use() and memoized for parallel execution.
89
+ */
90
+ export async function resolveLoaders<TEnv>(
91
+ entry: EntryData,
92
+ ctx: HandlerContext<any, TEnv>,
93
+ belongsToRoute: boolean,
94
+ deps: SegmentResolutionDeps<TEnv>,
95
+ shortCodeOverride?: string,
96
+ ): Promise<ResolvedSegment[]> {
97
+ const loaderEntries = entry.loader ?? [];
98
+ if (loaderEntries.length === 0) return [];
99
+
100
+ const shortCode = shortCodeOverride ?? entry.shortCode;
101
+ const hasLoading = "loading" in entry && entry.loading !== undefined;
102
+ const loadingDisabled = hasLoading && entry.loading === false;
103
+ const ms = _getRequestContext()?._metricsStore;
104
+
105
+ if (!loadingDisabled) {
106
+ // Streaming loaders: promises kick off now, settle during RSC serialization.
107
+ const segments = loaderEntries.map((loaderEntry, i) => {
108
+ const { loader } = loaderEntry;
109
+ const segmentId = `${shortCode}D${i}.${loader.$$id}`;
110
+ return {
111
+ id: segmentId,
112
+ namespace: entry.id,
113
+ type: "loader" as const,
114
+ index: i,
115
+ component: null,
116
+ params: ctx.params,
117
+ loaderId: loader.$$id,
118
+ loaderData: deps.wrapLoaderPromise(
119
+ runInsideLoaderScope(() =>
120
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
121
+ ),
122
+ entry,
123
+ segmentId,
124
+ ctx.pathname,
125
+ ),
126
+ belongsToRoute,
127
+ };
128
+ });
129
+
130
+ return segments;
131
+ }
132
+
133
+ // Loading disabled: still start all loaders in parallel, but only emit
134
+ // settled promises so handlers don't stream loading placeholders.
135
+ const pendingLoaderData = loaderEntries.map((loaderEntry) => {
136
+ const start = performance.now();
137
+ const promise = runInsideLoaderScope(() =>
138
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
139
+ );
140
+ return { promise, start, loaderId: loaderEntry.loader.$$id };
141
+ });
142
+ await Promise.all(pendingLoaderData.map((p) => p.promise));
143
+
144
+ return loaderEntries.map((loaderEntry, i) => {
145
+ const { loader } = loaderEntry;
146
+ const segmentId = `${shortCode}D${i}.${loader.$$id}`;
147
+ const pending = pendingLoaderData[i]!;
148
+ if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
149
+ // All loaders ran in parallel via Promise.all — each span covers
150
+ // from its own kickoff to the batch settlement, giving a ceiling
151
+ // on that loader's contribution to the overall wait.
152
+ const batchEnd = performance.now();
153
+ appendMetric(
154
+ ms,
155
+ `loader:${loader.$$id}`,
156
+ pending.start,
157
+ batchEnd - pending.start,
158
+ 2,
159
+ );
160
+ }
161
+ return {
162
+ id: segmentId,
163
+ namespace: entry.id,
164
+ type: "loader" as const,
165
+ index: i,
166
+ component: null,
167
+ params: ctx.params,
168
+ loaderId: loader.$$id,
169
+ loaderData: deps.wrapLoaderPromise(
170
+ pending.promise,
171
+ entry,
172
+ segmentId,
173
+ ctx.pathname,
174
+ ),
175
+ belongsToRoute,
176
+ };
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Options for segment resolution.
182
+ */
183
+ export interface ResolveSegmentOptions {
184
+ /** When true, skip resolveLoaders() calls (used for pre-rendering) */
185
+ skipLoaders?: boolean;
186
+ }
187
+
188
+ /**
189
+ * Resolve segments from EntryData.
190
+ * Executes middlewares, loaders, parallels, and handlers in correct order.
191
+ * Returns array: [main segment, ...orphan layout segments]
192
+ */
193
+ export async function resolveSegment<TEnv>(
194
+ entry: EntryData,
195
+ routeKey: string,
196
+ params: Record<string, string>,
197
+ context: HandlerContext<any, TEnv>,
198
+ loaderPromises: Map<string, Promise<any>>,
199
+ deps: SegmentResolutionDeps<TEnv>,
200
+ isRouteEntry: boolean = false,
201
+ options?: ResolveSegmentOptions,
202
+ ): Promise<ResolvedSegment[]> {
203
+ const segments: ResolvedSegment[] = [];
204
+
205
+ if (entry.type === "layout" || entry.type === "cache") {
206
+ if (!options?.skipLoaders) {
207
+ const loaderSegments = await resolveLoaders(entry, context, false, deps);
208
+ segments.push(...loaderSegments);
209
+ }
210
+
211
+ // Handler-first: layout handler executes before its parallels and orphan
212
+ // layouts so that ctx.set() values are visible to all children.
213
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
214
+ entry.shortCode;
215
+
216
+ const doneLayoutHandler = track(`handler:${entry.id}`, 2);
217
+ const component = await resolveLayoutComponent(entry, context);
218
+ doneLayoutHandler();
219
+
220
+ segments.push({
221
+ id: entry.shortCode,
222
+ namespace: entry.id,
223
+ type: "layout",
224
+ index: 0,
225
+ component,
226
+ loading: entry.loading === false ? null : entry.loading,
227
+ transition: entry.transition,
228
+ params,
229
+ belongsToRoute: false,
230
+ layoutName: entry.id,
231
+ ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
232
+ });
233
+
234
+ const resolvedParallelEntries = new Set<string>();
235
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
236
+ entry.parallel,
237
+ )) {
238
+ const parallelSegments = await resolveParallelEntry(
239
+ parallelEntry,
240
+ params,
241
+ context,
242
+ false,
243
+ entry.shortCode,
244
+ deps,
245
+ options,
246
+ routeKey,
247
+ [slot],
248
+ !resolvedParallelEntries.has(parallelEntry.id),
249
+ );
250
+ segments.push(...parallelSegments);
251
+ resolvedParallelEntries.add(parallelEntry.id);
252
+ }
253
+
254
+ for (const orphan of entry.layout) {
255
+ const orphanSegments = await resolveOrphanLayout(
256
+ orphan,
257
+ params,
258
+ context,
259
+ loaderPromises,
260
+ false,
261
+ deps,
262
+ options,
263
+ routeKey,
264
+ );
265
+ segments.push(...orphanSegments);
266
+ }
267
+ } else if (entry.type === "route") {
268
+ if (!options?.skipLoaders) {
269
+ const loaderSegments = await resolveLoaders(entry, context, true, deps);
270
+ segments.push(...loaderSegments);
271
+ }
272
+
273
+ // Route handler EXECUTES before its children (orphan layouts, parallels).
274
+ // This lets the handler set() context variables that children can read
275
+ // via get(). Caching wraps all segments together (per-route, not
276
+ // per-segment), so either all run or none do -- no partial scenarios.
277
+ //
278
+ // The handler's segment is PUSHED after orphans/parallels to preserve
279
+ // the correct tree composition order (layouts wrap the route content).
280
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
281
+ entry.shortCode;
282
+ let component: ReactNode | undefined = await tryStaticHandler(
283
+ entry,
284
+ entry.shortCode,
285
+ );
286
+ if (component === undefined) {
287
+ // For Passthrough routes at runtime, use the live handler instead of
288
+ // the build handler. At build time (context.build === true), always
289
+ // use the build handler from entry.handler.
290
+ const handler =
291
+ !context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
292
+ const doneRouteHandler = track(`handler:${entry.id}`, 2);
293
+ if (entry.loading) {
294
+ const result = handleHandlerResult(handler(context));
295
+ if (result instanceof Promise) {
296
+ result.finally(doneRouteHandler).catch(() => {});
297
+ const tracked = deps.trackHandler(result, {
298
+ segmentId: entry.shortCode,
299
+ segmentType: entry.type,
300
+ });
301
+ observeStreamedHandler(
302
+ tracked,
303
+ entry.shortCode,
304
+ entry.type,
305
+ context.pathname,
306
+ routeKey,
307
+ params,
308
+ );
309
+ component = tracked;
310
+ } else {
311
+ doneRouteHandler();
312
+ component = result;
313
+ }
314
+ } else {
315
+ component = handleHandlerResult(await handler(context));
316
+ doneRouteHandler();
317
+ }
318
+ }
319
+
320
+ for (const orphan of entry.layout) {
321
+ const orphanSegments = await resolveOrphanLayout(
322
+ orphan,
323
+ params,
324
+ context,
325
+ loaderPromises,
326
+ true,
327
+ deps,
328
+ options,
329
+ routeKey,
330
+ entry,
331
+ );
332
+ segments.push(...orphanSegments);
333
+ }
334
+
335
+ const resolvedParallelEntries = new Set<string>();
336
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
337
+ entry.parallel,
338
+ )) {
339
+ const parallelSegments = await resolveParallelEntry(
340
+ parallelEntry,
341
+ params,
342
+ context,
343
+ true,
344
+ entry.shortCode,
345
+ deps,
346
+ options,
347
+ routeKey,
348
+ [slot],
349
+ !resolvedParallelEntries.has(parallelEntry.id),
350
+ );
351
+ segments.push(...parallelSegments);
352
+ resolvedParallelEntries.add(parallelEntry.id);
353
+ }
354
+
355
+ segments.push({
356
+ id: entry.shortCode,
357
+ namespace: entry.id,
358
+ type: "route",
359
+ index: 0,
360
+ component: component ?? null,
361
+ loading: entry.loading === false ? null : entry.loading,
362
+ transition: entry.transition,
363
+ params,
364
+ belongsToRoute: true,
365
+ ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
366
+ });
367
+ } else {
368
+ throw new Error(`Unknown entry type: ${(entry as any).type}`);
369
+ }
370
+
371
+ return segments;
372
+ }
373
+
374
+ /**
375
+ * Resolve orphan layout with its middlewares, loaders, and parallels.
376
+ */
377
+ export async function resolveOrphanLayout<TEnv>(
378
+ orphan: EntryData,
379
+ params: Record<string, string>,
380
+ context: HandlerContext<any, TEnv>,
381
+ loaderPromises: Map<string, Promise<any>>,
382
+ belongsToRoute: boolean,
383
+ deps: SegmentResolutionDeps<TEnv>,
384
+ options?: ResolveSegmentOptions,
385
+ routeKey?: string,
386
+ /** Parent route entry — its loaders are inherited by the layout so
387
+ * parallel slots inside this layout can access them via useLoader(). */
388
+ parentRouteEntry?: EntryData,
389
+ ): Promise<ResolvedSegment[]> {
390
+ invariant(
391
+ orphan.type === "layout" || orphan.type === "cache",
392
+ `Expected orphan to be a layout or cache, got: ${orphan.type}`,
393
+ );
394
+
395
+ const segments: ResolvedSegment[] = [];
396
+ if (!options?.skipLoaders) {
397
+ const loaderSegments = await resolveLoaders(
398
+ orphan,
399
+ context,
400
+ belongsToRoute,
401
+ deps,
402
+ );
403
+ segments.push(...loaderSegments);
404
+
405
+ // Inherit parent route's loaders so parallel slots inside this layout
406
+ // can access them via useLoader(). Without this, the route's loaders
407
+ // are only in the route's OutletProvider (rendered as <Outlet /> content),
408
+ // which is a child — not a parent — of the layout's context.
409
+ if (
410
+ parentRouteEntry &&
411
+ parentRouteEntry.loader &&
412
+ parentRouteEntry.loader.length > 0 &&
413
+ Object.keys(orphan.parallel).length > 0
414
+ ) {
415
+ const inheritedLoaders = await resolveLoaders(
416
+ parentRouteEntry,
417
+ context,
418
+ belongsToRoute,
419
+ deps,
420
+ orphan.shortCode,
421
+ );
422
+ // Tag as inherited so buildMatchResult can deduplicate when safe
423
+ for (const s of inheritedLoaders) {
424
+ s._inherited = true;
425
+ }
426
+ segments.push(...inheritedLoaders);
427
+ }
428
+ }
429
+
430
+ // Handler-first: orphan layout handler executes before its parallels
431
+ // so that ctx.set() values are visible to parallel children.
432
+ const doneOrphanHandler = track(`handler:${orphan.id}`, 2);
433
+ const component = await resolveLayoutComponent(orphan, context);
434
+ doneOrphanHandler();
435
+
436
+ segments.push({
437
+ id: orphan.shortCode,
438
+ namespace: orphan.id,
439
+ type: "layout",
440
+ index: 0,
441
+ component,
442
+ params,
443
+ belongsToRoute,
444
+ layoutName: orphan.id,
445
+ loading: orphan.loading === false ? null : orphan.loading,
446
+ transition: orphan.transition,
447
+ ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
448
+ });
449
+
450
+ const resolvedParallelEntries = new Set<string>();
451
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
452
+ orphan.parallel,
453
+ )) {
454
+ const parallelSegments = await resolveParallelEntry(
455
+ parallelEntry,
456
+ params,
457
+ context,
458
+ belongsToRoute,
459
+ orphan.shortCode,
460
+ deps,
461
+ options,
462
+ routeKey,
463
+ [slot],
464
+ !resolvedParallelEntries.has(parallelEntry.id),
465
+ );
466
+ segments.push(...parallelSegments);
467
+ resolvedParallelEntries.add(parallelEntry.id);
468
+ }
469
+
470
+ return segments;
471
+ }
472
+
473
+ /**
474
+ * Resolve parallel EntryData with its loaders and slot handlers.
475
+ */
476
+ export async function resolveParallelEntry<TEnv>(
477
+ parallelEntry: EntryData,
478
+ params: Record<string, string>,
479
+ context: HandlerContext<any, TEnv>,
480
+ belongsToRoute: boolean,
481
+ parentShortCode: string,
482
+ deps: SegmentResolutionDeps<TEnv>,
483
+ options?: ResolveSegmentOptions,
484
+ routeKey?: string,
485
+ slotNames?: `@${string}`[],
486
+ includeLoaders: boolean = true,
487
+ ): Promise<ResolvedSegment[]> {
488
+ invariant(
489
+ parallelEntry.type === "parallel",
490
+ `Expected parallel entry, got: ${parallelEntry.type}`,
491
+ );
492
+
493
+ const segments: ResolvedSegment[] = [];
494
+
495
+ const slots = parallelEntry.handler as Record<
496
+ `@${string}`,
497
+ | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
498
+ | ReactNode
499
+ >;
500
+
501
+ const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
502
+
503
+ for (const slot of slotsToResolve) {
504
+ // Try static lookup first — in production, handler bodies are evicted
505
+ // and replaced with stubs that have no .handler property (undefined).
506
+ // The static store holds the pre-rendered component for these slots.
507
+ let component: ReactNode | undefined = await tryStaticSlot(
508
+ parallelEntry,
509
+ slot,
510
+ `${parentShortCode}.${slot}`,
511
+ );
512
+
513
+ if (component === undefined) {
514
+ const handler = slots[slot];
515
+ if (handler === undefined) {
516
+ continue;
517
+ }
518
+ const doneParallelHandler = track(
519
+ `handler:${parallelEntry.id}.${slot}`,
520
+ 2,
521
+ );
522
+ const hasLoadingFallback =
523
+ parallelEntry.loading !== undefined && parallelEntry.loading !== false;
524
+ if (hasLoadingFallback) {
525
+ const result =
526
+ typeof handler === "function" ? handler(context) : handler;
527
+ if (result instanceof Promise) {
528
+ result.finally(doneParallelHandler).catch(() => {});
529
+ const tracked = deps.trackHandler(result, {
530
+ segmentId: `${parentShortCode}.${slot}`,
531
+ segmentType: "parallel",
532
+ });
533
+ observeStreamedHandler(
534
+ tracked,
535
+ `${parentShortCode}.${slot}`,
536
+ "parallel",
537
+ context.pathname,
538
+ routeKey,
539
+ params,
540
+ );
541
+ component = tracked as ReactNode;
542
+ } else {
543
+ doneParallelHandler();
544
+ component = result as ReactNode;
545
+ }
546
+ } else {
547
+ component =
548
+ typeof handler === "function" ? await handler(context) : handler;
549
+ doneParallelHandler();
550
+ }
551
+ }
552
+
553
+ segments.push({
554
+ id: `${parentShortCode}.${slot}`,
555
+ namespace: parallelEntry.id,
556
+ type: "parallel",
557
+ index: 0,
558
+ component,
559
+ loading: parallelEntry.loading === false ? null : parallelEntry.loading,
560
+ transition: parallelEntry.transition,
561
+ params,
562
+ slot,
563
+ belongsToRoute,
564
+ parallelName: `${parallelEntry.id}.${slot}`,
565
+ ...(parallelEntry.mountPath
566
+ ? { mountPath: parallelEntry.mountPath }
567
+ : {}),
568
+ });
569
+ }
570
+
571
+ if (!options?.skipLoaders && includeLoaders) {
572
+ const loaderSegments = await resolveLoaders(
573
+ parallelEntry,
574
+ context,
575
+ belongsToRoute,
576
+ deps,
577
+ parentShortCode,
578
+ );
579
+ // Tag parallel-owned loaders so renderSegments can stream them
580
+ // using the parallel's loading() instead of awaiting on the layout
581
+ const parallelLoading =
582
+ parallelEntry.loading === false ? undefined : parallelEntry.loading;
583
+ if (parallelLoading) {
584
+ for (const seg of loaderSegments) {
585
+ seg.parallelLoading = parallelLoading;
586
+ }
587
+ }
588
+ segments.push(...loaderSegments);
589
+ }
590
+
591
+ return segments;
592
+ }
593
+
594
+ /**
595
+ * Resolve all segments for a route (used for single-cache-per-request pattern).
596
+ */
597
+ export async function resolveAllSegments<TEnv>(
598
+ entries: EntryData[],
599
+ routeKey: string,
600
+ params: Record<string, string>,
601
+ context: HandlerContext<any, TEnv>,
602
+ loaderPromises: Map<string, Promise<any>>,
603
+ deps: SegmentResolutionDeps<TEnv>,
604
+ options?: ResolveSegmentOptions,
605
+ ): Promise<ResolvedSegment[]> {
606
+ const allSegments: ResolvedSegment[] = [];
607
+ const seenIds = new Set<string>();
608
+
609
+ // Safe request access: during build-time prerendering, context.request
610
+ // is a throwing getter. Use undefined when unavailable.
611
+ let safeRequest: Request | undefined;
612
+ try {
613
+ safeRequest = context.request;
614
+ } catch {}
615
+
616
+ // Get telemetry sink from RouterContext (may not exist during prerendering)
617
+ let telemetry;
618
+ try {
619
+ telemetry = getRouterContext()?.telemetry;
620
+ } catch {}
621
+
622
+ for (const entry of entries) {
623
+ // Set ALS flag when entering a cache() boundary so that ctx.get()
624
+ // can guard non-cacheable variable reads. Also guards response-level
625
+ // side effects (headers.set). Persists for all descendant entries.
626
+ if (entry.type === "cache") {
627
+ const store = RSCRouterContext.getStore();
628
+ if (store) store.insideCacheScope = true;
629
+ }
630
+ const doneEntry = track(`segment:${entry.id}`, 1);
631
+ const resolvedSegments = await resolveWithErrorBoundary(
632
+ entry,
633
+ params,
634
+ () =>
635
+ resolveSegment(
636
+ entry,
637
+ routeKey,
638
+ params,
639
+ context,
640
+ loaderPromises,
641
+ deps,
642
+ false,
643
+ options,
644
+ ),
645
+ (seg) => [seg],
646
+ deps,
647
+ { request: safeRequest, url: context.url, routeKey, telemetry },
648
+ context.pathname,
649
+ );
650
+ doneEntry();
651
+ // Deduplicate by segment ID. include() scopes can produce entries that
652
+ // resolve the same shared layout/loader segment. Duplicates in the segment
653
+ // array propagate to the client's matched[] and change the React tree depth.
654
+ for (const seg of resolvedSegments) {
655
+ if (!seenIds.has(seg.id)) {
656
+ seenIds.add(seg.id);
657
+ allSegments.push(seg);
658
+ }
659
+ }
660
+ }
661
+
662
+ return allSegments;
663
+ }
664
+
665
+ /**
666
+ * Resolve only loader segments for all entries (used when serving cached non-loader segments).
667
+ */
668
+ export async function resolveLoadersOnly<TEnv>(
669
+ entries: EntryData[],
670
+ context: HandlerContext<any, TEnv>,
671
+ deps: SegmentResolutionDeps<TEnv>,
672
+ ): Promise<ResolvedSegment[]> {
673
+ const loaderSegments: ResolvedSegment[] = [];
674
+ const seenIds = new Set<string>();
675
+
676
+ async function collectEntryLoaders(
677
+ entry: EntryData,
678
+ belongsToRoute: boolean,
679
+ shortCodeOverride?: string,
680
+ ): Promise<void> {
681
+ // Skip if all loaders from this entry have already been resolved
682
+ // via a parent (e.g., cache boundary wrapping a layout with shared loaders).
683
+ const entryLoaders = entry.loader ?? [];
684
+ const sc = shortCodeOverride ?? entry.shortCode;
685
+ const allAlreadySeen =
686
+ entryLoaders.length > 0 &&
687
+ entryLoaders.every((le, i) =>
688
+ seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
689
+ );
690
+ if (!allAlreadySeen) {
691
+ const segments = await resolveLoaders(
692
+ entry,
693
+ context,
694
+ belongsToRoute,
695
+ deps,
696
+ shortCodeOverride,
697
+ );
698
+ for (const seg of segments) {
699
+ if (!seenIds.has(seg.id)) {
700
+ seenIds.add(seg.id);
701
+ loaderSegments.push(seg);
702
+ }
703
+ }
704
+ }
705
+
706
+ const seenParallelEntryIds = new Set<string>();
707
+ for (const parallelEntry of getParallelEntries(entry.parallel)) {
708
+ if (seenParallelEntryIds.has(parallelEntry.id)) continue;
709
+ seenParallelEntryIds.add(parallelEntry.id);
710
+ await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
711
+ }
712
+
713
+ const childBelongsToRoute = belongsToRoute || entry.type === "route";
714
+ for (const layoutEntry of entry.layout) {
715
+ await collectEntryLoaders(layoutEntry, childBelongsToRoute);
716
+ // Inherit route loaders for orphan layouts with parallels.
717
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
718
+ // route entry, as that would re-iterate route.layout and loop.
719
+ if (
720
+ entry.type === "route" &&
721
+ entry.loader &&
722
+ entry.loader.length > 0 &&
723
+ Object.keys(layoutEntry.parallel).length > 0
724
+ ) {
725
+ const inherited = await resolveLoaders(
726
+ entry,
727
+ context,
728
+ childBelongsToRoute,
729
+ deps,
730
+ layoutEntry.shortCode,
731
+ );
732
+ for (const seg of inherited) {
733
+ if (!seenIds.has(seg.id)) {
734
+ seenIds.add(seg.id);
735
+ seg._inherited = true;
736
+ loaderSegments.push(seg);
737
+ }
738
+ }
739
+ }
740
+ }
741
+ }
742
+
743
+ for (const entry of entries) {
744
+ await collectEntryLoaders(entry, entry.type === "route");
745
+ }
746
+
747
+ return loaderSegments;
748
+ }