@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -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 +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -8,12 +8,97 @@ argument-hint: [middleware-name]
8
8
 
9
9
  Middleware runs before/after route handlers using the onion model.
10
10
 
11
+ ## Execution Model
12
+
13
+ Canonical semantics reference:
14
+ [docs/execution-model.md](../../docs/internal/execution-model.md)
15
+
16
+ There are two levels of middleware with different execution scopes:
17
+
18
+ ### Global middleware (`router.use()`)
19
+
20
+ Registered on the router instance. Wraps the **entire request**, including server actions, rendering, and progressive enhancement (PE) re-renders.
21
+
22
+ ```typescript
23
+ const router = createRouter<AppEnv>({})
24
+ .use(loggerMiddleware) // all routes
25
+ .use("/admin/*", authMiddleware) // pattern-scoped
26
+ .routes(urlpatterns);
27
+ ```
28
+
29
+ When the router has a `basename`, pattern-scoped `.use()` patterns are automatically prefixed. For example, with `basename: "/app"`, `.use("/admin/*", mw)` matches `/app/admin/*`.
30
+
31
+ ### Route middleware (`middleware()` in `urls()`)
32
+
33
+ Registered inside `urls()` callback. Wraps **rendering only** -- it does NOT wrap server action execution. Actions run before route middleware, so when route middleware executes during post-action revalidation, it can observe state that the action set (cookies, context variables, headers).
34
+
35
+ > **Implication for auth:** route middleware cannot guard server actions. Use `router.use("/admin/*", requireAuth)` (global, scoped) for action protection, or check inside the action body. See `/server-actions` for action-side auth patterns.
36
+
37
+ ```
38
+ Request flow (with action):
39
+ global mw -> action executes -> route mw -> layout -> handler -> loaders
40
+
41
+ Request flow (no action):
42
+ global mw -> route mw -> layout -> handler -> loaders
43
+
44
+ Progressive enhancement (no-JS form POST):
45
+ global mw -> action executes -> route mw -> full page re-render
46
+ ```
47
+
48
+ The contract is: **route middleware wraps rendering regardless of transport** (JS-enabled RSC stream or no-JS HTML). During PE re-render, route middleware observes action-set state (cookies, context variables) the same way it does during JS-enabled post-action revalidation.
49
+
50
+ Revalidation is still partial. Route middleware wraps the render pass that
51
+ does happen, but it does not force unrelated outer segments to recompute.
52
+ If a child segment depends on data established by an outer handler/layout,
53
+ revalidate that outer segment too, or have the child guard/reload the
54
+ data itself.
55
+
56
+ ### Revalidation Contracts with Middleware-Backed Trees
57
+
58
+ Middleware can establish request-level context (`ctx.set`) for segments that
59
+ execute in the current render pass. It does not change partial revalidation
60
+ boundaries between handler/layout/parallel segments.
61
+
62
+ For shared segment data, use named revalidation contracts on both the producer
63
+ and consumer segments, even when middleware is present in the chain.
64
+
65
+ ```typescript
66
+ export const revalidateCartData = ({ actionId }) =>
67
+ actionId?.includes("src/actions/cart.ts#") ?? false;
68
+
69
+ layout(CartLayout, () => [
70
+ middleware(cartRenderMiddleware),
71
+ revalidate(revalidateCartData), // producer reruns
72
+ parallel(
73
+ { "@cart": CartSummary },
74
+ () => [revalidate(revalidateCartData)], // consumer reruns
75
+ ),
76
+ ]);
77
+ ```
78
+
79
+ You can package those contracts as importable helpers to avoid repeating
80
+ `revalidate(...)` at each segment:
81
+
82
+ ```typescript
83
+ import { revalidate } from "@rangojs/router";
84
+
85
+ export const revalidateCart = () => [revalidate(revalidateCartData)];
86
+
87
+ layout(CartLayout, () => [
88
+ middleware(cartRenderMiddleware),
89
+ revalidateCart(),
90
+ parallel({ "@cart": CartSummary }, () => [revalidateCart()]),
91
+ ]);
92
+ ```
93
+
94
+ Route middleware is the right place for per-route concerns that affect rendering (setting context variables for handlers, adding response headers, reading cookies set by actions). It is NOT the right place for action guards -- use global middleware for that.
95
+
11
96
  ## Basic Middleware
12
97
 
13
98
  ```typescript
14
- import { createMiddleware } from "@rangojs/router";
99
+ import type { Middleware } from "@rangojs/router";
15
100
 
16
- export const authMiddleware = createMiddleware(async (ctx, next) => {
101
+ export const authMiddleware: Middleware = async (ctx, next) => {
17
102
  const token = ctx.request.headers.get("Authorization");
18
103
 
19
104
  if (!token) {
@@ -21,10 +106,10 @@ export const authMiddleware = createMiddleware(async (ctx, next) => {
21
106
  }
22
107
 
23
108
  const user = await verifyToken(token);
24
- ctx.env.Variables.user = user;
109
+ ctx.set("user", user);
25
110
 
26
111
  await next();
27
- });
112
+ };
28
113
  ```
