@rangojs/router 0.0.0-experimental.002d056c

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 (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ import { Outlet } from "../client.js";
3
+
4
+ const MapRootLayout = (
5
+ <>
6
+ <Outlet />
7
+ </>
8
+ ) as ReactNode;
9
+
10
+ export default MapRootLayout;
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2020"],
5
+ "types": ["node", "vite/client"],
6
+ "typeRoots": ["../../node_modules/@types"],
7
+ "moduleResolution": "bundler",
8
+ "rootDir": ".",
9
+ "outDir": "../../dist/server",
10
+ "composite": true,
11
+ "verbatimModuleSyntax": true
12
+ },
13
+ "include": ["./**/*"]
14
+ }
package/src/server.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @rangojs/router/server — Internal subpath
3
+ *
4
+ * This module is NOT user-facing. Import from "@rangojs/router" instead.
5
+ *
6
+ * Exports here are consumed by the Vite plugin (discovery, manifest injection,
7
+ * virtual modules) and the RSC handler internals. They are not part of the
8
+ * public API and may change without notice.
9
+ */
10
+
11
+ // Router registry (used by Vite plugin for build-time discovery)
12
+ export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
13
+
14
+ // Host router registry (used by Vite plugin for host-router lazy discovery)
15
+ export {
16
+ HostRouterRegistry,
17
+ type HostRouterRegistryEntry,
18
+ } from "./host/router.js";
19
+
20
+ // Route map builder (Vite plugin injects these via virtual modules)
21
+ export {
22
+ registerRouteMap,
23
+ setCachedManifest,
24
+ clearCachedManifest,
25
+ clearAllRouterData,
26
+ getGlobalRouteMap,
27
+ getRouterManifest,
28
+ setPrecomputedEntries,
29
+ setRouteTrie,
30
+ setManifestReadyPromise,
31
+ setRouterManifest,
32
+ setRouterTrie,
33
+ setRouterPrecomputedEntries,
34
+ registerRouterManifestLoader,
35
+ ensureRouterManifest,
36
+ } from "./route-map-builder.js";
37
+
38
+ // Loader registry (Vite plugin registers lazy loader imports)
39
+ export {
40
+ registerLoaderById,
41
+ setLoaderImports,
42
+ } from "./server/loader-registry.js";
43
+
44
+ // Request context creation (used by RSC handler, not user-facing)
45
+ export {
46
+ createRequestContext,
47
+ type CreateRequestContextOptions,
48
+ } from "./server/request-context.js";
49
+
50
+ // Component utilities (used internally for server/client boundary checks)
51
+ export { isClientComponent, assertClientComponent } from "./component-utils.js";
@@ -0,0 +1,365 @@
1
+ import React from "react";
2
+ import { renderSegments } from "../segment-system.js";
3
+ import { filterSegmentOrder } from "../browser/react/filter-segment-order.js";
4
+ import { ThemeProvider } from "../theme/ThemeProvider.js";
5
+ import { NonceContext } from "../browser/react/nonce-context.js";
6
+ import { NavigationStoreContext } from "../browser/react/context.js";
7
+ import type { NavigationStoreContextValue } from "../browser/react/context.js";
8
+ import type { HandleData } from "../browser/types.js";
9
+ import type { ErrorPhase } from "../types.js";
10
+ import type { ResolvedSegment } from "../types.js";
11
+ import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
12
+ import type {
13
+ EventController,
14
+ DerivedNavigationState,
15
+ } from "../browser/event-controller.js";
16
+
17
+ /**
18
+ * Options for injectRSCPayload
19
+ */
20
+ export interface InjectRSCPayloadOptions {
21
+ /**
22
+ * Nonce for Content Security Policy (CSP)
23
+ */
24
+ nonce?: string;
25
+ }
26
+
27
+ /**
28
+ * Options for renderToReadableStream from react-dom/server
29
+ */
30
+ interface RenderToReadableStreamOptions {
31
+ bootstrapScriptContent?: string;
32
+ nonce?: string;
33
+ formState?: unknown;
34
+ }
35
+
36
+ /**
37
+ * ReadableStream with the allReady promise added by react-dom/server.edge.
38
+ */
39
+ interface ReactDOMReadableStream extends ReadableStream<Uint8Array> {
40
+ allReady: Promise<void>;
41
+ }
42
+
43
+ /**
44
+ * Options for the renderHTML function
45
+ */
46
+ export interface SSRRenderOptions {
47
+ /**
48
+ * Form state for useActionState progressive enhancement.
49
+ * This is the result of decodeFormState() and should be passed to
50
+ * react-dom's renderToReadableStream to enable useActionState to
51
+ * receive the action result during SSR.
52
+ */
53
+ formState?: unknown;
54
+
55
+ /**
56
+ * Nonce for Content Security Policy (CSP)
57
+ */
58
+ nonce?: string;
59
+
60
+ /**
61
+ * SSR stream mode.
62
+ *
63
+ * - `"stream"` (default) — start flushing HTML immediately.
64
+ * - `"allReady"` — await `stream.allReady` before returning.
65
+ */
66
+ streamMode?: import("../router/router-options.js").SSRStreamMode;
67
+ }
68
+
69
+ /**
70
+ * SSR dependencies from external packages
71
+ */
72
+ export interface SSRDependencies<TEnv = unknown> {
73
+ /**
74
+ * createFromReadableStream from @vitejs/plugin-rsc/ssr
75
+ */
76
+ createFromReadableStream: <T>(
77
+ stream: ReadableStream<Uint8Array>,
78
+ ) => Promise<T>;
79
+
80
+ /**
81
+ * renderToReadableStream from react-dom/server.edge
82
+ */
83
+ renderToReadableStream: (
84
+ element: React.ReactNode,
85
+ options?: RenderToReadableStreamOptions,
86
+ ) => Promise<ReactDOMReadableStream>;
87
+
88
+ /**
89
+ * injectRSCPayload from rsc-html-stream/server
90
+ */
91
+ injectRSCPayload: (
92
+ rscStream: ReadableStream<Uint8Array>,
93
+ options?: InjectRSCPayloadOptions,
94
+ ) => TransformStream<Uint8Array, Uint8Array>;
95
+
96
+ /**
97
+ * Function to load bootstrap script content
98
+ * Typically: () => import.meta.viteRsc.loadBootstrapScriptContent("index")
99
+ */
100
+ loadBootstrapScriptContent: () => Promise<string>;
101
+
102
+ /**
103
+ * Optional callback invoked when an error occurs during SSR rendering.
104
+ *
105
+ * This callback is for notification/logging purposes.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * export const renderHTML = createSSRHandler({
110
+ * // ... other deps
111
+ * onError: (error, context) => {
112
+ * console.error('[SSR] Rendering error:', error);
113
+ * Sentry.captureException(error);
114
+ * },
115
+ * });
116
+ * ```
117
+ */
118
+ onError?: (error: Error, context: { phase: ErrorPhase }) => void;
119
+ }
120
+
121
+ /**
122
+ * RSC payload type (minimal interface for SSR)
123
+ */
124
+ interface RscPayload {
125
+ metadata?: {
126
+ segments?: ResolvedSegment[];
127
+ rootLayout?: React.ComponentType<{ children: React.ReactNode }>;
128
+ handles?: AsyncGenerator<HandleData, void, unknown>;
129
+ matched?: string[];
130
+ pathname?: string;
131
+ params?: Record<string, string>;
132
+ themeConfig?: ResolvedThemeConfig | null;
133
+ initialTheme?: Theme;
134
+ version?: string;
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Consume an async generator and return a Promise that resolves with the final value.
140
+ * Used for SSR where we need to await all handle data before rendering.
141
+ */
142
+ async function consumeAsyncGenerator(
143
+ generator: AsyncGenerator<HandleData, void, unknown>,
144
+ ): Promise<HandleData> {
145
+ let lastData: HandleData = {};
146
+ for await (const data of generator) {
147
+ lastData = data;
148
+ }
149
+ return lastData;
150
+ }
151
+
152
+ /**
153
+ * Create a minimal event controller for SSR.
154
+ * This provides the correct pathname so useNavigation returns the right value during SSR.
155
+ */
156
+ function createSsrEventController(opts: {
157
+ pathname: string;
158
+ params?: Record<string, string>;
159
+ handleData?: HandleData;
160
+ matched?: string[];
161
+ }): EventController {
162
+ const location = new URL(opts.pathname, "http://localhost");
163
+ let params = opts.params ?? {};
164
+ const handleState = {
165
+ data: opts.handleData ?? {},
166
+ segmentOrder: filterSegmentOrder(opts.matched ?? []),
167
+ };
168
+ const state: DerivedNavigationState = {
169
+ state: "idle",
170
+ isStreaming: false,
171
+ location,
172
+ pendingUrl: null,
173
+ inflightActions: [],
174
+ };
175
+
176
+ return {
177
+ getState: () => state,
178
+ getLocation: () => location,
179
+ subscribe: () => () => {},
180
+ getActionState: () => ({
181
+ state: "idle",
182
+ actionId: null,
183
+ payload: null,
184
+ error: null,
185
+ result: null,
186
+ }),
187
+ subscribeToAction: () => () => {},
188
+ subscribeToHandles: () => () => {},
189
+ setHandleData: () => {},
190
+ getHandleState: () => handleState,
191
+ setParams: (nextParams) => {
192
+ params = nextParams;
193
+ },
194
+ getParams: () => params,
195
+ setLocation: () => {},
196
+ startNavigation: () => {
197
+ throw new Error("Navigation not supported during SSR");
198
+ },
199
+ abortNavigation: () => {},
200
+ startAction: () => {
201
+ throw new Error("Actions not supported during SSR");
202
+ },
203
+ abortAllActions: () => {},
204
+ getCurrentNavigation: () => null,
205
+ getInflightActions: () => new Map(),
206
+ hadAnyConcurrentActions: () => false,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Create an SSR handler that converts RSC streams to HTML.
212
+ *
213
+ * @example
214
+ * ```tsx
215
+ * import { createSSRHandler } from "rsc-router/ssr";
216
+ * import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
217
+ * import { renderToReadableStream } from "react-dom/server.edge";
218
+ * import { injectRSCPayload } from "rsc-html-stream/server";
219
+ *
220
+ * export const renderHTML = createSSRHandler({
221
+ * createFromReadableStream,
222
+ * renderToReadableStream,
223
+ * injectRSCPayload,
224
+ * loadBootstrapScriptContent: () =>
225
+ * import.meta.viteRsc.loadBootstrapScriptContent("index"),
226
+ * });
227
+ * ```
228
+ */
229
+ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
230
+ const {
231
+ createFromReadableStream,
232
+ renderToReadableStream,
233
+ injectRSCPayload,
234
+ loadBootstrapScriptContent,
235
+ onError,
236
+ } = deps;
237
+
238
+ /**
239
+ * Render RSC stream to HTML stream
240
+ *
241
+ * @param rscStream - The RSC stream to render
242
+ * @param options - Optional render options including formState for useActionState and nonce for CSP
243
+ */
244
+ return async function renderHTML(
245
+ rscStream: ReadableStream<Uint8Array>,
246
+ options?: SSRRenderOptions,
247
+ ): Promise<ReadableStream<Uint8Array>> {
248
+ const { nonce, formState, streamMode } = options ?? {};
249
+
250
+ try {
251
+ // Tee the stream:
252
+ // - rscStream1: For SSR rendering (deserialize to React VDOM)
253
+ // - rscStream2: For browser hydration (inject as __FLIGHT_DATA__)
254
+ const [rscStream1, rscStream2] = rscStream.tee();
255
+
256
+ // Deserialize RSC stream to React tree
257
+ let payload: Promise<RscPayload> | undefined;
258
+ let handlesPromise: Promise<HandleData> | undefined;
259
+ let ssrContextValue: NavigationStoreContextValue | undefined;
260
+ function SsrRoot() {
261
+ payload ??= createFromReadableStream<RscPayload>(rscStream1);
262
+ const resolved = React.use(payload);
263
+ const themeConfig = resolved.metadata?.themeConfig ?? null;
264
+ const pathname = resolved.metadata?.pathname ?? "/";
265
+
266
+ // Await handles before creating SSR event controller so hooks can
267
+ // read request-local handle data via NavigationStoreContext.
268
+ // The handles property is an async generator that yields on each push
269
+ // Memoize the promise since async generators can only be iterated once
270
+ let handleData: HandleData = {};
271
+ if (resolved.metadata?.handles) {
272
+ handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
273
+ handleData = React.use(handlesPromise);
274
+ }
275
+
276
+ // Create SSR context with request-local pathname/params/handles.
277
+ ssrContextValue ??= {
278
+ store: null as any,
279
+ eventController: createSsrEventController({
280
+ pathname,
281
+ params: resolved.metadata?.params,
282
+ handleData,
283
+ matched: resolved.metadata?.matched,
284
+ }),
285
+ navigate: async () => {},
286
+ refresh: async () => {},
287
+ version: resolved.metadata?.version,
288
+ };
289
+
290
+ // Build content tree from segments.
291
+ // Order must match NavigationProvider: NavigationStoreContext > ThemeProvider > content
292
+ const reconstructedRoot = renderSegments(
293
+ resolved.metadata?.segments ?? [],
294
+ {
295
+ rootLayout: resolved.metadata?.rootLayout,
296
+ },
297
+ );
298
+ let content: React.ReactNode =
299
+ reconstructedRoot instanceof Promise
300
+ ? React.use(reconstructedRoot)
301
+ : reconstructedRoot;
302
+
303
+ // Wrap content with ThemeProvider if theme is enabled
304
+ if (themeConfig) {
305
+ content = (
306
+ <ThemeProvider
307
+ config={themeConfig}
308
+ initialTheme={resolved.metadata?.initialTheme}
309
+ >
310
+ {content}
311
+ </ThemeProvider>
312
+ );
313
+ }
314
+
315
+ // Wrap with NonceContext so client components (e.g. MetaTags) can
316
+ // apply CSP nonces to inline scripts during SSR. Always present to
317
+ // match the browser-side NavigationProvider tree shape for hydration.
318
+ content = (
319
+ <NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
320
+ );
321
+
322
+ // Wrap with NavigationStoreContext for useNavigation hook
323
+ return (
324
+ <NavigationStoreContext.Provider value={ssrContextValue!}>
325
+ {content}
326
+ </NavigationStoreContext.Provider>
327
+ );
328
+ }
329
+
330
+ // Get bootstrap script content
331
+ const bootstrapScriptContent = await loadBootstrapScriptContent();
332
+
333
+ // Render React tree to HTML stream
334
+ // Pass formState for useActionState progressive enhancement if provided
335
+ // Pass nonce for CSP if provided
336
+ const htmlStream = await renderToReadableStream(<SsrRoot />, {
337
+ bootstrapScriptContent,
338
+ formState,
339
+ nonce,
340
+ });
341
+
342
+ // Wait for all Suspense boundaries to resolve when streamMode is "allReady".
343
+ // This buffers the entire HTML before flushing — used for bots that
344
+ // cannot process streamed HTML.
345
+ if (streamMode === "allReady") {
346
+ await htmlStream.allReady;
347
+ }
348
+
349
+ // Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
350
+ return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
351
+ } catch (error) {
352
+ // Invoke onError callback if provided
353
+ if (onError) {
354
+ const errorObj =
355
+ error instanceof Error ? error : new Error(String(error));
356
+ try {
357
+ onError(errorObj, { phase: "rendering" });
358
+ } catch (callbackError) {
359
+ console.error("[SSRHandler.onError] Callback error:", callbackError);
360
+ }
361
+ }
362
+ throw error;
363
+ }
364
+ };
365
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Static handler definition for build-time rendering of individual segments.
3
+ *
4
+ * Static wraps a handler so that in production the segment is
5
+ * rendered once at build time. The handler is then replaced with a static
6
+ * asset import -- no runtime store lookup needed.
7
+ *
8
+ * In dev mode, Static behaves as a normal handler: the wrapped
9
+ * function runs on every request, identical to a regular layout/path handler.
10
+ *
11
+ * The $$id is auto-generated by the Vite exposeInternalIds plugin based on
12
+ * file path and export name. No manual naming required.
13
+ *
14
+ * Key difference from Prerender:
15
+ * - Prerender: route-scoped, produces URLs via getParams, renders subtree per-params
16
+ * - Static: segment-scoped, renders once, no URLs, no params
17
+ *
18
+ * Works on: layout(), parallel(), and path().
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * export const DocsNav = Static((ctx) => <Nav docs={readDocsSync()} />);
23
+ * export const DocShell = Static((ctx) => <Shell />);
24
+ *
25
+ * urls(({ path, layout }) => [
26
+ * layout(DocsNav, () => [
27
+ * path("/getting-started", DocShell, { name: "doc.gs" }),
28
+ * path("/:slug", Prerender(getParams, DocPageHandler)),
29
+ * ]),
30
+ * ]);
31
+ * ```
32
+ */
33
+ import type { ReactNode } from "react";
34
+ import type { Handler } from "./types.js";
35
+ import type { PrerenderOptions, StaticBuildContext } from "./prerender.js";
36
+ import { isCachedFunction } from "./cache/taint.js";
37
+
38
+ // -- Types ------------------------------------------------------------------
39
+
40
+ export interface StaticHandlerDefinition<
41
+ TParams extends Record<string, any> = any,
42
+ > {
43
+ readonly __brand: "staticHandler";
44
+ /** Auto-generated unique ID (injected by Vite plugin). */
45
+ $$id: string;
46
+ /** In dev mode, the actual handler function that layout/path/parallel can call. */
47
+ handler: Handler<TParams>;
48
+ /** Static handler options (passthrough support). */
49
+ options?: PrerenderOptions;
50
+ }
51
+
52
+ // -- Function ---------------------------------------------------------------
53
+
54
+ export function Static<TParams extends Record<string, any> = {}>(
55
+ handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
56
+ options?: PrerenderOptions,
57
+ __injectedId?: string,
58
+ ): StaticHandlerDefinition<TParams>;
59
+
60
+ // -- Implementation ---------------------------------------------------------
61
+
62
+ export function Static<TParams extends Record<string, any>>(
63
+ handler: Function,
64
+ optionsOrId?: PrerenderOptions | string,
65
+ maybeId?: string,
66
+ ): StaticHandlerDefinition<TParams> {
67
+ if (isCachedFunction(handler)) {
68
+ throw new Error(
69
+ 'A "use cache" function cannot be used as a Static() handler. ' +
70
+ "Static handlers are rendered once at build time. Remove the " +
71
+ '"use cache" directive — Static already provides caching.',
72
+ );
73
+ }
74
+
75
+ let options: PrerenderOptions | undefined;
76
+ let id: string;
77
+
78
+ if (typeof optionsOrId === "string") {
79
+ id = optionsOrId;
80
+ } else {
81
+ options = optionsOrId as PrerenderOptions | undefined;
82
+ id = maybeId ?? "";
83
+ }
84
+
85
+ if (!id) {
86
+ throw new Error(
87
+ "[rsc-router] Static: missing $$id. " +
88
+ "Ensure the exposeInternalIds Vite plugin is configured.",
89
+ );
90
+ }
91
+
92
+ return {
93
+ __brand: "staticHandler" as const,
94
+ $$id: id,
95
+ handler: handler as Handler<TParams>,
96
+ ...(options ? { options } : {}),
97
+ };
98
+ }
99
+
100
+ // -- Type guard -------------------------------------------------------------
101
+
102
+ /**
103
+ * Type guard to check if a value is a StaticHandlerDefinition.
104
+ */
105
+ export function isStaticHandler(
106
+ value: unknown,
107
+ ): value is StaticHandlerDefinition {
108
+ return (
109
+ typeof value === "object" &&
110
+ value !== null &&
111
+ "__brand" in value &&
112
+ (value as { __brand: unknown }).__brand === "staticHandler"
113
+ );
114
+ }