@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: layout
3
- description: Define layout routes that wrap child routes in @rangojs/router
3
+ description: Define layout routes that wrap child routes in @rangojs/router. Use when sharing a persistent UI shell (nav, sidebar) across nested routes, or asking how to wrap child pages with a common layout.
4
4
  argument-hint: [component]
5
5
  ---
6
6
 
@@ -147,12 +147,21 @@ A layout as a child of `path()` wraps the route content and can read
147
147
  data set by the route handler via `ctx.get()`. The handler always
148
148
  executes before its children.
149
149
 
150
- This handler-first guarantee applies to a single full render pass
151
- (initial render, prerender, or full HTML re-render). During partial
152
- action revalidation, only the segments that revalidate are recomputed.
153
- If an orphan layout depends on data established by an outer handler or
154
- layout, that outer segment must also revalidate, or the orphan must
155
- guard/reload the data independently.
150
+ This is the recommended way to pass handler data downward, and it is
151
+ safe under partial action revalidation with zero configuration: orphan
152
+ layouts (and their parallels) belong to the route entry, and on an
153
+ action the whole entry re-runs together by default route segment,
154
+ loaders, and `belongsToRoute` children all seed revalidate-true, with
155
+ handler-first ordering preserved. Producer and consumer cannot desync
156
+ unless you narrow one side with a predicate that returns a hard `false`
157
+ (then put the same contract on both — see "Revalidation Contracts").
158
+
159
+ Data from an **outer** handler or layout entry is the opposite case:
160
+ outer entries do not revalidate on actions by default (parent-chain
161
+ skip). If an orphan layout depends on data established above its own
162
+ route entry, that outer segment must share a revalidation contract, or
163
+ the orphan must guard/reload the data independently. See `/rango` →
164
+ "Passing data down the tree" for the full safest-first ladder.
156
165
 
157
166
  ```typescript
158
167
  import { Outlet, ParallelOutlet } from "@rangojs/router/client";
@@ -191,7 +200,10 @@ orphan layouts to read them.
191
200
 
192
201
  ## Layout Revalidation
193
202
 
194
- Layouts don't revalidate by default. Control with `revalidate()`:
203
+ Standalone `layout()` entries don't revalidate by default on an action,
204
+ parent-chain segments are skipped unless a `revalidate()` opts them in.
205
+ (Orphan layouts inside a `path()` are the opposite: they ride along with
206
+ the route entry by default.) Control with `revalidate()`:
195
207
 
196
208
  ```typescript