29
114
 
30
115
  ## Using Middleware in Routes
@@ -54,56 +139,141 @@ export const urlpatterns = urls(({ path, layout, middleware }) => [
54
139
  ## Middleware with Multiple Handlers
55
140
 
56
141
  ```typescript
57
- // Spread multiple middleware from a single export
142
+ // Group multiple middleware in an array
58
143
  export const shopMiddleware = [loggerMiddleware, mockAuthMiddleware];
59
144
 
60
- // In routes
145
+ // In routes — pass the array directly
61
146
  layout(<ShopLayout />, () => [
62
- middleware(...shopMiddleware),
147
+ middleware(shopMiddleware),
63
148
 
64
149
  path("/shop", ShopIndex, { name: "shop" }),
65
150
  ])
66
151
  ```
67
152
 
153
+ ## Wrapping Middleware (Scoped to Children)
154
+
155
+ Use the wrapping form to scope middleware to a subset of routes without
156
+ introducing a visible layout:
157
+
158
+ ```typescript
159
+ urls(({ path, middleware }) => [
160
+ // authMw only applies to /admin and /admin/settings
161
+ middleware(authMw, () => [
162
+ path("/admin", AdminPage, { name: "admin" }),
163
+ path("/admin/settings", SettingsPage, { name: "settings" }),
164
+ ]),
165
+
166
+ // Public route — no authMw
167
+ path("/", HomePage, { name: "home" }),
168
+ ]);
169
+ ```
170
+
171
+ Multiple middleware with wrapping:
172
+
173
+ ```typescript
174
+ middleware([authMw, loggingMw], () => [
175
+ path("/admin", AdminPage, { name: "admin" }),
176
+ ]);
177
+ ```
178
+
179
+ This creates a transparent layout (`<Outlet />`) that carries the middleware.
180
+ The middleware does not affect sibling routes outside the callback.
181
+
68
182
  ## Middleware Context
69
183
 
70
184
  ```typescript
71
- export const myMiddleware = createMiddleware(async (ctx, next) => {
185
+ export const myMiddleware: Middleware = async (ctx, next) => {
72
186
  // Access request
73
- ctx.request; // Request object
74
- ctx.url; // Parsed URL
75
- ctx.params; // Route parameters
187
+ ctx.request; // Request object
188
+ ctx.url; // Parsed URL
189
+ ctx.params; // Route parameters
76
190
 
77
- // Access environment
78
- ctx.env.Bindings.DB; // Cloudflare bindings
79
- ctx.env.Variables; // Mutable variables
191
+ // Access platform bindings (plain bindings from createRouter<TEnv>())
192
+ ctx.env.DB; // D1Database
193
+ ctx.env.KV; // KVNamespace
80
194
 
81
- // Set variables for downstream handlers
82
- ctx.env.Variables.user = { id: "123", name: "John" };
195
+ // Set variables for downstream handlers (typed via RSCRouter.Vars)
196
+ ctx.set("user", { id: "123", name: "John" });
83
197
 
84
198
  // Continue to next middleware/handler
85
199
  await next();
86
200
 
87
201
  // After handler (response intercepting)
88
202
  console.log("Handler completed");
203
+ };
204
+ ```
205
+
206
+ ### Typed context variables in middleware
207
+
208
+ Use `createVar<T>()` for type-safe data sharing between middleware and handlers:
209
+
210
+ ```typescript
211
+ import { createVar } from "@rangojs/router";
212
+ import type { Middleware } from "@rangojs/router";
213
+
214
+ interface AuthUser { id: string; email: string; role: string }
215
+ export const CurrentUser = createVar<AuthUser>();
216
+
217
+ export const authMiddleware: Middleware = async (ctx, next) => {
218
+ const token = ctx.request.headers.get("Authorization");
219
+ if (!token) throw new Response("Unauthorized", { status: 401 });
220
+
221
+ const user = await verifyToken(token);
222
+ ctx.set(CurrentUser, user); // type-checked
223
+ await next();
224
+ };
225
+
226
+ // In a handler -- typed read
227
+ import { CurrentUser } from "./middleware";
228
+
229
+ const Dashboard: Handler<"dashboard"> = (ctx) => {
230
+ const user = ctx.get(CurrentUser); // typed as AuthUser | undefined
231
+ return <DashboardPage user={user!} />;
232
+ };
233
+ ```
234
+
235
+ This works alongside `ctx.get("key")` / `ctx.set("key", value)` (global typing
236
+ via RSCRouter.Vars augmentation). Use `createVar` for route-local or feature-scoped
237
+ data; use RSCRouter.Vars for app-wide middleware state.
238
+
239
+ ## Redirect with State in Middleware
240
+
241
+ ```typescript
242
+ import { redirect, createLocationState } from "@rangojs/router";
243
+ import type { Middleware } from "@rangojs/router";
244
+
245
+ export const FlashMessage = createLocationState<{ text: string }>({
246
+ flash: true,
89
247
  });
248
+
249
+ export const requireAuthMiddleware: Middleware = async (ctx, next) => {
250
+ const token = ctx.request.headers.get("Authorization");
251
+ if (!token) {
252
+ return redirect("/login", {
253
+ state: [FlashMessage({ text: "Please log in to continue" })],
254
+ });
255
+ }
256
+ await next();
257
+ };
90
258
  ```
91
259
 
260
+ Read the flash on the target page with `useLocationState(FlashMessage)`. The `{ flash: true }` option makes it auto-clear after first render. See `/hooks`.
261
+
92
262
  ## Authentication Middleware
93
263
 
94
264
  ```typescript
95
- export const requireAuthMiddleware = createMiddleware(async (ctx, next) => {
96
- const user = ctx.env.Variables.user;
265
+ export const requireAuthMiddleware: Middleware = async (ctx, next) => {
266
+ const user = ctx.get("user");
97
267
 
98
268
  if (!user) {
99
269
  throw new Response("Unauthorized", { status: 401 });
100
270
  }
101
271
 
102
272
  await next();
103
- });
273
+ };
104
274
 
105
- export const permissionsMiddleware = createMiddleware(async (ctx, next) => {
106
- const user = ctx.env.Variables.user;
275
+ export const permissionsMiddleware: Middleware = async (ctx, next) => {
276
+ const user = ctx.get("user");
107
277
  const requiredPermission = "admin";
108
278
 
109
279
  if (!user?.permissions?.includes(requiredPermission)) {
@@ -111,13 +281,13 @@ export const permissionsMiddleware = createMiddleware(async (ctx, next) => {
111
281
  }
112
282
 
113
283
  await next();
114
- });
284
+ };
115
285
  ```
116
286
 
117
287
  ## Logger Middleware
118
288
 
119
289
  ```typescript
120
- export const loggerMiddleware = createMiddleware(async (ctx, next) => {
290
+ export const loggerMiddleware: Middleware = async (ctx, next) => {
121
291
  const start = Date.now();
122
292
 
123
293
  console.log(`[${ctx.request.method}] ${ctx.url.pathname}`);
@@ -126,54 +296,54 @@ export const loggerMiddleware = createMiddleware(async (ctx, next) => {
126
296
 
127
297
  const duration = Date.now() - start;
128
298
  console.log(`[${ctx.request.method}] ${ctx.url.pathname} - ${duration}ms`);
129
- });
299
+ };
130
300
  ```
131
301
 
132
302
  ## Rate Limiting Middleware
133
303
 
134
304
  ```typescript
135
- export const rateLimitMiddleware = createMiddleware(async (ctx, next) => {
305
+ export const rateLimitMiddleware: Middleware = async (ctx, next) => {
136
306
  const ip = ctx.request.headers.get("CF-Connecting-IP") ?? "unknown";
137
307
  const key = `rate-limit:${ip}`;
138
308
 
139
- const count = await ctx.env.Bindings.KV.get(key);
309
+ const count = await ctx.env.KV.get(key);
140
310
  const requests = count ? parseInt(count) : 0;
141
311
 
142
312
  if (requests > 100) {
143
313
  throw new Response("Too Many Requests", { status: 429 });
144
314
  }
145
315
 
146
- await ctx.env.Bindings.KV.put(key, String(requests + 1), {
316
+ await ctx.env.KV.put(key, String(requests + 1), {
147
317
  expirationTtl: 60,
148
318
  });
149
319
 
150
320
  await next();
151
- });
321
+ };
152
322
  ```
153
323
 
154
324
  ## Complete Example
155
325
 
156
326
  ```typescript
157
327
  // middleware/index.ts
158
- import { createMiddleware } from "@rangojs/router";
328
+ import type { Middleware } from "@rangojs/router";
159
329
 
160
- export const loggerMiddleware = createMiddleware(async (ctx, next) => {
330
+ export const loggerMiddleware: Middleware = async (ctx, next) => {
161
331
  console.log(`[${ctx.request.method}] ${ctx.url.pathname}`);
162
332
  await next();
163
- });
333
+ };
164
334
 
165
- export const mockAuthMiddleware = createMiddleware(async (ctx, next) => {
335
+ export const mockAuthMiddleware: Middleware = async (ctx, next) => {
166
336
  // Mock user for development
167
- ctx.env.Variables.user = { id: "1", name: "Demo User" };
337
+ ctx.set("user", { id: "1", name: "Demo User" });
168
338
  await next();
169
- });
339
+ };
170
340
 
171
- export const requireAuthMiddleware = createMiddleware(async (ctx, next) => {
172
- if (!ctx.env.Variables.user) {
341
+ export const requireAuthMiddleware: Middleware = async (ctx, next) => {
342
+ if (!ctx.get("user")) {
173
343
  throw new Response("Unauthorized", { status: 401 });
174
344
  }
175
345
  await next();
176
- });
346
+ };
177
347
 
178
348
  // urls.tsx
179
349
  import { urls } from "@rangojs/router";