@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26

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 (225) hide show
  1. package/README.md +294 -28
  2. package/dist/bin/rango.js +355 -47
  3. package/dist/vite/index.js +1658 -1239
  4. package/package.json +3 -3
  5. package/skills/cache-guide/SKILL.md +9 -5
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +40 -29
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/intercept/SKILL.md +79 -0
  11. package/skills/layout/SKILL.md +62 -2
  12. package/skills/loader/SKILL.md +229 -15
  13. package/skills/middleware/SKILL.md +109 -30
  14. package/skills/parallel/SKILL.md +57 -2
  15. package/skills/prerender/SKILL.md +189 -19
  16. package/skills/rango/SKILL.md +1 -2
  17. package/skills/response-routes/SKILL.md +3 -3
  18. package/skills/route/SKILL.md +44 -3
  19. package/skills/router-setup/SKILL.md +80 -3
  20. package/skills/theme/SKILL.md +5 -4
  21. package/skills/typesafety/SKILL.md +59 -16
  22. package/skills/use-cache/SKILL.md +16 -2
  23. package/src/__internal.ts +1 -1
  24. package/src/bin/rango.ts +56 -19
  25. package/src/browser/action-coordinator.ts +97 -0
  26. package/src/browser/event-controller.ts +29 -48
  27. package/src/browser/history-state.ts +80 -0
  28. package/src/browser/intercept-utils.ts +1 -1
  29. package/src/browser/link-interceptor.ts +19 -3
  30. package/src/browser/merge-segment-loaders.ts +9 -2
  31. package/src/browser/navigation-bridge.ts +66 -443
  32. package/src/browser/navigation-client.ts +34 -62
  33. package/src/browser/navigation-store.ts +4 -33
  34. package/src/browser/navigation-transaction.ts +295 -0
  35. package/src/browser/partial-update.ts +103 -151
  36. package/src/browser/prefetch/cache.ts +67 -0
  37. package/src/browser/prefetch/fetch.ts +137 -0
  38. package/src/browser/prefetch/observer.ts +65 -0
  39. package/src/browser/prefetch/policy.ts +42 -0
  40. package/src/browser/prefetch/queue.ts +88 -0
  41. package/src/browser/rango-state.ts +112 -0
  42. package/src/browser/react/Link.tsx +154 -44
  43. package/src/browser/react/NavigationProvider.tsx +32 -0
  44. package/src/browser/react/context.ts +6 -0
  45. package/src/browser/react/filter-segment-order.ts +11 -0
  46. package/src/browser/react/index.ts +2 -6
  47. package/src/browser/react/location-state-shared.ts +29 -11
  48. package/src/browser/react/location-state.ts +6 -4
  49. package/src/browser/react/nonce-context.ts +23 -0
  50. package/src/browser/react/shallow-equal.ts +27 -0
  51. package/src/browser/react/use-action.ts +23 -45
  52. package/src/browser/react/use-client-cache.ts +5 -3
  53. package/src/browser/react/use-handle.ts +21 -64
  54. package/src/browser/react/use-navigation.ts +7 -32
  55. package/src/browser/react/use-params.ts +5 -34
  56. package/src/browser/react/use-pathname.ts +2 -3
  57. package/src/browser/react/use-router.ts +3 -6
  58. package/src/browser/react/use-search-params.ts +2 -1
  59. package/src/browser/react/use-segments.ts +75 -114
  60. package/src/browser/response-adapter.ts +73 -0
  61. package/src/browser/rsc-router.tsx +46 -22
  62. package/src/browser/scroll-restoration.ts +10 -7
  63. package/src/browser/server-action-bridge.ts +458 -405
  64. package/src/browser/types.ts +21 -35
  65. package/src/browser/validate-redirect-origin.ts +29 -0
  66. package/src/build/generate-manifest.ts +38 -13
  67. package/src/build/generate-route-types.ts +4 -0
  68. package/src/build/index.ts +1 -0
  69. package/src/build/route-trie.ts +19 -3
  70. package/src/build/route-types/codegen.ts +13 -4
  71. package/src/build/route-types/include-resolution.ts +13 -0
  72. package/src/build/route-types/per-module-writer.ts +15 -3
  73. package/src/build/route-types/router-processing.ts +170 -18
  74. package/src/build/runtime-discovery.ts +13 -1
  75. package/src/cache/background-task.ts +34 -0
  76. package/src/cache/cache-key-utils.ts +44 -0
  77. package/src/cache/cache-policy.ts +125 -0
  78. package/src/cache/cache-runtime.ts +136 -123
  79. package/src/cache/cache-scope.ts +76 -83
  80. package/src/cache/cf/cf-cache-store.ts +12 -7
  81. package/src/cache/document-cache.ts +93 -69
  82. package/src/cache/handle-capture.ts +81 -0
  83. package/src/cache/index.ts +0 -15
  84. package/src/cache/memory-segment-store.ts +43 -69
  85. package/src/cache/profile-registry.ts +43 -8
  86. package/src/cache/read-through-swr.ts +134 -0
  87. package/src/cache/segment-codec.ts +140 -117
  88. package/src/cache/taint.ts +30 -3
  89. package/src/cache/types.ts +1 -115
  90. package/src/client.rsc.tsx +0 -1
  91. package/src/client.tsx +53 -76
  92. package/src/errors.ts +6 -1
  93. package/src/handle.ts +1 -1
  94. package/src/handles/MetaTags.tsx +5 -2
  95. package/src/host/cookie-handler.ts +8 -3
  96. package/src/host/index.ts +0 -3
  97. package/src/host/router.ts +14 -1
  98. package/src/href-client.ts +3 -1
  99. package/src/index.rsc.ts +53 -10
  100. package/src/index.ts +73 -43
  101. package/src/loader.rsc.ts +12 -4
  102. package/src/loader.ts +8 -0
  103. package/src/prerender/store.ts +60 -18
  104. package/src/prerender.ts +76 -18
  105. package/src/reverse.ts +11 -7
  106. package/src/root-error-boundary.tsx +30 -26
  107. package/src/route-definition/dsl-helpers.ts +9 -6
  108. package/src/route-definition/index.ts +0 -3
  109. package/src/route-definition/redirect.ts +15 -3
  110. package/src/route-map-builder.ts +38 -2
  111. package/src/route-name.ts +53 -0
  112. package/src/route-types.ts +7 -0
  113. package/src/router/content-negotiation.ts +1 -1
  114. package/src/router/debug-manifest.ts +16 -3
  115. package/src/router/handler-context.ts +96 -17
  116. package/src/router/intercept-resolution.ts +6 -4
  117. package/src/router/lazy-includes.ts +4 -0
  118. package/src/router/loader-resolution.ts +6 -11
  119. package/src/router/logging.ts +100 -3
  120. package/src/router/manifest.ts +32 -3
  121. package/src/router/match-api.ts +62 -54
  122. package/src/router/match-context.ts +3 -0
  123. package/src/router/match-handlers.ts +185 -11
  124. package/src/router/match-middleware/background-revalidation.ts +65 -85
  125. package/src/router/match-middleware/cache-lookup.ts +78 -10
  126. package/src/router/match-middleware/cache-store.ts +2 -0
  127. package/src/router/match-pipelines.ts +8 -43
  128. package/src/router/match-result.ts +0 -9
  129. package/src/router/metrics.ts +233 -13
  130. package/src/router/middleware-types.ts +34 -39
  131. package/src/router/middleware.ts +290 -130
  132. package/src/router/pattern-matching.ts +61 -10
  133. package/src/router/prerender-match.ts +36 -6
  134. package/src/router/preview-match.ts +7 -1
  135. package/src/router/revalidation.ts +61 -2
  136. package/src/router/router-context.ts +15 -0
  137. package/src/router/router-interfaces.ts +158 -40
  138. package/src/router/router-options.ts +223 -1
  139. package/src/router/router-registry.ts +5 -2
  140. package/src/router/segment-resolution/fresh.ts +165 -242
  141. package/src/router/segment-resolution/helpers.ts +263 -0
  142. package/src/router/segment-resolution/loader-cache.ts +102 -98
  143. package/src/router/segment-resolution/revalidation.ts +394 -272
  144. package/src/router/segment-resolution/static-store.ts +2 -2
  145. package/src/router/segment-resolution.ts +1 -3
  146. package/src/router/segment-wrappers.ts +3 -0
  147. package/src/router/telemetry-otel.ts +299 -0
  148. package/src/router/telemetry.ts +300 -0
  149. package/src/router/timeout.ts +148 -0
  150. package/src/router/trie-matching.ts +20 -2
  151. package/src/router/types.ts +7 -1
  152. package/src/router.ts +203 -18
  153. package/src/rsc/handler-context.ts +13 -2
  154. package/src/rsc/handler.ts +489 -438
  155. package/src/rsc/helpers.ts +125 -5
  156. package/src/rsc/index.ts +0 -20
  157. package/src/rsc/loader-fetch.ts +84 -42
  158. package/src/rsc/manifest-init.ts +3 -2
  159. package/src/rsc/origin-guard.ts +141 -0
  160. package/src/rsc/progressive-enhancement.ts +245 -19
  161. package/src/rsc/response-route-handler.ts +347 -0
  162. package/src/rsc/rsc-rendering.ts +47 -43
  163. package/src/rsc/runtime-warnings.ts +42 -0
  164. package/src/rsc/server-action.ts +166 -66
  165. package/src/rsc/ssr-setup.ts +128 -0
  166. package/src/rsc/types.ts +20 -2
  167. package/src/search-params.ts +38 -23
  168. package/src/server/context.ts +61 -7
  169. package/src/server/cookie-store.ts +190 -0
  170. package/src/server/fetchable-loader-store.ts +11 -6
  171. package/src/server/handle-store.ts +84 -12
  172. package/src/server/loader-registry.ts +11 -46
  173. package/src/server/request-context.ts +275 -49
  174. package/src/server.ts +6 -0
  175. package/src/ssr/index.tsx +67 -28
  176. package/src/static-handler.ts +7 -0
  177. package/src/theme/ThemeProvider.tsx +6 -1
  178. package/src/theme/index.ts +4 -18
  179. package/src/theme/theme-context.ts +1 -28
  180. package/src/theme/theme-script.ts +2 -1
  181. package/src/types/cache-types.ts +6 -1
  182. package/src/types/error-types.ts +3 -0
  183. package/src/types/global-namespace.ts +22 -0
  184. package/src/types/handler-context.ts +103 -16
  185. package/src/types/index.ts +1 -1
  186. package/src/types/loader-types.ts +9 -6
  187. package/src/types/route-config.ts +17 -26
  188. package/src/types/route-entry.ts +28 -0
  189. package/src/types/segments.ts +0 -5
  190. package/src/urls/include-helper.ts +49 -8
  191. package/src/urls/index.ts +1 -0
  192. package/src/urls/path-helper-types.ts +30 -12
  193. package/src/urls/path-helper.ts +17 -2
  194. package/src/urls/pattern-types.ts +21 -1
  195. package/src/urls/response-types.ts +29 -7
  196. package/src/urls/type-extraction.ts +23 -15
  197. package/src/use-loader.tsx +27 -9
  198. package/src/vite/discovery/bundle-postprocess.ts +32 -52
  199. package/src/vite/discovery/discover-routers.ts +52 -26
  200. package/src/vite/discovery/prerender-collection.ts +58 -41
  201. package/src/vite/discovery/route-types-writer.ts +7 -7
  202. package/src/vite/discovery/state.ts +7 -7
  203. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  204. package/src/vite/index.ts +10 -51
  205. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  206. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  207. package/src/vite/plugins/expose-internal-ids.ts +4 -3
  208. package/src/vite/plugins/refresh-cmd.ts +65 -0
  209. package/src/vite/plugins/use-cache-transform.ts +91 -3
  210. package/src/vite/plugins/version-plugin.ts +188 -18
  211. package/src/vite/rango.ts +61 -36
  212. package/src/vite/router-discovery.ts +173 -100
  213. package/src/vite/utils/prerender-utils.ts +81 -0
  214. package/src/vite/utils/shared-utils.ts +19 -9
  215. package/skills/testing/SKILL.md +0 -226
  216. package/src/browser/lru-cache.ts +0 -61
  217. package/src/browser/react/prefetch.ts +0 -27
  218. package/src/browser/request-controller.ts +0 -164
  219. package/src/cache/memory-store.ts +0 -253
  220. package/src/href-context.ts +0 -33
  221. package/src/route-definition/route-function.ts +0 -119
  222. package/src/router.gen.ts +0 -6
  223. package/src/static-handler.gen.ts +0 -5
  224. package/src/urls.gen.ts +0 -8
  225. /package/{CLAUDE.md → AGENTS.md} +0 -0
