@rangojs/router 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -1,26 +1,35 @@
1
1
  ---
2
2
  name: prerender
3
- description: Pre-render route segments at build time with Prerender and Passthrough live fallback. Use when a page's content is mostly static and shouldn't render on every request, speeding up cold responses, or deciding which routes to prerender vs render live.
3
+ description: Pre-render route segments at build time with Prerender (with getParams), Passthrough live fallback, Static segments, and Skip. Use when a page's content is mostly static and shouldn't render on every request, speeding up cold responses, reading build-only data (files, build env), or deciding which routes to prerender vs render live.
4
4
  argument-hint: [passthrough]
5
5
  ---
6
6
 
7
7
  # Pre-rendering with Prerender
8
8
 
9
- Pre-rendering is **caching at build time**. Same serialization format, same
10
- deserialization path, same segment system. The worker handles every request --
11
- there are NO static .html or .rsc files served from assets. The worker reads
12
- pre-computed Flight payloads instead of executing handler code.
9
+ Pre-rendering is **caching at build time**. `Prerender()` renders a route's
10
+ segments once per known param set during `vite build`, and `Static()` renders a
11
+ single segment once. Same serialization format, same deserialization path, same
12
+ segment system as the runtime `cache()`. The worker handles every request --
13
+ there are NO static .html or .rsc files served from assets. At runtime the
14
+ worker reads the pre-computed Flight payloads instead of executing handler code,
15
+ exactly like a cache hit; middleware and loaders still run on every request.
16
+
17
+ Use it when the page content is known at build time (markdown files, a CMS
18
+ snapshot, a fixed list of products) or needs build-only APIs such as `node:fs`.
19
+ The handler code and its imports are removed from the production bundle.
13
20
 
14
21
  ## Not this skill if…
15
22
 
16
- - You want a cached HTML shell captured at runtime, with holes and loaders
17
- staying live per request — see `/ppr`.
23
+ - You want a cached HTML shell captured at runtime, with holes staying live
24
+ per request (loaders without `ssr: false` run per request; `ssr: false`
25
+ loaders bake) — see `/ppr`.
18
26
  - You want runtime segment caching with TTL/SWR — that is the `cache()` DSL:
19
27
  see `/caching`. Prerender is the same cache filled at build time.
28
+ - You are unsure which cache layer you need — start at `/cache-guide`.
20
29
 
21
30
  ## API: Prerender
22
31
 
23
- ### Static Route (no params)
32
+ ### Route without params
24
33
 
25
34
  ```typescript
26
35
  import { Prerender } from "@rangojs/router";
@@ -96,7 +105,7 @@ functions — no `ctx.build` branching needed.
96
105
  | Unknown params | Handler evicted, no live fallback | Live handler runs at request time |
97
106
  | `ctx.passthrough()` | Throws (not on Passthrough route) | Skips artifact, defers to live handler |
98
107
  | Bundle size | Build handler code + imports removed | Build handler evicted, live handler kept |
99
- | `revalidate()` | Not allowed (handler gone) | Allowed (live handler can re-render) |
108
+ | `revalidate()` | No effect on prerendered segments | Allowed (live handler can re-render) |
100
109
  | `loading()` | Ignored (segments fully resolved) | Works for live fallback renders |
101
110
 
102
111
  ### When to use Passthrough
@@ -138,12 +147,19 @@ interface BuildContext<TParams> {
138
147
  search?: Record<string, unknown>,
139
148
  ): string; // URL generation
140
149
  passthrough(): PrerenderPassthroughResult; // Skip local artifact (Passthrough routes only)
141
- dynamic(): void; // No-op in Prerender/Static handlers; use middleware for PPR shell opt-out
142
150
  env: DefaultEnv; // Available when buildEnv is configured in rango() (throws otherwise)
143
- // NOT available: request, headers, cookies (always throw)
151
+ // ctx.request, ctx.res, ctx.headers throw: there is no request at build time.
152
+ // ctx.waitUntil() is a no-op. ctx.dynamic() exists at runtime but is a no-op
153
+ // here -- opt a URL out of a build shell from middleware instead.
144
154
  }
145
155
  ```
146
156
 
