@rangojs/router 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -103,14 +103,19 @@
103
103
  import type { ResolvedSegment } from "../../types.js";
104
104
  import {
105
105
  getRequestContext,
106
- runWithRequestContext,
106
+ type RequestContext,
107
107
  } from "../../server/request-context.js";
108
108
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
109
- import { createShellImplicitDocScope } from "../../cache/cache-scope.js";
109
+ import {
110
+ createShellImplicitDocScope,
111
+ resolveCacheTags,
112
+ } from "../../cache/cache-scope.js";
113
+ import { recordSegmentTags } from "../../cache/cache-tag.js";
110
114
  import { getRouterContext } from "../router-context.js";
111
115
  import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
112
116
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
113
117
  import type { GeneratorMiddleware } from "./cache-lookup.js";
118
+ import { rerenderAndCacheRoute } from "./background-revalidation.js";
114
119
 
115
120
  /**
116
121
  * Creates cache store middleware
@@ -159,13 +164,9 @@ export function withCacheStore<TEnv>(
159
164
  return;
160
165
  }
161
166
 
162
- const {
163
- createHandlerContext,
164
- setupLoaderAccess,
165
- resolveAllSegments,
166
- resolveInterceptEntry,
167
- createHandleStore,
168
- } = getRouterContext<TEnv>();
167
+ // Read synchronously in the pipeline: the proactive write runs in
168
+ // waitUntil, where the router-context ALS may be gone.
169
+ const routerCtx = getRouterContext<TEnv>();
169
170
 
170
171
  // On a fresh intercept miss the intercept slot segments flow through
171
172
  // `source` into allSegments AND are also recorded on state.interceptSegments.
@@ -178,18 +179,21 @@ export function withCacheStore<TEnv>(
178
179
  ...state.interceptSegments.filter((s) => !seenSegmentIds.has(s.id)),
179
180
  ];
180
181
 
182
+ const cacheScope = ctx.cacheScope;
183
+
184
+ // Only the segments the entry stores count: a null layout above the
185
+ // boundary (the client kept it) is never written, so it needs no re-render.
181
186
  const hasNullComponents = allSegmentsToCache.some(
182
187
  (s) =>
183
188
  s.component === null &&
184
189
  s.type !== "loader" &&
185
- ctx.clientSegmentSet.has(s.id),
190
+ ctx.clientSegmentSet.has(s.id) &&
191
+ cacheScope.covers(s.id, s.namespace),
186
192
  );
187
193
 
188
194
  const requestCtx = getRequestContext();
189
195
  if (!requestCtx) return;
190
196
 
191
- const cacheScope = ctx.cacheScope;
192
-
193
197
  // Record the route's segment-DSL cache tags into the request tag union NOW,
194
198
  // synchronously in the pipeline. The actual store write (cacheRoute) runs in
195
199
  // requestCtx.waitUntil() below — and the proactive path re-resolves the whole
@@ -216,91 +220,21 @@ export function withCacheStore<TEnv>(
216
220
 
217
221
  if (hasNullComponents) {
218
222
  requestCtx.waitUntil(async () => {
219
- const savedMetrics = ctx.Store.metrics;
220
- ctx.Store.metrics = undefined;
221
-
222
223
  const start = performance.now();
223
224
  debugLog("cacheStore", "proactive caching started", {
224
225
  pathname: ctx.pathname,
225
226
  });
226
- const originalHandleStore = requestCtx._handleStore;
227
- requestCtx._handleStore = createHandleStore();
228
227
  try {
229
- const proactiveHandlerContext = createHandlerContext(
230
- ctx.matched.params,
231
- ctx.request,
232
- ctx.url.searchParams,
233
- ctx.pathname,
234
- ctx.url,
235
- ctx.env,
236
- ctx.routeMap,
237
- ctx.matched.routeKey,
238
- ctx.matched.responseType,
239
- ctx.matched.pt === true,
240
- );
241
- const proactiveLoaderPromises = new Map<string, Promise<any>>();
242
-
243
- setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
244
-
245
- const Store = ctx.Store;
246
- // Re-establish the request-context ALS around the re-render. Store
247
- // is a different ALS (DSL build context); on workerd a waitUntil
248
- // task runs detached from the request's I/O context, so a handler/
249
- // component that reads the ambient getRequestContext() during this
250
- // background re-render would otherwise throw "called outside of a
251
- // request context".
252
- const freshSegments = await runWithRequestContext(requestCtx, () =>
253
- Store.run(() =>
254
- resolveAllSegments(
255
- ctx.entries,
256
- ctx.routeKey,
257
- ctx.matched.params,
258
- proactiveHandlerContext,
259
- proactiveLoaderPromises,
260
- { skipLoaders: true },
261
- ),
262
- ),
263
- );
264
-
265
- let freshInterceptSegments: ResolvedSegment[] = [];
266
- if (ctx.interceptResult) {
267
- freshInterceptSegments = await runWithRequestContext(
268
- requestCtx,
269
- () =>
270
- Store.run(() =>
271
- resolveInterceptEntry(
272
- ctx.interceptResult!.intercept,
273
- ctx.interceptResult!.entry,
274
- ctx.matched.params,
275
- proactiveHandlerContext,
276
- true, // belongsToRoute
277
- // No revalidationContext = render fresh
278
- undefined,
279
- // Skip intercept middleware: the foreground already ran it
280
- // before the response was sent. Re-running here (post-
281
- // response, background) would fire side effects twice and a
282
- // short-circuit Response would silently abort this write.
283
- { skipMiddleware: true },
284
- ),
285
- ),
286
- );
287
- }
288
-
289
- const completeSegments = [
290
- ...freshSegments,
291
- ...freshInterceptSegments,
292
- ];
293
- requestCtx._handleStore.seal();
294
- await cacheScope.cacheRoute(
295
- ctx.pathname,
296
- ctx.matched.params,
297
- completeSegments,
298
- ctx.isIntercept,
228
+ const count = await rerenderAndCacheRoute<TEnv>(
229
+ ctx,
230
+ requestCtx as RequestContext<TEnv>,
231
+ cacheScope,
232
+ routerCtx,
299
233
  );
300
234
  if (INTERNAL_RANGO_DEBUG) {
301
235
  const dur = performance.now() - start;
302
236
  console.log(
303
- `[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${completeSegments.length}`,
237
+ `[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${count}`,
304
238
  );
305
239
  }
306
240
  debugLog("cacheStore", "proactive caching complete", {
@@ -317,9 +251,6 @@ export function withCacheStore<TEnv>(
317
251
  pathname: ctx.pathname,
318
252
  error: String(error),
319
253
  });
320
- } finally {
321
- requestCtx._handleStore = originalHandleStore;
322
- ctx.Store.metrics = savedMetrics;
323
254
  }
324
255
  });
325
256
  } else {
@@ -389,9 +320,10 @@ export function withCacheStore<TEnv>(
389
320
  * store) on the no-eligible-snapshot path, so the lookup's own refusal is
390
321
  * still surfaced while the fallback has nothing to serve.
391
322
  *
392
- * Wrapped in requestCtx.waitUntil — during a capture that is the tracked-write
393
- * override, so captureAndStoreShell's settleWrites awaits the record before
394
- * draining the snapshot.
323
+ * Registered through requestCtx.onResponse, which a capture fires only after
324
+ * the handler pushes settle (settleCaptureRecord), and wrapped in
325
+ * requestCtx.waitUntil — during a capture that is the tracked-write override,
326
+ * so settleWrites awaits the record before the capture reads it.
395
327
  */