@@ -6,47 +6,67 @@
6
6
  */
7
7
 
8
8
  import type { ReactNode } from "react";
9
- import { DataNotFoundError, invariant } from "../../errors";
10
- import {
11
- createErrorInfo,
12
- createErrorSegment,
13
- createNotFoundInfo,
14
- createNotFoundSegment,
15
- } from "../error-handling.js";
16
- import { getRequestContext } from "../../server/request-context.js";
17
- import { DefaultErrorFallback } from "../../default-error-boundary.js";
9
+ import { invariant } from "../../errors";
18
10
  import type { EntryData } from "../../server/context";
19
11
  import type {
20
12
  HandlerContext,
21
13
  InternalHandlerContext,
22
14
  ResolvedSegment,
23
- ErrorInfo,
24
15
  } from "../../types";
25
16
  import type { SegmentResolutionDeps } from "../types.js";
26
- import { debugLog } from "../logging.js";
27
- import { tryStaticLookup } from "./static-store.js";
28
17
  import { resolveLoaderData } from "./loader-cache.js";
18
+ import {
19
+ handleHandlerResult,
20
+ tryStaticHandler,
21
+ tryStaticSlot,
22
+ resolveLayoutComponent,
23
+ resolveWithErrorBoundary,
24
+ } from "./helpers.js";
25
+ import { getRouterContext } from "../router-context.js";
26
+ import { resolveSink, safeEmit } from "../telemetry.js";
27
+ import { track } from "../../server/context.js";
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Streamed handler telemetry
31
+ // ---------------------------------------------------------------------------
29
32
 