197
209
  layout(<ShopLayout />, () => [
@@ -202,8 +214,10 @@ layout(<ShopLayout />, () => [
202
214
  ])
203
215
 
204
216
  // Or revalidate based on conditions
217
+ import * as CartActions from "./actions/cart";
218
+
205
219
  layout(<CartLayout />, () => [
206
- revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
220
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
207
221
 
208
222
  path("/cart", CartPage, { name: "cart" }),
209
223
  ])
@@ -216,13 +230,19 @@ their `ctx.set()` state.
216
230
 
217
231
  ### Revalidation Contracts
218
232
 
219
- For shared upstream data, define named revalidation functions and reuse
220
- them on both producer and consumer segments:
233
+ Contracts are the tool for cross-entry sharing the bottom rung of the
234
+ data-passing ladder (`/rango` "Passing data down the tree"). Before
235
+ writing one, check whether the producer can move down a rung: into the
236
+ consumer's own entry as an orphan layout, into middleware, or into a
237
+ loader. When the data genuinely must flow from an outer entry, define
238
+ named revalidation functions and reuse them on both producer and
239
+ consumer segments:
221
240
 
222
241
  ```typescript
223
242
  // revalidation-contracts.ts
224
- export const revalidateCartData = ({ actionId }) =>
225
- actionId?.includes("src/actions/cart.ts#addToCart") || undefined;
243
+ import { addToCart } from "./actions/cart";
244
+
245
+ export const revalidateCartData = (ctx) => ctx.isAction(addToCart) || undefined;
226
246
  ```
227
247
 
228
248
  ```typescript
@@ -242,9 +262,10 @@ You can also package them as importable handoff helpers:
242
262
  ```typescript
243
263
  // revalidation-contracts.ts
244
264
  import { revalidate } from "@rangojs/router";
265
+ import * as AuthActions from "./actions/auth";
245
266
 
246
- export const revalidateAuthData = ({ actionId }) =>
247
- actionId?.includes("src/actions/auth.ts#") || undefined;
267
+ export const revalidateAuthData = (ctx) =>
268
+ ctx.isAction(AuthActions) || undefined;
248
269
  export const revalidateAuth = () => [revalidate(revalidateAuthData)];
249
270
  ```
250
271
 
@@ -262,6 +283,7 @@ layout(<ShellLayout />, () => [
262
283
  ```typescript
263
284
  import { urls } from "@rangojs/router";
264
285
  import { Outlet, ParallelOutlet } from "@rangojs/router/client";
286
+ import * as CartActions from "./actions/cart";
265
287
 
266
288
  function ShopLayout() {
267
289
  return (
@@ -291,7 +313,7 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
291
313
  }, () => [
292
314
  // Layout loaders
293
315
  loader(CartLoader, () => [
294
- revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
316
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
295
317
  ]),
296
318
 
297
319
  // Parallel routes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: links
3
- description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse
3
+ description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse. Use when generating a link to a route by name instead of hardcoding a path, or a link breaks after routes move or get mounted elsewhere.
4
4
  argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loader
3
- description: Define data loaders for fetching data in routes with createLoader
3
+ description: Define data loaders for fetching data in routes with createLoader. Use when pages need per-request data that stays fresh, data should stream while the page renders, or client components need reactive server data.
4
4
  argument-hint: [loader]
5
5
  ---
6
6
 
@@ -11,6 +11,13 @@ Loaders fetch data on the server and stream it to the client. For mutations
11
11
  `/server-actions`. Loaders re-resolve after an action runs, so the typical
12
12
  flow is _action mutates → loader re-reads → UI updates_.
13
13
 
14
+ ## Not this skill if…
15
+
16
+ - You want to mutate state — mutations are `"use server"` actions: see
17
+ `/server-actions`. Loaders read per-request live data.
18
+ - You want to cache a function's return value — loaders are fresh every request
19
+ by default; caching one function is `"use cache"`: see `/use-cache`.
20
+
14
21
  ## Creating a Loader
15
22
 
16
23
  ```typescript
@@ -140,6 +147,11 @@ same memoized result — loaders never run twice per request.
140
147
  - The handler output depends on the loader data. If the route is inside
141
148
  `cache()`, the handler is cached with the loader result baked in —
142
149
  defeating the live data guarantee.
150
+ - The same holds under a PPR shell capture (`/ppr`): handler consumption is
151
+ the BAKED lane — the loader executes at capture (identity reads permitted)
152
+ and the rendered value is a capture-time copy; `useLoader` client-side is
153
+ the live lane. One rule across `cache()`, `"use cache"`, and PPR: the
154
+ consumption-lane rule (`/rango` → Invariants).
143
155
  - Non-cacheable variable reads (`createVar({ cache: false })`) inside the
144
156
  handler still throw, even if the data came from a loader.
145
157
  - Prefer DSL `loader()` + client `useLoader()` for data that depends on
@@ -160,23 +172,23 @@ Loaders receive the same context shape as route handlers.
160
172
 
161
173
  ### Full field surface
162
174
 
163
- | Field | Type | Notes |
164
- | -------------- | ------------------------------ | --------------------------------------------------------------------------------------------------- |
165
- | `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
166
- | `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
167
- | `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
168
- | `url` | `URL` | Parsed request URL. |
169
- | `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
170
- | `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
171
- | `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
172
- | `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). |
173
- | `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware. |
174
- | `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`). |
175
- | `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for non-loader segments before reading handle data. |
176
- | `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
177
- | `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
178
- | `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
179
- | `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers). |
175
+ | Field | Type | Notes |
176
+ | -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
178
+ | `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
179
+ | `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
180
+ | `url` | `URL` | Parsed request URL. |
181
+ | `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
182
+ | `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
183
+ | `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
184
+ | `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). |
185
+ | `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware. |
186
+ | `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`). |
187
+ | `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. |
188
+ | `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
189
+ | `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
190
+ | `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
191
+ | `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers). |
180
192
 
