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

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