@rangojs/router 0.16.0 → 0.17.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 (245) hide show
  1. package/README.md +254 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  4. package/dist/types/browser/partial-update.d.ts +11 -0
  5. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  6. package/dist/types/browser/rsc-router.d.ts +1 -1
  7. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  8. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  9. package/dist/types/cache/cache-scope.d.ts +34 -5
  10. package/dist/types/cache/handle-capture.d.ts +41 -3
  11. package/dist/types/cache/handle-snapshot.d.ts +53 -13
  12. package/dist/types/cache/segment-codec.d.ts +10 -3
  13. package/dist/types/cache/taint.d.ts +1 -1
  14. package/dist/types/cache/types.d.ts +15 -3
  15. package/dist/types/client.d.ts +4 -4
  16. package/dist/types/client.rsc.d.ts +4 -5
  17. package/dist/types/components/DefaultDocument.d.ts +5 -2
  18. package/dist/types/context-var.d.ts +6 -5
  19. package/dist/types/escape-script.d.ts +1 -1
  20. package/dist/types/handles/MetaTags.d.ts +5 -4
  21. package/dist/types/handles/Scripts.d.ts +8 -7
  22. package/dist/types/handles/meta.d.ts +2 -2
  23. package/dist/types/handles/script.d.ts +17 -16
  24. package/dist/types/href-client.d.ts +1 -1
  25. package/dist/types/html.d.ts +12 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.rsc.d.ts +3 -2
  28. package/dist/types/loader-store.d.ts +7 -4
  29. package/dist/types/reverse.d.ts +36 -4
  30. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  31. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  32. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  33. package/dist/types/route-types.d.ts +1 -1
  34. package/dist/types/router/debug-manifest.d.ts +3 -2
  35. package/dist/types/router/error-handling.d.ts +20 -0
  36. package/dist/types/router/handler-context.d.ts +6 -0
  37. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  38. package/dist/types/router/middleware-types.d.ts +3 -2
  39. package/dist/types/router/router-context.d.ts +3 -1
  40. package/dist/types/router/router-interfaces.d.ts +24 -2
  41. package/dist/types/router/router-options.d.ts +8 -5
  42. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  43. package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
  44. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  45. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
  46. package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
  47. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  48. package/dist/types/router/segment-wrappers.d.ts +1 -1
  49. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  50. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  51. package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
  52. package/dist/types/rsc/shell-capture.d.ts +5 -4
  53. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  54. package/dist/types/rsc/types.d.ts +10 -0
  55. package/dist/types/server/context.d.ts +33 -3
  56. package/dist/types/server/handle-store.d.ts +26 -2
  57. package/dist/types/server/request-context.d.ts +24 -21
  58. package/dist/types/ssr/index.d.ts +15 -0
  59. package/dist/types/static-handler.d.ts +4 -1
  60. package/dist/types/testing/generated-routes.d.ts +2 -2
  61. package/dist/types/testing/internal/context.d.ts +1 -1
  62. package/dist/types/testing/render-route.d.ts +32 -6
  63. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  64. package/dist/types/testing/vitest.d.ts +35 -1
  65. package/dist/types/theme/ThemeScript.d.ts +3 -3
  66. package/dist/types/types/cache-types.d.ts +14 -10
  67. package/dist/types/types/handler-context.d.ts +14 -0
  68. package/dist/types/types/loader-types.d.ts +8 -6
  69. package/dist/types/urls/response-types.d.ts +3 -12
  70. package/dist/types/use-loader.d.ts +12 -3
  71. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  72. package/dist/types/vite/rango.d.ts +1 -1
  73. package/dist/vite/index.js +1381 -1346
  74. package/package.json +2 -2
  75. package/skills/api-client/SKILL.md +22 -10
  76. package/skills/breadcrumbs/SKILL.md +65 -22
  77. package/skills/bundle-analysis/SKILL.md +22 -22
  78. package/skills/cache-guide/SKILL.md +207 -97
  79. package/skills/caching/SKILL.md +295 -93
  80. package/skills/catalog.json +16 -16
  81. package/skills/client-urls/SKILL.md +53 -31
  82. package/skills/cloudflare/SKILL.md +72 -20
  83. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  84. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  85. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  86. package/skills/comparison/SKILL.md +5 -0
  87. package/skills/comparison/references/framework-comparison.md +39 -33
  88. package/skills/composability/SKILL.md +38 -29
  89. package/skills/css/SKILL.md +10 -13
  90. package/skills/debug-manifest/SKILL.md +74 -31
  91. package/skills/defer-hydration/SKILL.md +2 -1
  92. package/skills/deployment-caching/SKILL.md +6 -1
  93. package/skills/document-cache/SKILL.md +92 -28
  94. package/skills/fonts/SKILL.md +16 -8
  95. package/skills/handler-use/SKILL.md +53 -17
  96. package/skills/hooks/SKILL.md +28 -20
  97. package/skills/hooks/data.md +23 -3
  98. package/skills/hooks/handle-and-actions.md +31 -16
  99. package/skills/hooks/navigation.md +39 -10
  100. package/skills/hooks/outlets.md +13 -6
  101. package/skills/hooks/state.md +37 -15
  102. package/skills/hooks/urls.md +7 -5
  103. package/skills/host-router/SKILL.md +64 -12
  104. package/skills/i18n/SKILL.md +60 -34
  105. package/skills/intercept/SKILL.md +161 -26
  106. package/skills/layout/SKILL.md +106 -50
  107. package/skills/links/SKILL.md +51 -19
  108. package/skills/loader/SKILL.md +242 -106
  109. package/skills/middleware/SKILL.md +119 -23
  110. package/skills/migrate-nextjs/SKILL.md +100 -45
  111. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  112. package/skills/migrate-react-router/SKILL.md +49 -38
  113. package/skills/migrate-react-router/component-migration.md +25 -6
  114. package/skills/migrate-react-router/data-and-actions.md +18 -5
  115. package/skills/migrate-react-router/route-mapping.md +12 -6
  116. package/skills/mime-routes/SKILL.md +12 -7
  117. package/skills/observability/SKILL.md +28 -14
  118. package/skills/parallel/SKILL.md +75 -39
  119. package/skills/ppr/SKILL.md +345 -150
  120. package/skills/prerender/SKILL.md +189 -100
  121. package/skills/rango/SKILL.md +65 -34
  122. package/skills/react-compiler/SKILL.md +6 -5
  123. package/skills/response-routes/SKILL.md +133 -25
  124. package/skills/route/SKILL.md +165 -60
  125. package/skills/router-setup/SKILL.md +418 -240
  126. package/skills/scripts/SKILL.md +31 -20
  127. package/skills/server-actions/SKILL.md +220 -59
  128. package/skills/shell-manifest/SKILL.md +12 -7
  129. package/skills/streams-and-websockets/SKILL.md +32 -14
  130. package/skills/tailwind/SKILL.md +37 -33
  131. package/skills/testing/SKILL.md +68 -32
  132. package/skills/testing/cache-prerender.md +55 -29
  133. package/skills/testing/client-components.md +59 -6
  134. package/skills/testing/e2e-parity.md +12 -2
  135. package/skills/testing/flight.md +19 -11
  136. package/skills/testing/handles.md +8 -5
  137. package/skills/testing/loader.md +8 -5
  138. package/skills/testing/middleware.md +8 -3
  139. package/skills/testing/predicates.md +120 -0
  140. package/skills/testing/render-handler.md +70 -22
  141. package/skills/testing/response-routes.md +11 -4
  142. package/skills/testing/reverse-and-types.md +20 -14
  143. package/skills/testing/server-actions.md +12 -3
  144. package/skills/testing/server-tree.md +9 -0
  145. package/skills/testing/setup.md +9 -5
  146. package/skills/theme/SKILL.md +106 -29
  147. package/skills/typesafety/SKILL.md +20 -9
  148. package/skills/typesafety/env-and-bindings.md +126 -47
  149. package/skills/typesafety/generated-files-and-cli.md +26 -14
  150. package/skills/typesafety/params-and-search.md +44 -19
  151. package/skills/typesafety/route-types.md +50 -14
  152. package/skills/use-cache/SKILL.md +200 -82
  153. package/skills/vercel/SKILL.md +21 -11
  154. package/skills/view-transitions/SKILL.md +41 -20
  155. package/src/__augment-tests__/augmented.check.ts +26 -1
  156. package/src/browser/index.ts +2 -2
  157. package/src/browser/navigation-bridge.ts +3 -4
  158. package/src/browser/navigation-transaction.ts +22 -2
  159. package/src/browser/partial-update.ts +35 -25
  160. package/src/browser/react/ScrollRestoration.tsx +6 -6
  161. package/src/browser/rsc-router.tsx +1 -1
  162. package/src/browser/scroll-restoration.ts +53 -6
  163. package/src/cache/cache-exec-scope.ts +37 -4
  164. package/src/cache/cache-key-utils.ts +1 -6
  165. package/src/cache/cache-runtime.ts +230 -61
  166. package/src/cache/cache-scope.ts +76 -12
  167. package/src/cache/document-cache.ts +15 -0
  168. package/src/cache/handle-capture.ts +90 -10
  169. package/src/cache/handle-snapshot.ts +132 -28
  170. package/src/cache/segment-codec.ts +26 -7
  171. package/src/cache/tag-invalidation.ts +4 -3
  172. package/src/cache/taint.ts +1 -1
  173. package/src/cache/types.ts +16 -3
  174. package/src/client.rsc.tsx +10 -9
  175. package/src/client.tsx +5 -4
  176. package/src/components/DefaultDocument.tsx +7 -2
  177. package/src/context-var.ts +6 -5
  178. package/src/escape-script.ts +1 -1
  179. package/src/handles/MetaTags.tsx +13 -11
  180. package/src/handles/Scripts.tsx +11 -10
  181. package/src/handles/meta.ts +2 -2
  182. package/src/handles/script.ts +17 -16
  183. package/src/href-client.ts +1 -1
  184. package/src/html.ts +12 -0
  185. package/src/index.rsc.ts +5 -0
  186. package/src/index.ts +1 -0
  187. package/src/loader-store.ts +7 -4
  188. package/src/prerender/build-shell-capture.ts +8 -11
  189. package/src/reverse.ts +44 -4
  190. package/src/route-definition/dsl-helpers.ts +117 -30
  191. package/src/route-definition/helpers-types.ts +33 -9
  192. package/src/route-definition/resolve-handler-use.ts +121 -6
  193. package/src/route-types.ts +0 -3
  194. package/src/router/debug-manifest.ts +10 -2
  195. package/src/router/error-handling.ts +51 -15
  196. package/src/router/handler-context.ts +34 -25
  197. package/src/router/intercept-resolution.ts +125 -39
  198. package/src/router/loader-resolution.ts +56 -11
  199. package/src/router/match-api.ts +12 -17
  200. package/src/router/match-middleware/background-revalidation.ts +161 -96
  201. package/src/router/match-middleware/cache-lookup.ts +93 -10
  202. package/src/router/match-middleware/cache-store.ts +17 -90
  203. package/src/router/middleware-types.ts +3 -5
  204. package/src/router/middleware.ts +52 -9
  205. package/src/router/prerender-match.ts +53 -19
  206. package/src/router/route-snapshot.ts +11 -1
  207. package/src/router/router-context.ts +2 -0
  208. package/src/router/router-interfaces.ts +25 -2
  209. package/src/router/router-options.ts +8 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -12
  211. package/src/router/segment-resolution/loader-cache.ts +205 -45
  212. package/src/router/segment-resolution/loader-mask.ts +12 -22
  213. package/src/router/segment-resolution/loader-snapshot.ts +6 -6
  214. package/src/router/segment-resolution/mask-nested.ts +7 -7
  215. package/src/router/segment-resolution/revalidation.ts +34 -6
  216. package/src/router/segment-wrappers.ts +4 -0
  217. package/src/rsc/render-pipeline.ts +3 -0
  218. package/src/rsc/response-route-handler.ts +11 -3
  219. package/src/rsc/rsc-rendering.ts +8 -45
  220. package/src/rsc/shell-capture-constants.ts +13 -0
  221. package/src/rsc/shell-capture.ts +111 -90
  222. package/src/rsc/ssr-setup.ts +14 -8
  223. package/src/rsc/types.ts +11 -0
  224. package/src/server/context.ts +88 -11
  225. package/src/server/cookie-store.ts +5 -4
  226. package/src/server/handle-store.ts +232 -7
  227. package/src/server/request-context.ts +35 -30
  228. package/src/ssr/index.tsx +40 -7
  229. package/src/static-handler.ts +4 -1
  230. package/src/testing/flight-runtime.d.ts +21 -0
  231. package/src/testing/generated-routes.ts +2 -2
  232. package/src/testing/internal/context.ts +1 -1
  233. package/src/testing/internal/flight-client-globals.ts +7 -5
  234. package/src/testing/render-route.tsx +85 -24
  235. package/src/testing/run-loader.ts +5 -1
  236. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  237. package/src/testing/vitest.ts +62 -1
  238. package/src/theme/ThemeScript.tsx +3 -3
  239. package/src/types/cache-types.ts +14 -10
  240. package/src/types/handler-context.ts +14 -0
  241. package/src/types/loader-types.ts +8 -6
  242. package/src/urls/response-types.ts +3 -23
  243. package/src/use-loader.tsx +12 -3
  244. package/src/vite/plugins/use-cache-transform.ts +118 -74
  245. package/src/vite/rango.ts +7 -3