396
328
  function recordShellCaptureDocRecord<TEnv>(
397
329
  ctx: MatchContext<TEnv>,
@@ -409,12 +341,27 @@ function recordShellCaptureDocRecord<TEnv>(
409
341
  if (!scope.allowsCache("write")) return;
410
342
 
411
343
  const docScope = createShellImplicitDocScope(marker);
412
- requestCtx.waitUntil(() =>
413
- docScope.cacheRoute(
414
- ctx.pathname,
415
- ctx.matched.params,
416
- segments,
417
- ctx.isIntercept,
418
- ),
344
+ // The route scope's cache({ tags }) describe these segments too: on its
345
+ // HIT they arrive with the replayed record (recordSegmentTags in
346
+ // lookupRouteDetailed); on a fresh render they are recorded here, so the
347
+ // doc record, and the shell taking its tags, carries them either way.
348
+ recordSegmentTags(
349
+ segments.map((s) => s.id),
350
+ resolveCacheTags(scope.config, requestCtx),
351
+ requestCtx,
419
352
  );
353
+ // Through onResponse, like the implicit scope's own write: the capture
354
+ // fires these callbacks (settleCaptureRecord) only after the handler
355
+ // pushes settled, so the record's handle encode never races them.
356
+ requestCtx.onResponse((response) => {
357
+ requestCtx.waitUntil(() =>
358
+ docScope.cacheRoute(
359
+ ctx.pathname,
360
+ ctx.matched.params,
361
+ segments,
362
+ ctx.isIntercept,
363
+ ),
364
+ );
365
+ return response;
366
+ });
420
367
  }
@@ -4,7 +4,7 @@ import type {
4
4
  DefaultRouteName,
5
5
  DefaultVars,
6
6
  } from "../types/global-namespace.js";
7
- import type { ScopedReverseFunction } from "../reverse.js";
7
+ import type { GlobalReverseFunction } from "../reverse.js";
8
8
  import type { Theme } from "../theme/types.js";
9
9
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
10
10
  import type { RequestScope } from "../types/request-scope.js";
@@ -68,16 +68,19 @@ export interface MiddlewareContext<
68
68
 
69
69
  debugPerformance(): void;
70
70
 
71
- theme?: Theme;
71
+ /**
72
+ * The visitor's theme (cookie or default); undefined without theme config.
73
+ * Guarded like `cookies()`: a read inside a `"use cache"` function throws
74
+ * (#971).
75
+ */
76
+ readonly theme?: Theme;
72
77
 
73
78
  setTheme?: (theme: Theme) => void;
74
79
 
75
80
  setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
76
81
 
77
- reverse: ScopedReverseFunction<
78
- Record<string, string>,
79
- DefaultReverseRouteMap
80
- >;
82
+ /** Global route names only; see {@link GlobalReverseFunction}. */
83
+ reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
81
84
  }
82
85
 
83
86
  export type MiddlewareFn<
@@ -1,6 +1,6 @@
1
1
  /// <reference types="vite/types/importMeta.d.ts" />
2
2
 
3
- import { contextGet, contextSet } from "../context-var.js";
3
+ import { contextGet, contextSet, isNonCacheable } from "../context-var.js";
4
4
  import { escapeRegExp } from "../regex-escape.js";
5
5
  import { parsePattern as parseRoutePattern } from "./pattern-matching.js";
6
6
  import { safeDecodeURIComponent } from "./url-params.js";
@@ -22,8 +22,17 @@ import {
22
22
  import { isAutoGeneratedRouteName } from "../route-name.js";
23
23
  import { appendMetric, createMetricsStore } from "./metrics.js";
24
24
  import { observePhase, PHASES } from "./instrument.js";
25
- import { stripInternalParams } from "./handler-context.js";
25
+ import { guardHeaderWrites, stripInternalParams } from "./handler-context.js";
26
26
  import { isWebSocketUpgradeResponse } from "../response-utils.js";
27
+ import {
28
+ INSIDE_CACHE_EXEC,
29
+ NOCACHE_SYMBOL,
30
+ assertNotInsideCacheExec,
31
+ } from "../cache/taint.js";
32
+ import { getCacheExecScope } from "../cache/cache-exec-scope.js";
33
+ import { assertNonCacheableReadAllowed } from "../server/context.js";
34
+ import { assertThemeReadAllowed } from "../server/cookie-store.js";
35
+ import type { Theme } from "../theme/types.js";
27
36
 
28
37
  // Re-export types consumed through this module's path.
29
38
  export type {
@@ -33,6 +42,25 @@ export type {
33
42
  MiddlewareFn,
34
43
  } from "./middleware-types.js";
35
44
 
45
+ /**
46
+ * Middleware ctx.theme: the visitor's theme cookie, guarded like the handler
47
+ * ctx.theme and cookies() (#971, #946), so a read inside a "use cache"
48
+ * function the middleware calls throws. Non-enumerable like the handler and
49
+ * request-context getters: a spread of the ctx (inside a "use cache"
50
+ * function, say) does not read it. One shared descriptor, so every ctx keeps
51
+ * the same property shape.
52
+ */
53
+ const MIDDLEWARE_THEME_DESCRIPTOR: PropertyDescriptor = {
54
+ get(): Theme | undefined {
55
+ const reqCtx = _getRequestContext();
56
+ if (!reqCtx?._themeConfig) return undefined;
57
+ assertThemeReadAllowed(reqCtx, "ctx.theme");
58
+ return reqCtx._readTheme();
59
+ },
60
+ enumerable: false,
61
+ configurable: true,
62
+ };
63
+
36
64
  const MIDDLEWARE_METRIC_DEPTH = 1;
37
65
  const POST_METRIC_MIN_DURATION_MS = 0.01;
38
66
 
@@ -230,7 +258,12 @@ export function createMiddlewareContext<TEnv>(
230
258
  // so snapshotting beats re-reading ALS on every access. The lazy getters
231
259
  // below (routeName, theme, setTheme) stay lazy because those can change
232
260
  // during `await next()`.
233
- return {
261
+ //
262
+ // Request-scoped like the handler ctx (issue #940): the NOCACHE_SYMBOL brand
263
+ // below makes a "use cache" function taking it key by host, _routeName,
264
+ // pathname, params and search; set/header/headers throw inside that
265
+ // function, and non-cacheable get reads run the shared guard.
266
+ const ctx: MiddlewareContext<TEnv> & { readonly _routeName?: string } = {
234
267
  request,
235
268
  url,
236
269
  originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
@@ -254,17 +287,43 @@ export function createMiddlewareContext<TEnv>(
254
287
  ) as MiddlewareContext<TEnv>["routeName"];
255
288
  },
256
289
 
290
+ // The matched route key, auto-generated names included (the handler
291
+ // ctx's _routeName); undefined before routing.
292
+ get _routeName(): string | undefined {
293
+ return _getRequestContext()?._routeName;
294
+ },
295
+
296
+ // Guarded only inside a "use cache" body: the stamp covers a miss that
297
+ // took this ctx as an argument, the exec scope a stale refresh (which does
298
+ // not stamp its args, cache-runtime.ts). A write there is lost on a hit.
257
299
  get headers(): Headers {
258
- return getResponse().headers;
300
+ const headers = getResponse().headers;
301
+ return (INSIDE_CACHE_EXEC as symbol) in ctx ||
302
+ getCacheExecScope() !== undefined
303
+ ? guardHeaderWrites(headers, () =>
304
+ assertNotInsideCacheExec(ctx, "headers"),
305
+ )
306
+ : headers;
259
307
  },
260
308
 
261
- get: ((keyOrVar: any) =>
262
- contextGet(variables, keyOrVar)) as MiddlewareContext<TEnv>["get"],
309
+ get: ((keyOrVar: any) => {
310
+ // "use cache" only: intercept middleware runs inside a cache()
311
+ // subtree's render scope, and a middleware read is never cached there.
312
+ if (
313
+ isNonCacheable(variables, keyOrVar) &&
314
+ getCacheExecScope() !== undefined
315
+ ) {
316
+ assertNonCacheableReadAllowed(keyOrVar);
317
+ }
318
+ return contextGet(variables, keyOrVar);
319
+ }) as MiddlewareContext<TEnv>["get"],
263
320
 
264
321
  set: ((keyOrVar: any, value: unknown, options?: any) => {
322
+ assertNotInsideCacheExec(ctx, "set");
265
323
  contextSet(variables, keyOrVar, value, options);
266
324
  }) as MiddlewareContext<TEnv>["set"],
267
325
  header(name: string, value: string): void {
326
+ assertNotInsideCacheExec(ctx, "header");
268
327
  // Before next(): delegate to shared RequestContext stub
269
328
  if (isPreNext()) {
270
329
  const reqCtx = _getRequestContext();
@@ -282,10 +341,7 @@ export function createMiddlewareContext<TEnv>(
282
341
  responseHolder.response.headers.set(name, value);
283
342
  },
284
343
 
285
- get theme(): MiddlewareContext<TEnv>["theme"] {
286
- return _getRequestContext()?.theme;
287
- },
288
-
344
+ // theme: MIDDLEWARE_THEME_DESCRIPTOR, defined after this literal.
289
345
  get setTheme(): MiddlewareContext<TEnv>["setTheme"] {
290
346
  return _getRequestContext()?.setTheme;
291
347
  },
@@ -323,6 +379,9 @@ export function createMiddlewareContext<TEnv>(
323
379
  }
324
380
  },
325
381
  };
382
+ Object.defineProperty(ctx, "theme", MIDDLEWARE_THEME_DESCRIPTOR);
383
+ (ctx as any)[NOCACHE_SYMBOL] = true;
384
+ return ctx;
326
385
  }
327
386
 
328
387
  /**
@@ -852,6 +911,7 @@ export function collectRouteMiddleware(
852
911
  params: Record<string, string>,
853
912
  ): CollectedMiddleware[] {
854
913
  const result: CollectedMiddleware[] = [];
914
+ const chain = [...entries];
855
915
 
856
916
  const collect = (entry: MiddlewareCollectableEntry): void => {
857
917
  // Collect entry's own middleware
@@ -860,15 +920,17 @@ export function collectRouteMiddleware(
860
920
  result.push({ handler: mw, params });
861
921
  }
862
922
  }
863
- // Collect middleware from orphan layouts (recursive)
923
+ // Collect middleware from orphan layouts (recursive). A bare cache()
924
+ // marker is both its layout's orphan and a chain entry of the routes
925
+ // after it; it is collected once, at its chain position (issue #918).
864
926
  if (entry.layout && entry.layout.length > 0) {
865
927
  for (const orphan of entry.layout) {
866
- collect(orphan);
928
+ if (!chain.includes(orphan)) collect(orphan);
867
929
  }
868
930
  }
869
931
  };
870
932
 
871
- for (const entry of entries) {
933
+ for (const entry of chain) {
872
934
  collect(entry);
873
935
  }
874
936
 
@@ -15,6 +15,7 @@ import {
15
15
  import { detectPrerenderPassthrough } from "../prerender.js";
16
16
  import { isRouteRootScoped } from "../route-map-builder.js";
17
17
  import { setupBuildUse } from "./loader-resolution.js";
18
+ import { findInOrphans } from "./error-handling.js";
18
19
  import { loadManifest } from "./manifest.js";
19
20
  import { traverseBack } from "./pattern-matching.js";
20
21
  import type { RouterContext } from "./router-context.js";
@@ -229,6 +230,7 @@ export async function matchForPrerender<TEnv = any>(
229
230
  _setStatus: () => {},
230
231
  _rotateStateCookie: () => {},
231
232
  _setKeepCacheDirective: () => {},
233
+ _setFreshReadsCookie: () => {},
232
234
  use: (() => {
233
235
  throw new Error("use() not available during pre-rendering");
234
236
  }) as any,
@@ -238,6 +240,8 @@ export async function matchForPrerender<TEnv = any>(
238
240
  waitUntil: () => {},
239
241
  onResponse: () => {},
240
242
  _onResponseCallbacks: [],
243
+ // No theme at build time: no request cookie.
244
+ _readTheme: () => undefined,
241
245
  setLocationState() {},
242
246
  _locationState: undefined,
243
247
  _renderBarrier: Promise.resolve(),
@@ -306,7 +310,18 @@ export async function matchForPrerender<TEnv = any>(
306
310
  // 12. Serialize segments using the cache serializer
307
311
  const { serializeSegments } = await import("../cache/segment-codec.js");
308
312
  const { encodeHandles } = await import("../cache/handle-snapshot.js");
309
- const serializedSegments = await serializeSegments(nonLoaderSegments);
313
+ // Flight encodes a component that throws (an async server component in
314
+ // the tree) as an error row and completes normally. Rethrown below like
315
+ // a handler throw, so the caller's policy decides instead of baking the
316
+ // error (#914).
317
+ const flightErrors: unknown[] = [];
318
+ const onFlightError = (error: unknown): void => {
319
+ flightErrors.push(error);
320
+ };
321
+ const serializedSegments = await serializeSegments(
322
+ nonLoaderSegments,
323
+ onFlightError,
324
+ );
310
325
 
311
326
  // 13. Collect handle data per segment (skip segments with no handle data).
312
327
  // Encoded through the Flight codec (not stored raw) so Promise/ReactNode
@@ -322,7 +337,9 @@ export async function matchForPrerender<TEnv = any>(
322
337
  handlesRecord[seg.id] = await resolveSegmentHandleValues(segHandles);
323
338
  }
324
339
  }
325
- const handles = await encodeHandles(handlesRecord);
340
+ const handles = await encodeHandles(handlesRecord, onFlightError);
341
+ // Before intercept resolution, as a handler throw would be.
342
+ if (flightErrors.length > 0) throw flightErrors[0];
326
343
 
327
344
  // Use the trie-level route key (e.g., "docs", "docs.article")
328
345
  const routeName = matched.routeKey;
@@ -350,19 +367,23 @@ export async function matchForPrerender<TEnv = any>(
350
367
  intercept: InterceptEntry;
351
368
  entry: EntryData;
352
369
  }[] = [];
353
- let current: EntryData | null = manifestEntry;
354
- while (current) {
355
- // Flatten the entry and its sibling layouts into one source list, the
356
- // same traversal findInterceptForRoute uses; the build keeps ALL matches
357
- // (not just the innermost) and skips when(). intercept/layout are
358
- // non-optional arrays, so empty ones are a no-op here.
359
- for (const source of [current, ...current.layout]) {
360
- for (const ic of source.intercept) {
361
- if (ic.routeName === matched.routeKey) {
362
- foundIntercepts.push({ intercept: ic, entry: source });
363
- }
370
+ // The entry, then its orphans (nested ones included): the same traversal
371
+ // findInterceptForRoute uses; the build keeps ALL matches (not just the
372
+ // innermost) and skips when().
373
+ const collect = (source: EntryData): undefined => {
374
+ for (const ic of source.intercept) {
375
+ if (ic.routeName === matched.routeKey) {
376
+ foundIntercepts.push({ intercept: ic, entry: source });
364
377
  }
365
378
  }
379
+ return undefined;
380
+ };
381
+ let current: EntryData | null = manifestEntry;
382
+ let prev: EntryData | null = null;
383
+ while (current) {
384
+ collect(current);
385
+ findInOrphans(current, collect, prev);
386
+ prev = current;
366
387
  current = current.parent;
367
388
  }
368
389
 
@@ -427,6 +448,7 @@ export async function matchForPrerender<TEnv = any>(
427
448
  await handleStore.settled;
428
449
  interceptSegments = await serializeSegments(
429
450
  interceptResolvedSegments,
451
+ onFlightError,
430
452
  );
431
453
  const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
432
454
  for (const seg of interceptResolvedSegments) {
@@ -439,13 +461,18 @@ export async function matchForPrerender<TEnv = any>(
439
461
  // The intercept artifact serves main + intercept segments together, so
440
462
  // encode the MERGED handle map here (the sinks no longer merge raw
441
463
  // records — they store this pre-encoded string as-is).
442
- interceptHandles = await encodeHandles({
443
- ...handlesRecord,
444
- ...interceptHandlesRecord,
445
- });
464
+ interceptHandles = await encodeHandles(
465
+ {
466
+ ...handlesRecord,
467
+ ...interceptHandlesRecord,
468
+ },
469
+ onFlightError,
470
+ );
446
471
  }
447
472
  }
448
473
 
474
+ if (flightErrors.length > 0) throw flightErrors[0];
475
+
449
476
  return {
450
477
  segments: serializedSegments,
451
478
  handles,
@@ -506,6 +533,7 @@ export async function renderStaticSegment<TEnv = any>(
506
533
  _setStatus: () => {},
507
534
  _rotateStateCookie: () => {},
508
535
  _setKeepCacheDirective: () => {},
536
+ _setFreshReadsCookie: () => {},
509
537
  use: (() => {
510
538
  throw new Error("use() not available during static pre-rendering");
511
539
  }) as any,
@@ -515,6 +543,8 @@ export async function renderStaticSegment<TEnv = any>(
515
543
  waitUntil: () => {},
516
544
  onResponse: () => {},
517
545
  _onResponseCallbacks: [],
546
+ // No theme at build time: no request cookie.
547
+ _readTheme: () => undefined,
518
548
  setLocationState() {},
519
549
  _locationState: undefined,
520
550
  _renderBarrier: Promise.resolve(),
@@ -576,7 +606,12 @@ export async function renderStaticSegment<TEnv = any>(
576
606
 
577
607
  const { serializeSegments } = await import("../cache/segment-codec.js");
578
608
  const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
579
- const [serialized] = await serializeSegments([segment]);
609
+ // Same error-row guard as matchForPrerender (#914).
610
+ const flightErrors: unknown[] = [];
611
+ const onFlightError = (error: unknown): void => {
612
+ flightErrors.push(error);
613
+ };
614
+ const [serialized] = await serializeSegments([segment], onFlightError);
580
615
 
581
616
  // Collect handle data pushed during rendering and Flight-encode it (so
582
617
  // Promise/ReactNode handle values survive the JSON build artifact). "" when
@@ -584,9 +619,14 @@ export async function renderStaticSegment<TEnv = any>(
584
619
  const segHandles = handleStore.getDataForSegment(handlerId);
585
620
  const handles =
586
621
  Object.keys(segHandles).length > 0
587
- ? await encodeHandleValue(await resolveSegmentHandleValues(segHandles))
622
+ ? await encodeHandleValue(
623
+ await resolveSegmentHandleValues(segHandles),
624
+ onFlightError,
625
+ )
588
626
  : "";
589
627
 
628
+ if (flightErrors.length > 0) throw flightErrors[0];
629
+
590
630
  return { encoded: serialized.encoded, handles };
591
631
  });
592
632
  }
@@ -15,6 +15,7 @@
15
15
  import type { CacheScope } from "../cache/cache-scope.js";
16
16
  import { createCacheScope } from "../cache/cache-scope.js";
17
17
  import type { EntryData, MetricsStore } from "../server/context.js";
18
+ import { isPprEntry } from "../server/context.js";
18
19
  import { loadManifest } from "./manifest.js";
19
20
  import { collectRouteMiddleware } from "./middleware.js";
20
21
  import type { CollectedMiddleware } from "./middleware-types.js";
@@ -222,10 +223,19 @@ function buildEntriesAndCacheScope(manifestEntry: EntryData): {
222
223
  cacheScope: CacheScope | null;
223
224
  } {
224
225
  const entries = [...traverseBack(manifestEntry)];
226
+ // A cache() boundary keeps the entries above it live. A ppr route is a
227
+ // document-scoped cache(): its whole chain bakes into the shell, and the
228
+ // shell HIT tail must replay it to match the prelude, so its scope covers
229
+ // the whole chain.
230
+ const wholeChain = isPprEntry(manifestEntry);
225
231
  let cacheScope: CacheScope | null = null;
226
232
  for (const entry of entries) {
227
233
  if (entry.cache) {
228
- cacheScope = createCacheScope(entry.cache, cacheScope);
234
+ cacheScope = createCacheScope(
235
+ entry.cache,
236
+ cacheScope,
237
+ wholeChain ? undefined : entry.shortCode,
238
+ );
229
239
  }
230
240
  }
231
241
  return { entries, cacheScope };
@@ -131,6 +131,8 @@ export interface RouterContext<TEnv = any> {
131
131
  localRouteName: string,
132
132
  pathname: string,
133
133
  stale?: boolean,
134
+ /** The matched route's full chain when `entries` is part of it (ResolveSegmentOptions.chain). */
135
+ chain?: readonly EntryData[],
134
136
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
135
137
 
136
138
  // Generator-based segment resolution (for pipeline)
@@ -177,6 +177,23 @@ export interface Rango<
177
177
  */
178
178
  readonly routeMap: TRoutes;
179
179
 
180
+ /**
181
+ * Serialize the route manifest for inspection: every route and layout the
182
+ * router's `.routes()` / `urls` mounts register, with shortCodes, parent
183
+ * links, and loader/middleware/error-boundary/parallel/intercept flags.
184
+ *
185
+ * Server-only development aid. It re-evaluates each mount's `urls()` tree.
186
+ * Groups mounted with `include()` are lazy and are not expanded: their
187
+ * routes are absent from the result.
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const manifest = await router.debugManifest();
192
+ * console.log(manifest.totalRoutes, Object.keys(manifest.routes));
193
+ * ```
194
+ */
195
+ debugManifest(): Promise<SerializedManifest>;
196
+
180
197
  /**
181
198
  * Handle an RSC request.
182
199
  *
@@ -187,7 +204,13 @@ export interface Rango<
187
204
  * ```tsx
188
205
  * import { router } from "./router";
189
206
  *
190
- * export default { fetch: router.fetch };
207
+ * // Workers call fetch(request, env, ctx); `{ fetch: router.fetch }` would
208
+ * // drop env and ctx.
209
+ * export default {
210
+ * fetch(request, env, ctx) {
211
+ * return router.fetch(request, { env, ctx });
212
+ * },
213
+ * } satisfies ExportedHandler;
191
214
  * ```
192
215
  *
193
216
  * @example Direct export
@@ -316,7 +339,7 @@ export interface RangoInternal<
316
339
 
317
340
  /**
318
341
  * Resolved theme configuration (null if theme not enabled)
319
- * Used by NavigationProvider to include ThemeProvider and by MetaTags to render theme script
342
+ * Used by NavigationProvider to include ThemeProvider and by `Html.Meta` to render theme script
320
343
  */
321
344
  readonly themeConfig: import("../theme/types.js").ResolvedThemeConfig | null;
322
345