157
+ `getParams` receives a smaller `GetParamsContext` (`build`, `dev`, `env`, `set`,
158
+ `reverse`). Values it stores with `ctx.set()` are copied into every param set's
159
+ handler context and read there with `ctx.get()`. `env` is the `buildEnv` from
160
+ `rango()` — build-time bindings shared by the whole build, not a live request
161
+ env.
162
+
147
163
  Use `createVar<T>()` to share typed data from a Prerender handler to child layouts:
148
164
 
149
165
  ```typescript
@@ -199,6 +215,13 @@ export const BlogPost = cph(getParams, handler);
199
215
  All patterns support whole-file stubbing, expression stubbing, and build-time
200
216
  module tracking. The same applies to `Static`.
201
217
 
218
+ Anything else gets no build-injected id, and the call throws
219
+ `Prerender: missing $$id` when the module loads. Unsupported shapes include
220
+ `export let`/`export var`, and calling `Prerender(...)` inline — for example
221
+ directly inside `path()`. Declare it as an exported `const` and pass the
222
+ binding. (`Passthrough(def, liveHandler)` needs no id and can be written inline
223
+ in `path()`.)
224
+
202
225
  ## Handler Eviction
203
226
 
204
227
  In production builds, `Prerender` exports are replaced with stubs:
@@ -221,46 +244,81 @@ In client and SSR environments, ALL prerender handlers are always stubbed.
221
244
 
222
245
  ## Sub-use Semantics
223
246
 
224
- Everything inside the path's use() callback is part of the B segment and gets
225
- pre-rendered:
247
+ Everything inside the path's use() callback is part of the pre-rendered route
248
+ subtree and gets pre-rendered. Loaders are the exception — they stay live:
226
249
 
227
250
  ```typescript
228
251
  path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
229
- layout(<PostLayout />, () => [ // inside B -> pre-rendered
252
+ layout(<PostLayout />, () => [ // inside the route -> pre-rendered
230
253
  loader(PostMetaLoader), // live at runtime, bundled normally
231
254
  ]),
232
- parallel({ "@sidebar": BlogSidebar }), // inside B -> pre-rendered
255
+ parallel({ "@sidebar": BlogSidebar }), // inside the route -> pre-rendered
233
256
  ])
234
257
  ```
235
258
 
236
- If a parallel or child layout uses node APIs, wrap it in `Prerender`
237
- (static, no getParams) so the Vite plugin can stub it:
259
+ Only the route's own `Prerender` handler is evicted from the bundle. If a
260
+ parallel or child layout uses build-only APIs such as `node:fs`, wrap it in
261
+ `Static()` (see "Static segments" below) so the Vite plugin can stub it too.
262
+ `layout()` and `parallel()` accept `Static()` definitions; they do not accept
263
+ `Prerender()` definitions.
238
264
 
239
265
  ```typescript
240
- // sidebar.tsx -- uses node:fs, must be a Prerender
241
- export const BlogSidebar = Prerender(async (ctx) => {
266
+ // sidebar.tsx -- uses node:fs, so it must be stubbed out of the bundle
267
+ import { Static } from "@rangojs/router";
268
+
269
+ export const BlogSidebar = Static(async () => {
242
270
  const files = await fs.readdir("content/blog/");
243
- return <Sidebar posts={files.map(f => basename(f, ".md"))} />;
271
+ return <Sidebar posts={files.map((f) => basename(f, ".md"))} />;
244
272
  });
245
273
 
246
274
  // urls.tsx
247
275
  path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
248
- parallel({ "@sidebar": BlogSidebar }), // stubbable, node:fs excluded
276
+ parallel({ "@sidebar": BlogSidebar }), // stubbable, node:fs excluded
249
277
  ])