@@ -1,12 +1,17 @@
1
1
  ---
2
2
  name: middleware
3
- description: Define middleware for authentication, logging, and request processing in @rangojs/router. Use when gating routes behind auth checks, logging requests, or running shared logic before a handler runs.
3
+ description: Define global (router.use) and route (middleware()) middleware in @rangojs/router — auth gates, logging, response headers, context variables, redirects. Use when gating routes behind auth checks, logging requests, setting headers or ctx variables before a handler runs, or deciding whether middleware wraps server actions.
4
4
  argument-hint: [middleware-name]
5
5
  ---
6
6
 
7
7
  # Middleware
8
8
 
9
- Middleware runs before/after route handlers using the onion model.
9
+ Middleware runs before and after route handlers using the onion model: code
10
+ before `await next()` runs on the way in, code after it runs on the way out
11
+ with the downstream `Response` available. This skill covers the two
12
+ registration levels (global `router.use()` and route `middleware()`), what each
13
+ wraps (actions, renders, fetchable loaders), the middleware context, and
14
+ common patterns.
10
15
 
11
16
  ## Execution Model
12
17
 
@@ -23,13 +28,15 @@ const router = createRouter<AppEnv>({})
23
28
  .routes(urlpatterns);
24
29
  ```
25
30
 
26
- When the router has a `basename`, pattern-scoped `.use()` patterns are automatically prefixed. For example, with `basename: "/app"`, `.use("/admin/*", mw)` matches `/app/admin/*`.
31
+ Scope patterns use the route pattern syntax: `:param` (typed on `ctx.params`), optional `:param?`, constrained `:locale(en|gb)`, and a trailing `*` wildcard. When the router has a `basename`, pattern-scoped `.use()` patterns are automatically prefixed. For example, with `basename: "/app"`, `.use("/admin/*", mw)` matches `/app/admin/*`.
32
+
33
+ A pattern matches the request URL. Server actions are posted to the current page's URL, but an action can be invoked through any URL that matches a route, so a pattern-scoped guard is not an authorization boundary for actions. Authorize sensitive actions inside the action body (see `/server-actions` → "Authorization in actions").
27
34
 
28
35
  ### Route middleware (`middleware()` in `urls()`)
29
36
 
30
37
  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).
31
38
 
32
- > **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.
39
+ > **Implication for auth:** route middleware cannot guard server actions. Load identity in global `router.use()` middleware (it wraps actions) and check authorization inside the action body. See `/server-actions` for action-side auth patterns.
33
40
 
34
41
  ```
35
42
  Request flow (with action):
@@ -74,9 +81,10 @@ For shared segment data, use named revalidation contracts on both the producer
74
81
  and consumer segments, even when middleware is present in the chain.
75
82
 
76
83
  ```typescript
84
+ import type { Revalidate } from "@rangojs/router";
77
85
  import * as CartActions from "./actions/cart";
78
86
 
79
- export const revalidateCartData = (ctx) =>
87
+ export const revalidateCartData: Revalidate = (ctx) =>
80
88
  ctx.isAction(CartActions) || undefined;
81
89
 
82
90
  layout(CartLayout, () => [
@@ -104,7 +112,7 @@ layout(CartLayout, () => [
104
112
  ]);
105
113
  ```
106
114
 
107
- 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.
115
+ 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 to load identity and check authorization in the action body.
108
116
 
109
117
  ## Basic Middleware
110
118
 
@@ -125,6 +133,13 @@ export const authMiddleware: Middleware = async (ctx, next) => {
125
133
  };
126
134
  ```
127
135
 
136
+ Every middleware must either call `next()` or return (or throw) a `Response`.
137
+ Returning or throwing a `Response` short-circuits the chain; downstream
138
+ middleware and the handler do not run, and headers/cookies already set on the
139
+ context are merged into it. A middleware that does neither throws an error; any
140
+ other return value is ignored with a warning. `await next()` resolves to the
141
+ downstream `Response`, so `return next()` and `await next()` are both fine.
142
+
128
143
  ## Using Middleware in Routes
129
144
 
130
145
  ```typescript
@@ -132,7 +147,7 @@ import { urls } from "@rangojs/router";
132
147
  import { authMiddleware, loggerMiddleware } from "./middleware";
133
148
 
134
149
  export const urlpatterns = urls(({ path, layout, middleware }) => [
135
- // Global middleware for all routes in this file
150
+ // Route middleware for every route in this urls() tree (wraps renders, not actions)
136
151
  middleware(loggerMiddleware),
137
152
 
138
153
  // Layout with scoped middleware
@@ -192,31 +207,90 @@ middleware([authMw, loggingMw], () => [
192
207
  This creates a transparent layout (`<Outlet />`) that carries the middleware.
193
208
  The middleware does not affect sibling routes outside the callback.
194
209
 
210
+ That scope comes from the routes inside the callback. With no routes inside,
211
+ the wrapper scopes nothing: its middleware runs for every route of the
212
+ enclosing path or layout. Such a wrapper cannot contain `layout()`;
213
+ `router.routes()` throws and points to the flat form:
214
+
215
+ ```typescript
216
+ // throws at definition time
217
+ path("/account", AccountPage, { name: "account" }, () => [
218
+ middleware(requireAuth, () => [layout(<AccountNav />)]),
219
+ ]);
220
+ // same behavior, written flat
221
+ path("/account", AccountPage, { name: "account" }, () => [
222
+ middleware(requireAuth),
223
+ layout(<AccountNav />),
224
+ ]);
225
+ ```
226
+
195
227
  ## Middleware Context
196
228
 
197
229
  ```typescript
198
- export const myMiddleware: Middleware = async (ctx, next) => {
199
- // Access request
200
- ctx.request; // Request object
201
- ctx.url; // Parsed URL
202
- ctx.params; // Route parameters
203
- ctx.build; // in middleware: true only during Prerender + ppr build-shell capture (plain Prerender does not run middleware)
230
+ import { cookies, headers } from "@rangojs/router";
231
+ import type { Middleware } from "@rangojs/router";
232
+ import { FlashMessage } from "./location-states";
204
233
 
205
- // Access platform bindings (plain bindings from createRouter<TEnv>())
234
+ export const myMiddleware: Middleware = async (ctx, next) => {
235
+ // Request
236
+ ctx.request; // incoming Request (raw URL, method, body)
237
+ ctx.url; // URL with internal _rsc* params stripped (also ctx.pathname, ctx.searchParams)
238
+ ctx.params; // params from the router.use() pattern or the matched route
239
+ ctx.routeName; // matched route name (reliable after await next() in global middleware)
240
+ ctx.build; // true only during Prerender + ppr build-shell capture (plain Prerender does not run middleware)
241
+ headers().get("accept-language"); // read-only request headers (free function)
242
+ cookies().get("session")?.value; // request cookies (free function; also .set/.delete)
243
+
244
+ // Platform bindings (plain bindings from createRouter<TEnv>())
206
245
  ctx.env.DB; // D1Database
207
- ctx.env.KV; // KVNamespace
246
+ ctx.waitUntil(async () => {}); // work after the response is sent
208
247
 
209
- // Set variables for downstream handlers (typed via Rango.Vars)
248
+ // Variables for downstream middleware, handlers, and loaders (typed via Rango.Vars)
210
249
  ctx.set("user", { id: "123", name: "John" });
250
+ ctx.get("user");
251
+
252
+ // Response shaping
253
+ ctx.header("X-Frame-Options", "DENY"); // set one response header
254
+ ctx.headers; // response Headers (stub before next(), the real response after)
255
+ ctx.setLocationState(FlashMessage({ text: "Saved" })); // history state for the client
256
+ ctx.reverse("home"); // URL for a global route name (no ".name", no param auto-fill)
211
257
 
212
258
  // Opt the current request out of PPR shell lookup/capture.
213
259
  ctx.dynamic();
260
+ // Print the per-request performance timeline (see /loader → "debugPerformance").
261
+ ctx.debugPerformance();
262
+
263
+ // Continue to the next middleware / the handler
264
+ const response = await next();
265
+
266
+ // After the handler: the real Response is available
267
+ console.log(response.status);
268
+ };
269
+ ```
270
+
271
+ Cookies and request headers are not `ctx` members; use the free functions
272
+ `cookies()` and `headers()` from `@rangojs/router`.
273
+
274
+ ### Changing the response after `next()`
214
275
 
215
- // Continue to next middleware/handler
276
+ After `await next()`, `ctx.header()` and `ctx.headers` write to the real
277
+ downstream response. Returning a different `Response` replaces it.
278
+
279
+ ```typescript
280
+ export const securityHeaders: Middleware = async (ctx, next) => {
216
281
  await next();
282
+ ctx.header("Strict-Transport-Security", "max-age=63072000");
283
+ ctx.headers.set("X-Content-Type-Options", "nosniff");
284
+ };
217
285
 
218
- // After handler (response intercepting)
219
- console.log("Handler completed");
286
+ export const timing: Middleware = async (ctx, next) => {
287
+ const start = performance.now();
288
+ try {
289
+ await next();
290
+ } finally {
291
+ // try/catch around next() also sees downstream errors
292
+ console.log(`${ctx.pathname} ${(performance.now() - start).toFixed(1)}ms`);
293
+ }
220
294
  };
221
295
  ```
222
296
 
@@ -229,7 +303,8 @@ import { createVar } from "@rangojs/router";
229
303
  import type { Middleware } from "@rangojs/router";
230
304
 
231
305
  interface AuthUser { id: string; email: string; role: string }
232
- export const CurrentUser = createVar<AuthUser>();
306
+ // cache: false — per-user data must never bake into a cache() segment
307
+ export const CurrentUser = createVar<AuthUser>({ cache: false });
233
308
 
234
309
  export const authMiddleware: Middleware = async (ctx, next) => {
235
310
  const token = ctx.request.headers.get("Authorization");
@@ -241,6 +316,7 @@ export const authMiddleware: Middleware = async (ctx, next) => {
241
316
  };
242
317
 
243
318
  // In a handler -- typed read
319
+ import type { Handler } from "@rangojs/router";
244
320
  import { CurrentUser } from "./middleware";
245
321
 
246
322
  const Dashboard: Handler<"dashboard"> = (ctx) => {
@@ -253,6 +329,18 @@ This works alongside `ctx.get("key")` / `ctx.set("key", value)` (global typing
253
329
  via Rango.Vars augmentation). Use `createVar` for route-local or feature-scoped
254
330
  data; use Rango.Vars for app-wide middleware state.
255
331
 
332
+ Mark request-specific data (sessions, users, tokens) non-cacheable: either on
333
+ the token (`createVar<T>({ cache: false })`) or per write
334
+ (`ctx.set("user", user, { cache: false })`). Reading it with `ctx.get()` inside
335
+ a `cache()` boundary or a `"use cache"` function then throws instead of baking
336
+ one user's data into a shared entry (inside `"use cache"`, also through a
337
+ middleware `ctx` passed in); loaders can still read it because they always run
338
+ fresh (a `"use cache"` function a loader calls cannot). A middleware `ctx`
339
+ passed to a `"use cache"` function keys the call by host, pathname, params and
340
+ search, plus the route name once the route is matched (a global middleware
341
+ before `next()` has none yet), and its `set()`, `header()` and `headers` writes
342
+ throw inside the function (`/use-cache`).
343
+
256
344
  ## Build-Time PPR Middleware
257
345
 
258
346
  Normal `Prerender` Flight payload collection does not run middleware: there is
@@ -285,12 +373,20 @@ export const commerceMiddleware: Middleware = async (ctx, next) => {
285
373
  ## Redirect with State in Middleware
286
374
 
287
375
  ```typescript
288
- import { redirect, createLocationState } from "@rangojs/router";
289
- import type { Middleware } from "@rangojs/router";
376
+ // location-states.ts — shared module; the client component that reads the
377
+ // state must import the same definition
378
+ import { createLocationState } from "@rangojs/router";
290
379
 
291
380
  export const FlashMessage = createLocationState<{ text: string }>({
292
381
  flash: true,
293
382
  });
383
+ ```
384
+
385
+ ```typescript
386
+ // middleware/auth.ts
387
+ import { redirect } from "@rangojs/router";
388
+ import type { Middleware } from "@rangojs/router";
389
+ import { FlashMessage } from "../location-states";
294
390
 
295
391
  export const requireAuthMiddleware: Middleware = async (ctx, next) => {
296
392
  const token = ctx.request.headers.get("Authorization");
@@ -400,7 +496,7 @@ import {
400
496
  } from "./middleware";
401
497
 
402
498
  export const urlpatterns = urls(({ path, layout, middleware }) => [
403
- // Global middleware
499
+ // Route middleware for every route in this tree (use router.use() to also wrap actions)
404
500
  middleware(loggerMiddleware),
405
501
  middleware(mockAuthMiddleware),
406
502
 
@@ -6,6 +6,13 @@ argument-hint: [path-to-nextjs-app]
6
6
 
7
7
  # Migrate from Next.js App Router to @rangojs/router
8
8
 
9
+ This skill maps a Next.js App Router project onto Rango: project setup, the
10
+ `app/` file conventions to the `urls()` DSL, data fetching and rendering modes
11
+ (SSG, ISR, PPR), middleware, navigation, server actions, metadata, API routes,
12
+ and theming. Use it when porting a Next.js app, or when a Next.js habit needs a
13
+ Rango equivalent. For a side-by-side evaluation rather than a port, see
14
+ `/comparison`; for React Router or Remix apps, see `/migrate-react-router`.
15
+
9
16
  ## Why Rango
10
17
 
11
18
  Common reasons to migrate:
@@ -99,10 +106,14 @@ Replace Next.js tooling with Vite + Rango:
99
106
 
100
107
  ```bash
101
108
  npm remove next @next/env
102
- npm install @rangojs/router @vitejs/plugin-react
109
+ npm install @rangojs/router # keep react and react-dom
103
110
  npm install -D vite
104
111
  ```
105
112
 
113
+ `rango()` already includes `@vitejs/plugin-rsc` and supplies the client and
114
+ server entries. Add `@vitejs/plugin-react` only if you want Fast Refresh or the
115
+ React Compiler (see `/react-compiler`).
116
+
106
117
  ```typescript
107
118
  // vite.config.ts
108
119
  import { defineConfig } from "vite";
@@ -119,7 +130,7 @@ import { createRouter } from "@rangojs/router";
119
130
  import { Document } from "./document";
120
131
  import { urlpatterns } from "./urls";
121
132
 
122
- export default createRouter({
133
+ export const router = createRouter({
123
134
  document: Document,
124
135
  }).routes(urlpatterns);
125
136
  ```
@@ -223,8 +234,8 @@ The `include()` name has three deliberate modes:
223
234
  | Form | Child route names |
224
235
  | ----------------------------------------------- | ----------------------------------------------------------------------- |
225
236
  | `include("/", patterns)` | private to the included module; omitted from the app-wide generated map |
226
- | `include("/", patterns, { name: "marketing" })` | globally registered as `marketing.home`, `marketing.pricing`, ... |
227
- | `include("/", patterns, { name: "" })` | flattened into the parent map as `home`, `pricing`, ... |
237
+ | `include("/", patterns, { name: "marketing" })` | globally registered as `marketing.landing`, `marketing.pricing`, ... |
238
+ | `include("/", patterns, { name: "" })` | flattened into the parent map as `landing`, `pricing`, ... |
228
239
 
229
240
  Use the empty-string form only when the child names are intentionally global
230
241
  and unique. Inside a private/namespaced module, prefer dot-local reversal or
@@ -300,17 +311,23 @@ run the Phase 0 audit before carrying those calls over unchanged.
300
311
  ```typescript
301
312
  // Next.js:
302
313
  async function ProductPage({ params }) {
303
- const product = await fetch(`/api/products/${params.slug}`).then(r => r.json());
314
+ const product = await fetch(`https://api.example.com/products/${params.slug}`).then(r => r.json());
304
315
  return <div>{product.name}</div>;
305
316
  }
306
317
 
307
318
  // Rango: same pattern, params come from ctx
319
+ import type { Handler } from "@rangojs/router";
320
+
308
321
  const ProductPage: Handler<"product"> = async (ctx) => {
309
- const product = await fetch(`/api/products/${ctx.params.slug}`).then(r => r.json());
322
+ const product = await fetch(`https://api.example.com/products/${ctx.params.slug}`).then(r => r.json());
310
323
  return <div>{product.name}</div>;
311
324
  };
312
325
  ```
313
326
 
327
+ If the Next page fetched its own `/api/...` route, call the underlying service
328
+ function directly instead of making a request back to the same app (see
329
+ [backend-host-swap.md](backend-host-swap.md#extract-shared-server-services)).
330
+
314
331
  ### When to use createLoader
315
332
 
316
333
  Loaders are Rango's live data layer. Use them when you need:
@@ -360,9 +377,11 @@ export const ProductDef = Prerender<{ slug: string }>(
360
377
  );
361
378
 
362
379
  // Rango (with live fallback — matches Next.js dynamicParams behavior):
363
- import { Prerender, Passthrough } from "@rangojs/router";
380
+ import { Prerender, Passthrough, notFound } from "@rangojs/router";
364
381
 
365
- const ProductDef = Prerender<{ slug: string }>(
382
+ // Keep the definition exported: the Vite plugin injects the stable id only
383
+ // into `export const X = Prerender(...)` (or `const X = ...; export { X }`).
384
+ export const ProductDef = Prerender<{ slug: string }>(
366
385
  async () => [{ slug: "a" }, { slug: "b" }],
367
386
  async (ctx) => {
368
387
  const product = await getProduct(ctx.params.slug);
@@ -373,8 +392,12 @@ const ProductDef = Prerender<{ slug: string }>(
373
392
 
374
393
  export const Product = Passthrough(ProductDef, async (ctx) => {
375
394
  const product = await getProduct(ctx.params.slug);
395
+ if (!product) notFound();
376
396
  return <ProductPage product={product} />;
377
397
  });
398
+
399
+ // In urls:
400
+ path("/products/:slug", Product, { name: "product" });
378
401
  ```
379
402
 
380
403
  Use `Passthrough()` whenever the Next.js route has `dynamicParams: true` (the
@@ -415,6 +438,9 @@ export default async function Page({ params }) {
415
438
  // Rango, step 1 — direct carry-over. Your Suspense tree IS the hole model:
416
439
  // hand the un-awaited promise down, keep the boundary, add the ppr option.
417
440
  // No loader, no loading(), no restructuring.
441
+ import { Suspense } from "react";
442
+ import type { HandlerContext } from "@rangojs/router";
443
+
418
444
  function ProductPage(ctx: HandlerContext) {
419
445
  const price = fetchPrice(ctx.params.id); // pending promise — NOT awaited
420
446
  return (
@@ -430,10 +456,9 @@ path("/products/:id", ProductPage, {
430
456
  ppr: { ttl: 600, swr: 120 }, // or ppr: true (default ttl 300s)
431
457
  });
432
458
 
433
- // Rango, step 2 (optional refinement) — promote the fetch to a loader for a
434
- // GUARANTEED hole: loaders are masked at capture and fresh on every serve,
435
- // even when the value resolves instantly (a raw promise that settles fast
436
- // would bake into the shell). loading() is the loader's hole boundary.
459
+ // Rango, step 2 (optional refinement) — promote the fetch to a live loader
460
+ // (no ssr: false) for a GUARANTEED hole, even when the value resolves
461
+ // instantly; loading() is its boundary (/ppr → The loader lane rule).
437
462
  path(
438
463
  "/products/:id",
439
464
  ProductPage,
@@ -449,20 +474,18 @@ Differences that matter during migration:
449
474
  postpones at capture and becomes a hole — existing Next PPR trees keep
450
475
  working as-is, no `loading()` required. One container rule everywhere
451
476
  (handlers, handles, loaders): awaited/settled data bakes into the shell; a
452
- promise nested inside your data stays a live hole. For loaders, `loading()`
453
- selects the lane: present = guaranteed live (masked at capture, fresh every
454
- serve, immune to fast resolution — prefer it for per-request data); absent =
455
- the bake lane (the settled container bakes and is snapshot-pinned per shell,
456
- nested promises stay live). Identity reads (`cookies()`/`headers()`) where
457
- the value would bake refuse the capture by construction.
477
+ promise nested inside your data stays a live hole. For loaders, `ssr: false`
478
+ (not `loading()`) selects the lane — see `/ppr` → The loader lane rule.
479
+ Identity reads (`cookies()`/`headers()`) where the value would bake refuse
480
+ the capture by construction.
458
481
  - **Shell freshness is explicit.** Next's PPR shell is fixed until the next
459
482
  build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
460
483
  `revalidateTag()` drop the shell (`revalidate()` does not — it is a data
461
484
  lever and never touches shell HTML).
462
485
  - **`cookies()`/`headers()` in shell material THROW during capture** (in Next
463
- they silently force dynamic rendering). Per-user reads must move behind a
464
- `loading()` boundary (the live loader lane) or into a nested promise — the
465
- refusal surfaces at migration time, which is the point.
486
+ they silently force dynamic rendering). Per-user reads must move into a live
487
+ loader (no `ssr: false`; a nested promise does not help). The refusal
488
+ surfaces at migration time, which is the point.
466
489
  - **A store is required.** PPR needs the app-level `createRouter({ cache })`
467
490
  store to implement the shell family (`MemorySegmentCacheStore`,
468
491
  `CFCacheStore`, `VercelCacheStore`). Without one the route quietly stays
@@ -496,7 +519,8 @@ renders into). Then invalidate by tag:
496
519
  `updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
497
520
  hard-purge (the next read re-renders fresh); the only difference is awaitability —
498
521
  despite the Next.js name, `revalidateTag` here is NOT stale-while-revalidate.
499
- Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`) index by tag. Next's
522
+ Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`)
523
+ index by tag. Next's
500
524
  `revalidatePath` has no path-based equivalent — tag the relevant entries instead.
501
525
 
502
526
  **2. Partial-render selection (which segments re-run after an action).** This is
@@ -507,7 +531,8 @@ re-rendering:
507
531
  ```typescript
508
532
  import { updateBlog } from "./actions/blog";
509
533
 
510
- // Re-run this layout when a blog action fires
534
+ // Re-run this layout when a blog action fires (layouts above the route are
535
+ // skipped after actions by default; undefined defers otherwise)
511
536
  layout(BlogLayout, () => [
512
537
  revalidate((ctx) => ctx.isAction(updateBlog) || undefined),
513
538
  path("/blog/:slug", BlogPost, { name: "blogPost" }),
@@ -617,12 +642,14 @@ path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
617
642
  "use client";
618
643
  export default function Error({ error, reset }) { ... }
619
644
 
620
- // Rango: errorBoundary wrapping a group of routes
645
+ // Rango: errorBoundary wrapping a group of routes. The fallback renders on the
646
+ // server and receives only `error` — there is no `reset` (a server render
647
+ // cannot be retried in place; the user navigates away or reloads).
621
648
  layout(<DashboardLayout />, () => [
622
- errorBoundary(({ error, reset }) => (
649
+ errorBoundary(({ error }) => (
623
650
  <div>
624
651
  <h2>Something went wrong</h2>
625
- <button onClick={reset}>Try again</button>
652
+ <p>{error.message}</p>
626
653
  </div>
627
654
  )),
628
655
  path("/dashboard", DashboardIndex, { name: "dashboard" }),
@@ -630,6 +657,30 @@ layout(<DashboardLayout />, () => [
630
657
  ])
631
658
  ```
632
659
 
660
+ For client-side render errors with a "Try again" button (Next's `reset`), wrap
661
+ the client subtree in `ErrorBoundary` from `@rangojs/router/client`; its
662
+ function fallback receives `{ error, reset }`:
663
+
664
+ ```tsx
665
+ "use client";
666
+ import { ErrorBoundary } from "@rangojs/router/client";
667
+
668
+ export function ChartPanel() {
669
+ return (
670
+ <ErrorBoundary
671
+ fallback={({ error, reset }) => (
672
+ <div>
673
+ <p>{error.message}</p>
674
+ <button onClick={reset}>Try again</button>
675
+ </div>
676
+ )}
677
+ >
678
+ <Chart />
679
+ </ErrorBoundary>
680
+ );
681
+ }
682
+ ```
683
+
633
684
  ```typescript
634
685
  // Next.js: app/not-found.tsx
635
686
  export default function NotFound() { ... }
@@ -657,15 +708,17 @@ children in their scope — handlers, loaders, and nested segments.
657
708
 
658
709
  ## 6. Navigation
659
710
 
660
- | Next.js | Rango |
661
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
662
- | `import Link from "next/link"` | `import { Link } from "@rangojs/router/client"` |
663
- | `<Link href="/about">` | `<Link to="/about">` |
664
- | `useRouter().push("/about")` | `useRouter().push("/about")` |
665
- | `useRouter().replace("/about")` | `useRouter().replace("/about")` |
666
- | `usePathname()` | `usePathname()` from `@rangojs/router/client` |
667
- | `useSearchParams()` | `useSearchParams()` from `@rangojs/router/client` — returns an RR-style TUPLE, so destructure the reader: `const [searchParams] = useSearchParams()`; the second element is a setter Next does not have |
668
- | `redirect("/login")` (server) | `redirect("/login")` from `@rangojs/router` |
711
+ | Next.js | Rango |
712
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
713
+ | `import Link from "next/link"` | `import { Link } from "@rangojs/router/client"` |
714
+ | `<Link href="/about">` | `<Link to="/about">` |
715
+ | `useRouter().push("/about")` | `useRouter().push("/about")` |
716
+ | `useRouter().replace("/about")` | `useRouter().replace("/about")` |
717
+ | `useRouter().back()` / `refresh()` / `prefetch(url)` | same methods on `useRouter()` from `@rangojs/router/client` |
718
+ | `useParams()` | `useParams()` from `@rangojs/router/client` (or `ctx.params` in a server handler) |
719
+ | `usePathname()` | `usePathname()` from `@rangojs/router/client` |
720
+ | `useSearchParams()` | `useSearchParams()` from `@rangojs/router/client` — returns an RR-style TUPLE, so destructure the reader: `const [searchParams] = useSearchParams()`; the second element is a setter Next does not have |
721
+ | `redirect("/login")` (server) | `redirect("/login")` from `@rangojs/router` |
669
722
 
670
723
  ### "Instant navigations" (Link prefetching)
671
724
 
@@ -699,22 +752,22 @@ has. Server-component routes and `clientUrls()` groups compose in one tree.
699
752
 
700
753
  ## 7. Server Actions
701
754
 
702
- Server actions work the same way — `"use server"` directive, `useActionState`, form actions. No migration needed for action logic.
755
+ Server actions work the same way — `"use server"` directive, `useActionState`, form actions. No migration needed for action logic. Keep passing the imported action itself to `<form action={...}>` or `useActionState(action, initial)` (not a client-side closure around it) so the form still submits with JavaScript disabled.
703
756
 
704
757
  Key difference: in Rango, route middleware does NOT wrap action execution. Actions only see global middleware context. Use `getRequestContext()` in actions to access `ctx.set()`/`ctx.get()`.
705
758
 
706
- Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge and is NOT read-your-own-writes, so reserve it for route handlers / webhooks (calling it from an action can leave that action's re-render stale). `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, to force specific matched segments (path/layout/parallel/intercept) and their loaders to re-render after an action, attach a `revalidate(({ actionId }) => ...)` rule to that segment or loader registration. See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
759
+ Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge and is NOT read-your-own-writes, so reserve it for route handlers / webhooks (calling it from an action can leave that action's re-render stale). `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, the route, the segments inside its `path()`, and all loaders already re-run after every action; layouts and parallels above the route are skipped. To re-render one of those skipped segments after an action, attach `revalidate((ctx) => ctx.isAction(updateBlog) || undefined)` to it; to narrow a loader or route segment to one action, use `revalidate((ctx) => (ctx.isAction() ? ctx.isAction(updateBlog) : undefined))` (match the imported action by reference, as in §3). See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
707
760
 
708
761
  ## 8. Metadata / Head
709
762
 
710
- Rango uses the `Meta` handle + `<MetaTags />` client component:
763
+ Rango uses the `Meta` handle + `<Html.Meta />` client component:
711
764
 
712
765
  ```typescript
713
766
  // Next.js: export const metadata = { title: "Home" }
714
767
  // Next.js: export function generateMetadata({ params }) { ... }
715
768
 
716
- // Rango: Meta handle in handlers (server), MetaTags in document <head> (client)
717
- import { Meta } from "@rangojs/router";
769
+ // Rango: Meta handle in handlers (server), Html.Meta in document <head> (client)
770
+ import { Meta, type Handler } from "@rangojs/router";
718
771
 
719
772
  const HomePage: Handler<"home"> = (ctx) => {
720
773
  const meta = ctx.use(Meta);
@@ -751,16 +804,16 @@ Without the flag the push still applies, but a slow loader's title lands
751
804
  post-hydration instead of in the document — see `/loader` → "Writing Handles
752
805
  from Loaders" for the delivery race.
753
806
 
754
- Add `<MetaTags />` in the Document component's `<head>`:
807
+ Add `<Html.Meta />` in the Document component's `<head>`:
755
808
 
756
809
  ```typescript
757
- import { MetaTags } from "@rangojs/router/client";
810
+ import { Html } from "@rangojs/router/client";
758
811
 
759
812
  function Document({ children }: { children: ReactNode }) {
760
813
  return (
761
814
  <html>
762
815
  <head>
763
- <MetaTags />
816
+ <Html.Meta />
764
817
  </head>
765
818
  <body>{children}</body>
766
819
  </html>
@@ -835,13 +888,15 @@ See `/theme` for full API including system detection and cookie persistence.
835
888
  4. [ ] Create `router.tsx` with `createRouter()`
836
889
  5. [ ] Convert file-based routes to `urls()` DSL in `urls.tsx`
837
890
  6. [ ] Migrate layouts to `layout()` with `<Outlet />`
838
- 7. [ ] Convert data fetching to `createLoader()` + `ctx.use()`
891
+ 7. [ ] Keep inline server `fetch`/DB calls in handlers; add `createLoader()` +
892
+ `loader()` only where you need live, refreshable, or independently
893
+ cached data (§3)
839
894
  8. [ ] Migrate `middleware.ts` to `router.use()` (auth, guards, logging)
840
895
  9. [ ] Replace `next/link` with `Link` from `@rangojs/router/client`; keep
841
896
  "instant navigations" via `prefetch="viewport"`/`defaultPrefetch` (§6)
842
897
  10. [ ] Convert loading/error files to `loading()` / `errorBoundary()`
843
898
  11. [ ] Migrate API routes to `path.json()` / `path.text()`
844
- 12. [ ] Update metadata to use `Meta` handle + `<MetaTags />` in document head
899
+ 12. [ ] Update metadata to use `Meta` handle + `<Html.Meta />` in document head
845
900
  (`generateMetadata` → loader push + `{ ssr: false }`)
846
901
  13. [ ] Replace `next-themes` with `theme: true` in createRouter (see `/theme`)
847
902
  14. [ ] Map rendering-mode segment config: `revalidate = N` → `cache({ ttl })`,
@@ -55,7 +55,9 @@ actor and tenant scope at the repository boundary, verify membership/role, and
55
55
  perform the data query through that scope. Avoid exporting an unrestricted DB
56
56
  handle to page handlers.
57
57
 
58
- Tests must prove denial, not only success:
58
+ Tests must prove denial, not only success (the `/testing` skill's `runMiddleware`,
59
+ `runLoader`, and `runInRequestContext` take your DB double through `env`; see its
60
+ `bindings.md`):
59
61
 
60
62
  - a user cannot read another tenant by guessing an ID;
61
63
  - a member cannot perform an owner/admin mutation;
@@ -102,7 +104,8 @@ For a Node-to-Workers move, search for:
102
104
 
103
105
  Prefer web standards (`fetch`, `Request`, `Response`, WebCrypto) and provider
104
106
  bindings. Test the production bundle in `vite preview`; a successful TypeScript
105
- check does not prove an SDK runs in workerd.
107
+ check does not prove an SDK runs in workerd. `/cloudflare` covers bindings,
108
+ local D1, streaming, and deploying the built Worker.
106
109
 
107
110
  ## Plan cutover and rollback
108
111