181
193
  ### Example
182
194
 
@@ -249,6 +261,8 @@ export const OrderLoader = createLoader(async (ctx) => {
249
261
  Add caching or revalidation to specific loaders:
250
262
 
251
263
  ```typescript
264
+ import * as CartActions from "./actions/cart";
265
+
252
266
  path("/product/:slug", ProductPage, { name: "product" }, () => [
253
267
  // Cached loader
254
268
  loader(ProductLoader, () => [cache({ ttl: 300 })]),
@@ -261,7 +275,7 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
261
275
  // Loader that revalidates after cart actions (defer otherwise — keeps the
262
276
  // permissive loader defaults for navigation and other actions intact)
263
277
  loader(CartLoader, () => [
264
- revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
278
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
265
279
  ]),
266
280
  ]);
267
281
  ```
@@ -559,6 +573,8 @@ entirely (no read, no write).
559
573
  ### Per-Loader Store Override
560
574
 
561
575
  ```typescript
576
+ import { MemorySegmentCacheStore } from "@rangojs/router/cache";
577
+
562
578
  const hotStore = new MemorySegmentCacheStore({ defaults: { ttl: 10 } });
563
579
 
564
580
  loader(PricingLoader, () => [
@@ -667,6 +683,16 @@ export const SearchLoader = createLoader(async (ctx) => {
667
683
  }, true); // true = fetchable
668
684
  ```
669
685
 
686
+ > **No registration needed — and no worker-entry import.** A fetchable loader
687
+ > does not have to be registered with `loader()` in the route DSL, and it does
688
+ > not have to be imported by any server module. Importing it into the client
689
+ > component that calls `useFetchLoader()` / `load()` is enough. Rango discovers
690
+ > every `createLoader(fn, true)` at build time and registers it for the
691
+ > `_rsc_loader` endpoint, so a loader reachable only through a client component
692
+ > still resolves in production — on both the generated entry and a hand-written
693
+ > worker entry (e.g. a Cloudflare `worker.rsc.tsx`). You do **not** need to
694
+ > force-import the loader in your worker entry to make it resolve.
695
+
670
696
  ### Fetchable Loader with Middleware
671
697
 
672
698
  Pass an options object instead of `true` to attach per-loader middleware.
@@ -781,10 +807,12 @@ export const CartLoader = createLoader(async (ctx) => {
781
807
  });
782
808
 
783
809
  // urls.tsx — register loaders in the DSL
810
+ import * as CartActions from "./actions/cart";
811
+
784
812
  export const urlpatterns = urls(({ path, layout, loader, loading, cache, revalidate }) => [
785
813
  layout(<ShopLayout />, () => [
786
814
  loader(CartLoader, () => [
787
- revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
815
+ revalidate((ctx) => ctx.isAction(CartActions) || undefined),
788
816
  ]),
789
817
 
790
818
  path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: middleware
3
- description: Define middleware for authentication, logging, and request processing in @rangojs/router
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.
4
4
  argument-hint: [middleware-name]
5
5
  ---
6
6
 
@@ -60,15 +60,21 @@ data itself.
60
60
  ### Revalidation Contracts with Middleware-Backed Trees
61
61
 
62
62
  Middleware can establish request-level context (`ctx.set`) for segments that
63
- execute in the current render pass. It does not change partial revalidation
64
- boundaries between handler/layout/parallel segments.
63
+ execute in the current render pass. Because route middleware wraps **every**
64
+ render pass normal renders, post-action revalidation, PE re-renders — its
65
+ variables are never stale: middleware is the safest `ctx.set` rung on the
66
+ data-passing ladder (`/rango` → "Passing data down the tree"). But it does
67
+ not change partial revalidation boundaries between handler/layout/parallel
68
+ segments.
65
69
 
66
70
  For shared segment data, use named revalidation contracts on both the producer
67
71
  and consumer segments, even when middleware is present in the chain.
68
72
 
69
73
  ```typescript
70
- export const revalidateCartData = ({ actionId }) =>
71
- actionId?.includes("src/actions/cart.ts#") || undefined;
74
+ import * as CartActions from "./actions/cart";
75
+
76
+ export const revalidateCartData = (ctx) =>
77
+ ctx.isAction(CartActions) || undefined;
72
78
 
73
79
  layout(CartLayout, () => [
74
80
  middleware(cartRenderMiddleware),
@@ -32,6 +32,10 @@ Common reasons to migrate:
32
32
  - **Build-time rendering** — `Static()` and `Prerender()` provide explicit
33
33
  build-time rendering instead of mixing rendering and caching behind conventions.
34
34
  See: `/prerender`
35
+ - **Partial prerendering, shipped** — the `ppr` path option caches a page's
36
+ HTML shell and resumes only the live holes on each request; loaders stay
37
+ fresh. The equivalent of Next's `experimental_ppr`, stable and per-route.
38
+ See: `/ppr`
35
39
  - **Composable route tree** — layouts, includes, middleware, parallels, and
36
40
  intercepts compose directly in the route definition.
37
41
  See: `/composability`, `/parallel`, `/intercept`
@@ -43,6 +47,34 @@ Common reasons to migrate:
43
47
 
44
48
  Work route-by-route, bottom-up. Start with leaf pages, then layouts, then middleware. Verify each route works before moving to the next.
45
49
 
50
+ ## Replace imports, never shim Next
51
+
52
+ Do NOT create mock `next/*` modules, Vite aliases for `next/*`, or compatibility
53
+ wrapper components (a local `Link` that forwards `href` to `to`, a fake
54
+ `useRouter`, a stubbed `next/headers`). Shims freeze Next semantics into the
55
+ app, hide unsupported behavior until runtime, and keep `next` in the dependency
56
+ graph — the migration looks done but isn't. Replace every `next/*` import at
57
+ its call site with the real Rango API:
58
+
59
+ | Next import | Replace with |
60
+ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
61
+ | `next/link` `Link` | `Link` from `@rangojs/router/client` — rename `href` to `to` (see §6) |
62
+ | `next/navigation` `useRouter`, `usePathname`, `useSearchParams`, `useParams` | same names from `@rangojs/router/client` |
63
+ | `next/navigation` `redirect`, `notFound` | `redirect`, `notFound` from `@rangojs/router` |
64
+ | `next/headers` `cookies`, `headers` | `cookies()`, `headers()` from `@rangojs/router` (server-only) |
65
+ | `next/cache` `revalidateTag`, `unstable_cache` | `updateTag`/`revalidateTag` from `@rangojs/router`; `"use cache"` (see §3 and `/use-cache`) |
66
+ | `next/server` `NextResponse`, `NextRequest` | web-standard `Response`/`Request`; middleware via `router.use()` (see §4) |
67
+ | `next/image` `Image` | plain `<img>` (keep explicit `width`/`height`) or your CDN's image URL — no built-in optimizer |
68
+ | `next/font` | see `/fonts` |
69
+ | `next/script` `Script` | see `/scripts` |
70
+ | `next-themes` | `theme: true` in `createRouter` (see §10) |
71
+
72
+ If an import has no row here and no obvious Rango equivalent, stop and surface
73
+ it to the user — do not mock it to keep the build green.
74
+
75
+ Done means: `grep -rn "from ['\"]next" src/ app/` returns nothing, and `next`
76
+ is gone from `package.json`.
77
+
46
78
  ## 1. Project Setup
47
79
 
48
80
  Replace Next.js tooling with Vite + Rango:
@@ -88,6 +120,21 @@ The Document component replaces `app/layout.tsx`'s `<html>` wrapper. See `/route
88
120
  | `app/shop/[...path]/page.tsx` | `path("/shop/:path+", CatchAll, { name: "shopCatchAll" })` |
89
121
  | `app/docs/[[...slug]]/page.tsx` | `path("/docs/:slug*", Docs, { name: "docs" })` |
90
122
 
123
+ The catch-all remainder is a single string at `ctx.params.<name>` with the `/`
124
+ separators preserved — split it to recover the array Next gives you:
125
+
126
+ ```typescript
127
+ // app/docs/[[...slug]]/page.tsx -> params.slug is string[] | undefined in Next
128
+ path("/docs/:slug*", (ctx) => {
129
+ // "" for /docs, "a/b/c" for /docs/a/b/c
130
+ const slug = ctx.params.slug === "" ? [] : ctx.params.slug.split("/");
131
+ return <Docs slug={slug} />;
132
+ }, { name: "docs" });
133
+ ```
134
+
135
+ `[...path]` (required, ≥1 segment) maps to `:path+`; `[[...slug]]` (optional,
136
+ matches the bare parent too) maps to `:slug*` — which binds `""` at `/docs`.
137
+
91
138
  ### Layouts
92
139
 
93
140
  ```typescript
@@ -153,6 +200,18 @@ export const marketingPatterns = urls(({ path }) => [
153
200
  include("/", marketingPatterns, { name: "marketing" }),
154
201
  ```
155
202
 
203
+ Next.js code-splits each route segment automatically. Rango's eager `include()`
204
+ bundles the group into the entry chunk; to get Next-style per-section splitting,
205
+ pass an async provider so the group loads on the first request under its prefix:
206
+
207
+ ```typescript
208
+ // urls/admin.tsx: `export default adminPatterns` — loads on first /admin request
209
+ include("/admin", () => import("./urls/admin"), { name: "admin" }),
210
+ ```
211
+
212
+ Route types, `href()`, and prerender still see every route in the split group.
213
+ See `/composability`.
214
+
156
215
  ### Parallel routes
157
216
 
158
217
  In Next.js, `@sidebar` and `@main` are both named slots. In Rango, the main content
@@ -193,9 +252,9 @@ The main content always goes through `<Outlet />` via the `path()` handler.
193
252
  // Rango: explicit intercept in layout
194
253
  layout(<ShopLayout />, () => [
195
254
  path("/product/:id", ProductPage, { name: "product" }),
196
- intercept("@modal", ".product", <ProductModal />, () => [
197
- when(({ from }) => from.pathname.startsWith("/shop")),
198
- ]),
255
+ intercept("@modal", ".product", <ProductModal />, {
256
+ when: ({ from }) => from.pathname.startsWith("/shop"),
257
+ }),
199
258
  ])
200
259
  ```
201
260
 
@@ -288,21 +347,136 @@ export const Product = Passthrough(ProductDef, async (ctx) => {
288
347
  Use `Passthrough()` whenever the Next.js route has `dynamicParams: true` (the
289
348
  default) or serves an open-ended param space. See `/prerender` for full API.
290
349
 
291
- ### Revalidation: different model
350
+ ### Rendering-mode segment config
351
+
352
+ Next.js route segment config maps onto Rango's explicit primitives:
292
353
 
293
- Next.js uses path/tag-based cache invalidation (`revalidatePath`, `revalidateTag`)
294
- to bust cached responses. Rango does not currently have a direct equivalent.
354
+ | Next.js segment config | Rango |
355
+ | --------------------------------------------------- | ------------------------------------------------------------ |
356
+ | `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`) |
357
+ | `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`) |
358
+ | `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them |
359
+ | `dynamicParams = true` | `Passthrough()` (above) |
360
+ | `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`) |
295
361
 
296
- In Rango, separate these two concepts:
362
+ ### Partial prerendering the `ppr` path option
297
363
 
298
- **Partial rendering revalidation** `revalidate()` controls which segments
299
- (layouts, paths, loaders, parallels) should re-run during partial action
300
- re-rendering. This is about the segment tree, not cache invalidation:
364
+ Next.js PPR statically prerenders a shell at build time and streams the parts
365
+ inside `<Suspense>` at request time. Rango ships the same model as a path
366
+ option the shell is captured at runtime into the app cache store and resumed
367
+ on later requests, with the holes rendered fresh per request:
301
368
 
302
369
  ```typescript
370
+ // Next.js: app/products/[id]/page.tsx
371
+ export const experimental_ppr = true;
372
+ export default async function Page({ params }) {
373
+ return (
374
+ <ProductShell>
375
+ <Suspense fallback={<PriceSkeleton />}>
376
+ <LivePrice id={params.id} />
377
+ </Suspense>
378
+ </ProductShell>
379
+ );
380
+ }
381
+
382
+ // Rango, step 1 — direct carry-over. Your Suspense tree IS the hole model:
383
+ // hand the un-awaited promise down, keep the boundary, add the ppr option.
384
+ // No loader, no loading(), no restructuring.
385
+ function ProductPage(ctx: HandlerContext) {
386
+ const price = fetchPrice(ctx.params.id); // pending promise — NOT awaited
387
+ return (
388
+ <ProductShell>
389
+ <Suspense fallback={<PriceSkeleton />}>
390
+ <LivePrice price={price} /> {/* use(price) inside */}
391
+ </Suspense>
392
+ </ProductShell>
393
+ );
394
+ }
395
+ path("/products/:id", ProductPage, {
396
+ name: "product",
397
+ ppr: { ttl: 600, swr: 120 }, // or ppr: true (default ttl 300s)
398
+ });
399
+
400
+ // Rango, step 2 (optional refinement) — promote the fetch to a loader for a
401
+ // GUARANTEED hole: loaders are masked at capture and fresh on every serve,
402
+ // even when the value resolves instantly (a raw promise that settles fast
403
+ // would bake into the shell). loading() is the loader's hole boundary.
404
+ path(
405
+ "/products/:id",
406
+ ProductPage,
407
+ { name: "product", ppr: { ttl: 600, swr: 120 } },
408
+ () => [loader(LivePriceLoader), loading(<PriceSkeleton />)],
409
+ ),
410
+ ```
411
+
412
+ Differences that matter during migration:
413
+
414
+ - **The Suspense/promise model carries over.** As in Next, a still-pending
415
+ promise handed to a component that suspends under its own `<Suspense>`
416
+ postpones at capture and becomes a hole — existing Next PPR trees keep
417
+ working as-is, no `loading()` required. One container rule everywhere
418
+ (handlers, handles, loaders): awaited/settled data bakes into the shell; a
419
+ promise nested inside your data stays a live hole. For loaders, `loading()`
420
+ selects the lane: present = guaranteed live (masked at capture, fresh every
421
+ serve, immune to fast resolution — prefer it for per-request data); absent =
422
+ the bake lane (the settled container bakes and is snapshot-pinned per shell,
423
+ nested promises stay live). Identity reads (`cookies()`/`headers()`) where
424
+ the value would bake refuse the capture by construction.
425
+ - **Shell freshness is explicit.** Next's PPR shell is fixed until the next
426
+ build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
427
+ `revalidateTag()` drop the shell (`revalidate()` does not — it is a data
428
+ lever and never touches shell HTML).
429
+ - **`cookies()`/`headers()` in shell material THROW during capture** (in Next
430
+ they silently force dynamic rendering). Per-user reads must move behind a
431
+ `loading()` boundary (the live loader lane) or into a nested promise — the
432
+ refusal surfaces at migration time, which is the point.
433
+ - **A store is required.** PPR needs the app-level `createRouter({ cache })`
434
+ store to implement the shell family (`MemorySegmentCacheStore`,
435
+ `CFCacheStore`, `VercelCacheStore`). Without one the route quietly stays
436
+ fully dynamic with a once-per-key warning.
437
+ - **Middleware still guards every serve.** Auth middleware (global or route
438
+ DSL) runs before any shell byte on HIT and MISS alike — no Next-style "PPR
439
+ bypasses middleware" caveats to migrate around.
440
+
441
+ A route without `ppr` pays zero cost. See `/ppr` for the full execution matrix,
442
+ hole rules, and pitfalls.
443
+
444
+ ### Revalidation: two distinct axes
445
+
446
+ Next.js conflates two things under "revalidation." Rango separates them — and
447
+ tag-based cache invalidation now maps directly.
448
+
449
+ **1. Cache invalidation (bust cached values) — direct equivalent.** Tag entries
450
+ with `cache({ tags })` or runtime `cacheTag(...tags)`. `cacheTag()` works inside a
451
+ `"use cache"` function (tags that entry) AND render-callable in a plain server
452
+ component (no `"use cache"` needed — it tags the document / PPR shell the component
453
+ renders into). Then invalidate by tag:
454
+
455
+ ```typescript
456
+ // Next.js Rango
457
+ // revalidateTag("products") → await updateTag("products") // in a server action: awaitable,
458
+ // // read-your-own-writes (next render is fresh)
459
+ // or revalidateTag("products") // in a route handler / webhook:
460
+ // // background, non-blocking (hard-purge)
461
+ ```
462
+
463
+ `updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
464
+ hard-purge (the next read re-renders fresh); the only difference is awaitability —
465
+ despite the Next.js name, `revalidateTag` here is NOT stale-while-revalidate.
466
+ Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`) index by tag. Next's
467
+ `revalidatePath` has no path-based equivalent — tag the relevant entries instead.
468
+
469
+ **2. Partial-render selection (which segments re-run after an action).** This is
470
+ NOT cache invalidation — it is `revalidate()`, controlling which segments
471
+ (layouts, paths, loaders, parallels) recompute during partial action
472
+ re-rendering:
473
+
474
+ ```typescript
475
+ import { updateBlog } from "./actions/blog";
476
+
303
477
  // Re-run this layout when a blog action fires
304
478
  layout(BlogLayout, () => [
305
- revalidate(({ actionId }) => actionId?.includes("updateBlog") || undefined),
479
+ revalidate((ctx) => ctx.isAction(updateBlog) || undefined),
306
480
  path("/blog/:slug", BlogPost, { name: "blogPost" }),
307
481
  ]);
308
482
 
@@ -323,15 +497,18 @@ cache({ ttl: 60, swr: 300 }, () => [
323
497
  ]);
324
498
  ```
325
499
 
326
- The key shift is:
500
+ The two axes compose: `updateTag()` / `revalidateTag()` bust cached values;
501
+ `revalidate()` selects which segments re-render and stream to the client after an
502
+ action.
327
503
 
328
- - Next.js asks "which cached path or tag should I invalidate?"
329
- - Rango asks "which segments should re-run after this action?"
504
+ When migrating:
330
505
 
331
- When migrating `revalidatePath()` / `revalidateTag()` usage, the Rango version
332
- usually is not a 1:1 API replacement. Instead, decide which layouts, routes,
333
- loaders, or parallels should recompute after an action and declare
334
- `revalidate()` at those segment boundaries.
506
+ - `revalidateTag(tag)` `await updateTag(tag)` (in a server action) or
507
+ `revalidateTag(tag)` (in a route handler / webhook). Effectively 1:1.
508
+ - `revalidatePath(path)` no path-based equivalent; tag the entries on that
509
+ route (`cache({ tags })` / `cacheTag(...)`) and invalidate by tag.
510
+ - To also force specific segments to re-render after the action (independent of
511
+ cache busting), attach a `revalidate()` rule at those segment boundaries.
335
512
 
336
513
  ## 4. Middleware
337
514
 
@@ -463,7 +640,7 @@ Server actions work the same way — `"use server"` directive, `useActionState`,
463
640
 
464
641
  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()`.
465
642
 
466
- Next.js's `revalidatePath()` / `revalidateTag()` have no direct equivalent — Rango partially re-renders matched route segments (path/layout/parallel/intercept) and re-resolves their loaders, and you scope re-runs by attaching a `revalidate(({ actionId }) => ...)` rule to any segment or loader registration. See `/server-actions` for the full pattern (validation, error handling, file uploads) and `/loader` for revalidation rule semantics.
643
+ 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.
467
644
 
468
645
  ## 8. Metadata / Head
469
646
 
@@ -559,4 +736,10 @@ See `/theme` for full API including system detection and cookie persistence.
559
736
  10. [ ] Migrate API routes to `path.json()` / `path.text()`
560
737
  11. [ ] Update metadata to use `Meta` handle + `<MetaTags />` in document head
561
738
  12. [ ] Replace `next-themes` with `theme: true` in createRouter (see `/theme`)
562
- 13. [ ] Run `npx rango generate src/` to generate route types
739
+ 13. [ ] Map rendering-mode segment config: `revalidate = N` `cache({ ttl })`,
740
+ `force-static` → `Static()`/`Prerender()`, `experimental_ppr` → the
741
+ `ppr` path option (loader + `loading()` as the hole)
742
+ 14. [ ] Run `npx rango generate src/` to generate route types
743
+ 15. [ ] Verify no shims: `grep -rn "from ['\"]next" src/ app/` returns nothing,
744
+ no mock `next/*` modules or aliases exist, and `next` is out of
745
+ `package.json`