250
278
  ```
251
279
 
280
+ ## Static segments
281
+
282
+ `Static(handler, options?)` renders ONE segment once at build time. It has no
283
+ params and produces no URLs; it works on `layout()`, `parallel()`, and `path()`.
284
+ Use it for build-time chrome such as a docs navigation built from files, or for
285
+ a static route with no params.
286
+
287
+ ```typescript
288
+ import { Static } from "@rangojs/router";
289
+
290
+ export const DocsNav = Static(async (ctx) => {
291
+ const docs = await readDocsIndex(); // build-only I/O
292
+ return <Nav docs={docs} />;
293
+ });
294
+
295
+ // urls.tsx
296
+ layout(DocsNav, () => [
297
+ path("/docs/:slug", DocPage, { name: "doc" }),
298
+ ]);
299
+ ```
300
+
301
+ - The handler receives `StaticBuildContext`: `build`, `dev`, `env` (with
302
+ `buildEnv`), `get`/`set`, `use(handle)`, and `reverse`. `params`, `url`,
303
+ `pathname`, and `request` throw.
304
+ - `Static(handler, { passthrough: true })` skips the build render and keeps the
305
+ handler in the bundle, rendering live at request time.
306
+ - `Static()` always renders sequentially (no `concurrency` option) and supports
307
+ `throw new Skip()` (see "Skipping Entries with Skip").
308
+ - The same export rules as `Prerender` apply (`export const X = Static(...)`).
309
+
252
310
  ## Interaction with DSL Items
253
311
 
254
- | DSL item | Behavior with Prerender |
255
- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
256
- | `loader()` | Live at runtime, bundled normally. Use `cache()` for caching. |
257
- | `revalidate()` | Not allowed without Passthrough. Allowed with Passthrough. |
258
- | `cache()` | Orthogonal -- use on parent layouts and loaders. |
259
- | `layout()` | Child layouts inside path are pre-rendered. Parent layouts are live. |
260
- | `parallel()` | Parallel slots inside path are pre-rendered. |
261
- | `middleware()` | Skipped while collecting build-time Flight payloads (no request). For `Prerender` + `ppr`, producer B replays global and route middleware during build-shell capture with `ctx.build === true`; `ctx.dynamic()` skips that shell. Runs at request time for loaders. |
262
- | `loading()` | Ignored without Passthrough. Works for live fallback with Passthrough. |
263
- | `intercept()` | Pre-rendered at build time. Intercept variant stored under `/i` key alongside main segments. At runtime, the correct variant is served based on `ctx.isIntercept`. `when` config conditions are skipped at build time (all intercepts are pre-rendered unconditionally). |
312
+ | DSL item | Behavior with Prerender |
313
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
314
+ | `loader()` | Live at runtime, bundled normally. Use `cache()` for caching. |
315
+ | `revalidate()` | Without Passthrough: no effect on the prerendered segments (re-served from the build artifact); loader revalidation still works. With Passthrough: re-renders through the live handler. |
316
+ | `cache()` | Orthogonal -- prerendered segments bypass the runtime cache. Use `cache()` on parent layouts and loaders. |
317
+ | `layout()` | Child layouts inside path are pre-rendered. Parent layouts are live. |
318
+ | `parallel()` | Parallel slots inside path are pre-rendered. |
319
+ | `middleware()` | Skipped while collecting build-time Flight payloads (no request). For `Prerender` + `ppr`, the build-shell step replays global and route middleware with `ctx.build === true`; `ctx.dynamic()` skips that shell. At request time middleware runs normally on every request. |
320
+ | `loading()` | Ignored without Passthrough. Works for live fallback with Passthrough. |
321
+ | `intercept()` | Pre-rendered at build time. Intercept variant stored under `/i` key alongside main segments. At runtime, the correct variant is served based on `ctx.isIntercept`. `when` config conditions are skipped at build time (all intercepts are pre-rendered unconditionally). |
264
322
 
265
323
  When Passthrough revalidation is enabled, remember that revalidation is
266
324
  still partial: opting a child segment into revalidation does not
@@ -269,9 +327,16 @@ implicitly re-run outer prerender-derived handlers/layouts.
269
327
  ## Prerender + PPR Build Shells
270
328
 
271
329
  A `Prerender` page may also declare `ppr` on the path option. The build still
272
- stores the Flight payload first. After that, producer B tries to bake the HTML
273
- shell for each generated URL so the first document request can be an
274
- `x-rango-shell: HIT`.
330
+ stores the Flight payload first. After that, the build-time shell capture
331
+ tries to bake the HTML shell for each generated URL so the first document
332
+ request can be an `x-rango-shell: HIT`. A route partitioned by
333
+ `cache({ key })` or a store `keyGenerator` never serves that build shell (the
334
+ build captured one partition); each partition captures at runtime, a
335
+ once-per-route warning says so, and a `keyGenerator` that returns the default
336
+ key unchanged keeps it. When the route's `cache()` scope (its own, or one
337
+ inherited from a layout) refuses a request — `cache(false)`, or a
338
+ `condition()` that returns false — that request gets no shell at all, the
339
+ build shell included: it renders like a cache miss.
275
340
 
276
341
  That shell capture is request-shaped enough to run middleware safely:
277
342
 
@@ -284,6 +349,11 @@ Use `ctx.build` inside middleware to avoid runtime-only side effects during
284
349
  build shell capture, or call `ctx.dynamic()` to leave that route to runtime
285
350
  PPR. Runtime requests still run the normal middleware chain.
286
351
 
352
+ Build shells are keyed by pathname and served only for requests without a query
353
+ string (after `cache.searchParams` filtering). A URL with search params has its
354
+ own shell identity and is captured at runtime. The runtime shell store is read
355
+ first, so a runtime capture supersedes the baked entry once it exists.
356
+
287
357
  ### Freshness of a build shell
288
358
 
289
359
  A build-baked shell is not on a wall clock the way a pure runtime `ppr` shell is.
@@ -299,30 +369,32 @@ entry STILL serves and a runtime recapture is scheduled that upgrades it in plac
299
369
  (SWR is the upgrade path from build entry → fresher runtime entry). Because the
300
370
  `Prerender` handler is evicted from the production bundle, that recapture never
301
371
  re-runs the handler — it replays the same build-time segments and only refreshes
302
- `cache()`-scoped data baked into the shell. **If nothing in the shell is
303
- `cache()`-backed, `ttl` has nothing to refresh** — reach for `updateTag` (or a
372
+ what the capture reads again: `cache()`-scoped data baked into the shell, and
373
+ the data of `ssr: false` loaders, which run at every capture. **If the shell
374
+ bakes neither, `ttl` has nothing to refresh** — reach for `updateTag` (or a
304
375
  redeploy) instead of a shorter `ttl`.
305
376
 
306
- `ctx.dynamic()` opts a request off the shell axis ONLY. A `Prerender` route has
377
+ `ctx.dynamic()` opts a request off the shell ONLY. A `Prerender` route has
307
378
  no live handler to fall back to (it was evicted), so a `dynamic()` request still
308
- serves the build-baked B-segments — fresh loaders in their holes, not a fresh
309
- handler render. There is no "fully dynamic" render for a prerendered route.
379
+ serves the build-baked segments — fresh loaders, not a fresh handler render.
380
+ There is no "fully dynamic" render for a prerendered route.
310
381
 
311
382
  ## Dev Mode
312
383
 
313
- In dev mode there is no production-style prerender build pass and no handler
314
- stubbing.
315
-
316
- **Node.js dev server** — `Prerender` acts as a normal handler. Routes render
317
- live on every request with full runtime context (`ctx.build === false`).
318
-
319
- **Non-Node runtimes (Cloudflare workerd, Deno workers)** — Handlers that
320
- depend on Node APIs (e.g. `node:fs`) cannot run in-process. The Vite plugin
321
- can intercept these requests and resolve them via the `/__rsc_prerender`
322
- endpoint, which runs `matchForPrerender` in a Node.js temp server. In this
323
- path the handler receives `BuildContext` (`ctx.build === true`) and segments
324
- are resolved identically to production prerendering, then served on-demand.
325
- This only applies when `__PRERENDER_DEV_URL` is set by the plugin.
384
+ In dev there is no build pass and no handler stubbing. Instead, a request to a
385
+ prerendered route is rendered **on demand** through the dev server's
386
+ `/__rsc_prerender` endpoint, which runs the same build-time resolution in
387
+ Node.js (so `node:fs` and `buildEnv` work even when the app itself runs in
388
+ workerd). This applies to both the Node and the Cloudflare presets.
389
+
390
+ - The handler receives `BuildContext` (`ctx.build === true`, `ctx.dev === true`),
391
+ and segments resolve exactly as in the production prerender.
392
+ - Results are memoized until you edit a file in the router's module graph, so
393
+ `getParams()` and handler side effects run once per edit, not once per request.
394
+ The endpoint reports `x-rango-prerender-cache: HIT | MISS`.
395
+ - If the on-demand render has no entry for the URL (unknown param, `Skip`,
396
+ `ctx.passthrough()`, or a render error, which is logged), the request falls
397
+ through to a live render — the Passthrough live handler when there is one.
326
398
 
327
399
  ## Storage Layout
328
400
 
@@ -378,13 +450,28 @@ export const BlogPost = Prerender(
378
450
  },
379
451
  );
380
452
 
381
- // Wrap with Passthrough to serve skipped params live at runtime
382
- export const BlogPost = Passthrough(BlogPostDef, async (ctx) => {
383
- if (ctx.params.slug === "draft") {
384
- throw new Skip("Draft articles are not pre-rendered");
385
- }
386
- return <PostPage slug={ctx.params.slug} />;
387
- });
453
+ ```
454
+
455
+ To serve skipped params live at runtime, give the definition its own name and
456
+ wrap it with `Passthrough()`; the live handler renders them:
457
+
458
+ ```typescript
459
+ import { Prerender, Passthrough, Skip } from "@rangojs/router";
460
+
461
+ export const BlogPostDef = Prerender(
462
+ async () => [{ slug: "published" }, { slug: "draft" }],
463
+ async (ctx) => {
464
+ if (ctx.params.slug === "draft") {
465
+ throw new Skip("Draft articles are not pre-rendered");
466
+ }
467
+ return <PostPage slug={ctx.params.slug} />;
468
+ },
469
+ );
470
+
471
+ // Live handler: runs at request time for "draft" and any unknown slug
472
+ export const BlogPost = Passthrough(BlogPostDef, async (ctx) => (
473
+ <PostPage slug={ctx.params.slug} />
474
+ ));
388
475
  ```