30
33
  /**
31
- * Handle Response returns from handlers.
32
- * When a handler returns a Response (e.g., redirect), throw it to trigger
33
- * the short-circuit mechanism. Otherwise return the ReactNode.
34
+ * Attach a fire-and-forget rejection observer to a streamed handler promise.
35
+ * React catches the actual error via its error boundary; this only emits
36
+ * the handler.error telemetry event.
34
37
  */
35
- export function handleHandlerResult(
36
- result: ReactNode | Response | Promise<ReactNode> | Promise<Response>,
37
- ): ReactNode {
38
- if (result instanceof Response) {
39
- throw result;
40
- }
41
- if (result instanceof Promise) {
42
- return result.then((resolved) => {
43
- if (resolved instanceof Response) {
44
- throw resolved;
45
- }
46
- return resolved;
47
- }) as ReactNode;
38
+ function observeStreamedHandler(
39
+ promise: Promise<ReactNode>,
40
+ segmentId: string,
41
+ segmentType: string,
42
+ pathname?: string,
43
+ routeKey?: string,
44
+ params?: Record<string, string>,
45
+ ): void {
46
+ let routerCtx;
47
+ try {
48
+ routerCtx = getRouterContext();
49
+ } catch {
50
+ return;
48
51
  }
49
- return result;
52
+ if (!routerCtx?.telemetry) return;
53
+ const sink = resolveSink(routerCtx.telemetry);
54
+ const reqId = routerCtx.requestId;
55
+ promise.catch((err: unknown) => {
56
+ const errorObj = err instanceof Error ? err : new Error(String(err));
57
+ safeEmit(sink, {
58
+ type: "handler.error",
59
+ timestamp: performance.now(),
60
+ requestId: reqId,
61
+ segmentId,
62
+ segmentType,
63
+ error: errorObj,
64
+ handledByBoundary: true,
65
+ pathname,
66
+ routeKey,
67
+ params,
68
+ });
69
+ });
50
70
  }
51
71
 
52
72
  // ---------------------------------------------------------------------------
@@ -154,38 +174,14 @@ export async function resolveSegment<TEnv>(
154
174
  segments.push(...loaderSegments);
155
175
  }
156
176
 
157
- for (const parallelEntry of entry.parallel) {
158
- const parallelSegments = await resolveParallelEntry(
159
- parallelEntry,
160
- params,
161
- context,
162
- false,
163
- entry.shortCode,
164
- deps,
165
- options,
166
- );
167
- segments.push(...parallelSegments);
168
- }
169
-
177
+ // Handler-first: layout handler executes before its parallels and orphan
178
+ // layouts so that ctx.set() values are visible to all children.
170
179
  (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
171
180
  entry.shortCode;
172
181
 
173
- // Static handler interception: use pre-rendered component from build-time store.
174
- // Cast via any because the cache entry type in the union lacks isStaticPrerender.
175
- const entryAny = entry as any;
176
- let component: ReactNode | undefined;
177
- if (entryAny.isStaticPrerender && entryAny.staticHandlerId) {
178
- component = await tryStaticLookup(
179
- entryAny.staticHandlerId,
180
- entry.shortCode,
181
- );
182
- }
183
- if (component === undefined) {
184
- component =
185
- typeof entry.handler === "function"
186
- ? handleHandlerResult(await entry.handler(context))
187
- : entry.handler;
188
- }
182
+ const doneLayoutHandler = track(`handler:${entry.id}`, 2);
183
+ const component = await resolveLayoutComponent(entry, context);
184
+ doneLayoutHandler();
189
185
 
190
186
  segments.push({
191
187
  id: entry.shortCode,
@@ -201,6 +197,20 @@ export async function resolveSegment<TEnv>(
201
197
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
202
198
  });
203
199
 
200
+ for (const parallelEntry of entry.parallel) {
201
+ const parallelSegments = await resolveParallelEntry(
202
+ parallelEntry,
203
+ params,
204
+ context,
205
+ false,
206
+ entry.shortCode,
207
+ deps,
208
+ options,
209
+ routeKey,
210
+ );
211
+ segments.push(...parallelSegments);
212
+ }
213
+
204
214
  for (const orphan of entry.layout) {
205
215
  const orphanSegments = await resolveOrphanLayout(
206
216
  orphan,
@@ -210,6 +220,7 @@ export async function resolveSegment<TEnv>(
210
220
  false,
211
221
  deps,
212
222
  options,
223
+ routeKey,
213
224
  );
214
225
  segments.push(...orphanSegments);
215
226
  }
@@ -228,22 +239,36 @@ export async function resolveSegment<TEnv>(
228
239
  // the correct tree composition order (layouts wrap the route content).
229
240
  (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
230
241
  entry.shortCode;
231
- let component: ReactNode | undefined;
232
-
233
- // Static handler interception: use pre-rendered component from build-time store
234
- if (entry.isStaticPrerender && (entry as any).staticHandlerId) {
235
- component = await tryStaticLookup(
236
- (entry as any).staticHandlerId,
237
- entry.shortCode,
238
- );
239
- }
242
+ let component: ReactNode | undefined = await tryStaticHandler(
243
+ entry,
244
+ entry.shortCode,
245
+ );
240
246
  if (component === undefined) {
247
+ const doneRouteHandler = track(`handler:${entry.id}`, 2);
241
248
  if (entry.loading) {
242
249
  const result = handleHandlerResult(entry.handler(context));
243
- component =
244
- result instanceof Promise ? deps.trackHandler(result) : result;
250
+ if (result instanceof Promise) {
251
+ result.finally(doneRouteHandler).catch(() => {});
252
+ const tracked = deps.trackHandler(result, {
253
+ segmentId: entry.shortCode,
254
+ segmentType: entry.type,
255
+ });
256
+ observeStreamedHandler(
257
+ tracked,
258
+ entry.shortCode,
259
+ entry.type,
260
+ context.pathname,
261
+ routeKey,
262
+ params,
263
+ );
264
+ component = tracked;
265
+ } else {
266
+ doneRouteHandler();
267
+ component = result;
268
+ }
245
269
  } else {
246
270
  component = handleHandlerResult(await entry.handler(context));
271
+ doneRouteHandler();
247
272
  }
248
273
  }
249
274
 
@@ -256,6 +281,7 @@ export async function resolveSegment<TEnv>(
256
281
  true,
257
282
  deps,
258
283
  options,
284
+ routeKey,
259
285
  );
260
286
  segments.push(...orphanSegments);
261
287
  }
@@ -269,6 +295,7 @@ export async function resolveSegment<TEnv>(
269
295
  entry.shortCode,
270
296
  deps,
271
297
  options,
298
+ routeKey,
272
299
  );
273
300
  segments.push(...parallelSegments);
274
301
  }
@@ -303,6 +330,7 @@ export async function resolveOrphanLayout<TEnv>(
303
330
  belongsToRoute: boolean,
304
331
  deps: SegmentResolutionDeps<TEnv>,
305
332
  options?: ResolveSegmentOptions,
333
+ routeKey?: string,
306
334
  ): Promise<ResolvedSegment[]> {
307
335
  invariant(
308
336
  orphan.type === "layout" || orphan.type === "cache",
@@ -320,34 +348,11 @@ export async function resolveOrphanLayout<TEnv>(
320
348
  segments.push(...loaderSegments);
321
349
  }
322
350
 
323
- for (const parallelEntry of orphan.parallel) {
324
- const parallelSegments = await resolveParallelEntry(
325
- parallelEntry,
326
- params,
327
- context,
328
- belongsToRoute,
329
- orphan.shortCode,
330
- deps,
331
- options,
332
- );
333
- segments.push(...parallelSegments);
334
- }
335
-
336
- // Static handler interception for orphan layouts
337
- const orphanAny = orphan as any;
338
- let component: ReactNode | undefined;
339
- if (orphanAny.isStaticPrerender && orphanAny.staticHandlerId) {
340
- component = await tryStaticLookup(
341
- orphanAny.staticHandlerId,
342
- orphan.shortCode,
343
- );
344
- }
345
- if (component === undefined) {
346
- component =
347
- typeof orphan.handler === "function"
348
- ? handleHandlerResult(await orphan.handler(context))
349
- : orphan.handler;
350
- }
351
+ // Handler-first: orphan layout handler executes before its parallels
352
+ // so that ctx.set() values are visible to parallel children.
353
+ const doneOrphanHandler = track(`handler:${orphan.id}`, 2);
354
+ const component = await resolveLayoutComponent(orphan, context);
355
+ doneOrphanHandler();
351
356
 
352
357
  segments.push({
353
358
  id: orphan.shortCode,
@@ -363,6 +368,20 @@ export async function resolveOrphanLayout<TEnv>(
363
368
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
364
369
  });
365
370
 
371
+ for (const parallelEntry of orphan.parallel) {
372
+ const parallelSegments = await resolveParallelEntry(
373
+ parallelEntry,
374
+ params,
375
+ context,
376
+ belongsToRoute,
377
+ orphan.shortCode,
378
+ deps,
379
+ options,
380
+ routeKey,
381
+ );
382
+ segments.push(...parallelSegments);
383
+ }
384
+
366
385
  return segments;
367
386
  }
368
387
 
@@ -377,6 +396,7 @@ export async function resolveParallelEntry<TEnv>(
377
396
  parentShortCode: string,
378
397
  deps: SegmentResolutionDeps<TEnv>,
379
398
  options?: ResolveSegmentOptions,
399
+ routeKey?: string,
380
400
  ): Promise<ResolvedSegment[]> {
381
401
  invariant(
382
402
  parallelEntry.type === "parallel",
@@ -392,27 +412,45 @@ export async function resolveParallelEntry<TEnv>(
392
412
  >;
393
413
 
394
414
  for (const [slot, handler] of Object.entries(slots)) {
395
- let component: ReactNode | undefined;
396
-
397
- // Static handler interception for individual parallel slots
398
- const slotStaticId = (parallelEntry as any).staticHandlerIds?.[slot];
399
- if (slotStaticId) {
400
- component = await tryStaticLookup(
401
- slotStaticId,
402
- `${parentShortCode}.${slot}`,
403
- );
404
- }
415
+ let component: ReactNode | undefined = await tryStaticSlot(
416
+ parallelEntry,
417
+ slot,
418
+ `${parentShortCode}.${slot}`,
419
+ );
405
420
 
406
421
  if (component === undefined) {
422
+ const doneParallelHandler = track(
423
+ `handler:${parallelEntry.id}.${slot}`,
424
+ 2,
425
+ );
407
426
  const hasLoadingFallback =
408
427
  parallelEntry.loading !== undefined && parallelEntry.loading !== false;
409
428
  if (hasLoadingFallback) {
410
429
  const result =
411
430
  typeof handler === "function" ? handler(context) : handler;
412
- component = result as ReactNode;
431
+ if (result instanceof Promise) {
432
+ result.finally(doneParallelHandler).catch(() => {});
433
+ const tracked = deps.trackHandler(result, {
434
+ segmentId: `${parentShortCode}.${slot}`,
435
+ segmentType: "parallel",
436
+ });
437
+ observeStreamedHandler(
438
+ tracked,
439
+ `${parentShortCode}.${slot}`,
440
+ "parallel",
441
+ context.pathname,
442
+ routeKey,
443
+ params,
444
+ );
445
+ component = tracked as ReactNode;
446
+ } else {
447
+ doneParallelHandler();
448
+ component = result as ReactNode;
449
+ }
413
450
  } else {
414
451
  component =
415
452
  typeof handler === "function" ? await handler(context) : handler;
453
+ doneParallelHandler();
416
454
  }
417
455
  }
418
456
 
@@ -448,136 +486,6 @@ export async function resolveParallelEntry<TEnv>(
448
486
  return segments;
449
487
  }
450
488
 
451
- /**
452
- * Wrapper that adds error boundary handling to segment resolution.
453
- */
454
- export async function resolveWithErrorHandling<TEnv>(
455
- entry: EntryData,
456
- routeKey: string,
457
- params: Record<string, string>,
458
- context: HandlerContext<any, TEnv>,
459
- loaderPromises: Map<string, Promise<any>>,
460
- resolveFn: () => Promise<ResolvedSegment[]>,
461
- deps: SegmentResolutionDeps<TEnv>,
462
- errorContext?: {
463
- env?: TEnv;
464
- isPartial?: boolean;
465
- requestStartTime?: number;
466
- },
467
- ): Promise<ResolvedSegment[]> {
468
- try {
469
- return await resolveFn();
470
- } catch (error) {
471
- if (error instanceof Response) {
472
- throw error;
473
- }
474
-
475
- if (error instanceof DataNotFoundError) {
476
- const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
477
-
478
- if (notFoundFallback) {
479
- const notFoundInfo = createNotFoundInfo(
480
- error,
481
- entry.shortCode,
482
- entry.type,
483
- context.pathname,
484
- );
485
-
486
- // Safe request access: during build-time prerendering, context.request
487
- // is a throwing getter. Use undefined when unavailable.
488
- let safeRequest: Request | undefined;
489
- try {
490
- safeRequest = context.request;
491
- } catch {}
492
-
493
- deps.callOnError(error, "handler", {
494
- request: safeRequest as Request,
495
- url: context.url,
496
- routeKey,
497
- params,
498
- segmentId: entry.shortCode,
499
- segmentType: entry.type as any,
500
- env: errorContext?.env,
501
- isPartial: errorContext?.isPartial,
502
- handledByBoundary: true,
503
- metadata: { notFound: true, message: notFoundInfo.message },
504
- requestStartTime: errorContext?.requestStartTime,
505
- });
506
-
507
- debugLog("segment", "notFound boundary handled error", {
508
- segmentId: entry.shortCode,
509
- message: notFoundInfo.message,
510
- });
511
-
512
- const reqCtx = getRequestContext();
513
- if (reqCtx) {
514
- reqCtx.res = new Response(null, {
515
- status: 404,
516
- headers: reqCtx.res.headers,
517
- });
518
- }
519
-
520
- const notFoundSegment = createNotFoundSegment(
521
- notFoundInfo,
522
- notFoundFallback,
523
- entry,
524
- params,
525
- );
526
- return [notFoundSegment];
527
- }
528
- }
529
-
530
- const fallback = deps.findNearestErrorBoundary(entry);
531
- const segmentType: ErrorInfo["segmentType"] = entry.type;
532
- const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
533
- const effectiveFallback = fallback ?? DefaultErrorFallback;
534
-
535
- // Safe request access: during build-time prerendering, context.request
536
- // is a throwing getter. Use undefined when unavailable.
537
- let safeReq: Request | undefined;
538
- try {
539
- safeReq = context.request;
540
- } catch {}
541
-
542
- deps.callOnError(error, "handler", {
543
- request: safeReq as Request,
544
- url: context.url,
545
- routeKey,
546
- params,
547
- segmentId: entry.shortCode,
548
- segmentType: entry.type as any,
549
- env: errorContext?.env,
550
- isPartial: errorContext?.isPartial,
551
- handledByBoundary: !!fallback,
552
- requestStartTime: errorContext?.requestStartTime,
553
- });
554
-
555
- debugLog("segment", "error boundary handled error", {
556
- segmentId: entry.shortCode,
557
- boundary: fallback ? "custom" : "default",
558
- message: errorInfo.message,
559
- });
560
-
561
- {
562
- const reqCtx = getRequestContext();
563
- if (reqCtx) {
564
- reqCtx.res = new Response(null, {
565
- status: 500,
566
- headers: reqCtx.res.headers,
567
- });
568
- }
569
- }
570
-
571
- const errorSegment = createErrorSegment(
572
- errorInfo,
573
- effectiveFallback,
574
- entry,
575
- params,
576
- );
577
- return [errorSegment];
578
- }
579
- }
580
-
581
489
  /**
582
490
  * Resolve all segments for a route (used for single-cache-per-request pattern).
583
491
  */
@@ -593,13 +501,24 @@ export async function resolveAllSegments<TEnv>(
593
501
  const allSegments: ResolvedSegment[] = [];
594
502
  const seenIds = new Set<string>();
595
503
 
504
+ // Safe request access: during build-time prerendering, context.request
505
+ // is a throwing getter. Use undefined when unavailable.
506
+ let safeRequest: Request | undefined;
507
+ try {
508
+ safeRequest = context.request;
509
+ } catch {}
510
+
511
+ // Get telemetry sink from RouterContext (may not exist during prerendering)
512
+ let telemetry;
513
+ try {
514
+ telemetry = getRouterContext()?.telemetry;
515
+ } catch {}
516
+
596
517
  for (const entry of entries) {
597
- const resolvedSegments = await resolveWithErrorHandling(
518
+ const doneEntry = track(`segment:${entry.id}`, 1);
519
+ const resolvedSegments = await resolveWithErrorBoundary(
598
520
  entry,
599
- routeKey,
600
521
  params,
601
- context,
602
- loaderPromises,
603
522
  () =>
604
523
  resolveSegment(
605
524
  entry,
@@ -611,8 +530,12 @@ export async function resolveAllSegments<TEnv>(
611
530
  false,
612
531
  options,
613
532
  ),
533
+ (seg) => [seg],
614
534
  deps,
535
+ { request: safeRequest, url: context.url, routeKey, telemetry },
536
+ context.pathname,
615
537
  );
538
+ doneEntry();
616
539
  // Deduplicate by segment ID. include() scopes can produce entries that
617
540
  // resolve the same shared layout/loader segment. Duplicates in the segment
618
541
  // array propagate to the client's matched[] and change the React tree depth.