389
476
 
390
477
  Skipped entries are excluded from the build output. With `Passthrough()`,
@@ -416,6 +503,12 @@ rango({ prerender: { onError: "warn" } }); // default is "fail"
416
503
  | `return ctx.passthrough()` | Skip entry, log PASS (Passthrough routes) | Skip entry, log PASS |
417
504
  | `throw new Skip("reason")` | Skip entry, log SKIP, continue | Skip entry, log SKIP, continue |
418
505
  | `throw new Error("reason")` | Log FAIL, stop ALL pre-rendering, fail build | Log WARN, skip the URL, continue |
506
+ | A child component throws | Log FAIL, stop ALL pre-rendering, fail build | Log WARN, skip the URL, continue |
507
+
508
+ The last row covers a handler that returns fine but whose tree holds an async
509
+ server component that throws while the page is rendered at build, for example
510
+ because its fetch fails. That error goes through the same policy instead of being
511
+ baked as an error row, and a `Skip` thrown there skips the URL.
419
512
 
420
513
  With `"warn"` the errored entry is logged and left un-baked (never served as a baked
421
514
  200 error page). `"warn"` is a build-unblock, not a runtime contract: the route falls
@@ -427,7 +520,8 @@ fallback) or `throw new Skip()` (an intentional skip — works in the render fn,
427
520
  only `getParams()`); otherwise prefer the default `"fail"`.
428
521
 
429
522
  Both `Skip` and hard errors propagate to the router's `onError` callback with phase
430
- `"prerender"` or `"static"`.
523
+ `"prerender"` or `"static"`; skipped entries (a `Skip`, or an error under
524
+ `"warn"`) carry `metadata.skipped: true`.
431
525
 
432
526
  ### Build logs
433
527
 
@@ -454,13 +548,9 @@ the build continues.
454
548
 
455
549
  ### Dev mode behavior
456
550
 
457
- **Node.js dev server** — `Skip` behaves like a regular runtime error because
458
- the handler runs live with `ctx.build === false`.
459
-
460
- **Non-Node runtimes using `/__rsc_prerender`** — `Skip` participates in the
461
- on-demand prerender path, so build-style skip logic does run for that request.
462
- The dev prerender endpoint treats it like a prerender miss and the request
463
- falls back according to normal dev/runtime behavior.
551
+ In dev, `Skip` runs inside the on-demand `/__rsc_prerender` render, so
552
+ build-style skip logic runs for that request. The endpoint treats it as a
553
+ prerender miss and the request falls through to a live render (see "Dev Mode").
464
554
 
465
555
  ## Per-Param Passthrough with ctx.passthrough()
466
556
 
@@ -553,6 +643,11 @@ Passthrough entries are logged distinctly:
553
643
  Loaders on pre-rendered routes run at request time. They are bundled normally
554
644
  and need `cache()` for caching. Do not use build-only APIs in loaders.
555
645
 
646
+ The exception is an `ssr: false` loader on a `Prerender` + `ppr` route: the
647
+ build-time shell capture runs it and bakes its settled data into the shell,
648
+ and a shell HIT runs it only when its return carries promises (`/ppr` → "The
649
+ bake lane"). The Flight payload collection itself still skips loaders.
650
+
556
651
  ### Build-time handle data is frozen
557
652
 
558
653
  Handle values pushed via `ctx.use()` DURING pre-rendering (handler pushes at
@@ -564,23 +659,48 @@ say) is request-time data — delivery follows the loader race model, see
564
659
 
565
660
  ### Server actions work normally
566
661
 
567
- Actions do not re-render the B segment. The pre-rendered handler output stays
568
- frozen. Loaders are live and can be revalidated by actions. With `Passthrough()`
569
- and `revalidate()`, the live handler can re-render.
662
+ Actions run normally; what changes is the re-render after them. Loaders are live
663
+ and are revalidated by actions as usual.
664
+
665
+ - **Plain `Prerender`**: the handler was evicted, so the action re-render serves
666
+ the stored prerender entry again. The prerendered output stays frozen; the
667
+ action's return value reaches the client as usual.
668
+ - **`Passthrough()`**: the action re-render runs the live handler, replacing the
669
+ prerendered output. To keep the frozen tree mounted after actions (for example
670
+ so a client component's form state survives), suppress action
671
+ revalidation on the route while keeping navigation defaults:
672
+ `revalidate((ctx) => (ctx.isAction() ? false : undefined))`.
673
+
674
+ A handler may also embed an inline `"use server"` action that closes over
675
+ build-time values; the captured values are frozen at build and the action body
676
+ runs live when invoked.
570
677
 
571
678
  ### Empty getParams
572
679
 
573
680
  If `getParams` returns an empty array, no Flight payloads are written. No error.
681
+ Every request to the route then behaves like an unknown param.
574
682
 
575
683
  ### Route name is required
576
684
 
577
685
  Routes using `Prerender` must have a `name` in path options.
578
686
  The name is used as the storage key for Flight payloads.
579
687
 
580
- ### No revalidate without Passthrough
688
+ ### revalidate() needs Passthrough
689
+
690
+ Without `Passthrough()` the handler is evicted, so there is nothing to
691
+ re-render: a revalidation re-serves the prerendered segments from the build
692
+ artifact. `revalidate()` on the route's loaders still works. No warning is
693
+ emitted — wrap the route in `Passthrough()` when its segments must re-render.
694
+
695
+ ### Tag invalidation does not reach prerendered segments
581
696
 
582
- Using `revalidate()` without `Passthrough()` produces a build-time warning.
583
- The handler is evicted -- there is nothing to re-render.
697
+ `updateTag()`/`revalidateTag()` evict runtime cache entries. They do not refresh
698
+ a prerendered route's build-time Flight payload, which is served before the tag
699
+ system is consulted, and a `cacheTag()` inside a `"use cache"` function that runs
700
+ during the build has no runtime effect. A redeploy replaces the payload. To make
701
+ a route tag-invalidatable, serve it from the runtime cache instead (`cache()`,
702
+ or a `Passthrough()` live handler). A build-baked **ppr shell** is different: a
703
+ tag it carries does drop it (see "Freshness of a build shell").
584
704
 
585
705
  ### loading() is ignored without Passthrough
586
706
 
@@ -589,11 +709,13 @@ With `Passthrough()`, `loading()` works for live fallback renders.
589
709
 
590
710
  ## Complete Example
591
711
 
712
+ Known guides are pre-rendered; any other slug is rendered live by the
713
+ Passthrough handler.
714
+
592
715
  ```typescript
593
716
  // pages/guides-handler.tsx
594
717
  import { Prerender, Passthrough } from "@rangojs/router";
595
718
  import { Link } from "@rangojs/router/client";
596
- import { href } from "../router.js";
597
719
 
598
720
  const knownGuides: Record<string, string> = {
599
721
  routing: "Routing Guide",
@@ -609,9 +731,9 @@ export const GuidesDetailDef = Prerender<{ slug: string }>(
609
731
  <h1>{title}</h1>
610
732
  <p>Slug: {ctx.params.slug}</p>
611
733
  <nav>
612
- <Link to={href("guides.detail", { slug: "routing" })}>Routing</Link>
734
+ <Link to={ctx.reverse("guides.detail", { slug: "routing" })}>Routing</Link>
613
735
  {" | "}
614
- <Link to={href("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
736
+ <Link to={ctx.reverse("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
615
737
  </nav>
616
738
  </div>
617
739
  );
@@ -625,9 +747,9 @@ export const GuidesDetail = Passthrough(GuidesDetailDef, async (ctx) => {
625
747
  <h1>{title}</h1>
626
748
  <p>Slug: {ctx.params.slug}</p>
627
749
  <nav>
628
- <Link to={href("guides.detail", { slug: "routing" })}>Routing</Link>
750
+ <Link to={ctx.reverse("guides.detail", { slug: "routing" })}>Routing</Link>
629
751
  {" | "}
630
- <Link to={href("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
752
+ <Link to={ctx.reverse("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
631
753
  </nav>
632
754
  </div>
633
755
  );
@@ -711,41 +833,21 @@ Loaders run fresh at request time for both variants.
711
833
 
712
834
  Pre-rendered routes set flags on the route trie leaf at build time:
713
835
 
714
- - `pr: true` -- route has pre-rendered B segment data
836
+ - `pr: true` -- route has pre-rendered segment data
715
837
  - `pt: true` -- route wrapped with `Passthrough()` (live handler available)
716
838
 
717
839
  At runtime, the cache-lookup middleware uses these flags:
718
840
 
719
841
  - `pr + hit` -- serve pre-rendered Flight payload
720
842
  - `pr + pt + miss` -- fall through to Passthrough live handler
721
- - `pr + miss` (no pt) -- fall through (handler stubbed, no live render)
722
-
723
- ## Contributor Checklist
724
-
725
- Before changing prerender behavior, run these tests.
726
-
727
- ### Tests to run
728
-
729
- ```bash
730
- # Core prerender e2e (Passthrough, eviction, loaders, sub-use, intercept)
731
- pnpm --filter @rangojs/router exec playwright test prerender
732
-
733
- # Prerender-specific unit test
734
- pnpm --filter @rangojs/router run test:unit -- prerender-passthrough
735
-
736
- # Semantic matrix (prerender rows cover intercept + ctx propagation)
737
- pnpm --filter @rangojs/router exec playwright test semantic-matrix
738
-
739
- # Handler-first (ctx.set/get visibility with prerender handlers)
740
- pnpm --filter @rangojs/router exec playwright test handler-first
741
- ```
843
+ - `pr + miss` (no pt) -- fall through to the stubbed handler, which throws a
844
+ not-found error ("No prerender data found for this route"); there is no live render
742
845
 
743
- ### Dev-only vs build-parity
846
+ ## Related
744
847
 
745
- - Prerender e2e tests run against a real production build by default (the
746
- fixture builds the test app). Dev-mode prerender behavior is tested via
747
- `/__rsc_prerender` endpoint tests and node.js dev-server fallback.
748
- - Log-based assertions (build output lines, debug cache logs) are inherently
749
- dev/build-only and do not need a production counterpart.
750
- - Behavioral assertions (rendered content, loader freshness, Passthrough
751
- fallback, intercept variant selection) must work in the production build.
848
+ - `/cache-guide` — how prerendering compares with the runtime cache layers
849
+ - `/caching` — runtime `cache()` segments and loader caching
850
+ - `/ppr` — the `ppr` path option; combine with `Prerender` for build-time shells
851
+ - `/shell-manifest` — prerendered shell feeding ids to a live loader
852
+ - `/deployment-caching` — why prerender output is not a CDN static file
853
+ - `/loader` — loaders stay live on prerendered routes