@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30

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 (297) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +883 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4655 -747
  5. package/package.json +78 -50
  6. package/skills/cache-guide/SKILL.md +262 -0
  7. package/skills/caching/SKILL.md +54 -25
  8. package/skills/composability/SKILL.md +172 -0
  9. package/skills/debug-manifest/SKILL.md +12 -8
  10. package/skills/document-cache/SKILL.md +23 -21
  11. package/skills/fonts/SKILL.md +167 -0
  12. package/skills/hooks/SKILL.md +390 -63
  13. package/skills/host-router/SKILL.md +218 -0
  14. package/skills/intercept/SKILL.md +133 -10
  15. package/skills/layout/SKILL.md +102 -5
  16. package/skills/links/SKILL.md +239 -0
  17. package/skills/loader/SKILL.md +366 -29
  18. package/skills/middleware/SKILL.md +173 -36
  19. package/skills/mime-routes/SKILL.md +128 -0
  20. package/skills/parallel/SKILL.md +80 -3
  21. package/skills/prerender/SKILL.md +643 -0
  22. package/skills/rango/SKILL.md +86 -16
  23. package/skills/response-routes/SKILL.md +411 -0
  24. package/skills/route/SKILL.md +227 -14
  25. package/skills/router-setup/SKILL.md +225 -32
  26. package/skills/tailwind/SKILL.md +129 -0
  27. package/skills/theme/SKILL.md +12 -11
  28. package/skills/typesafety/SKILL.md +401 -75
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +10 -4
  31. package/src/bin/rango.ts +321 -0
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +20 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +201 -553
  41. package/src/browser/navigation-client.ts +124 -71
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +267 -317
  46. package/src/browser/prefetch/cache.ts +146 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +42 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +173 -73
  53. package/src/browser/react/NavigationProvider.tsx +138 -27
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +37 -0
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +49 -65
  65. package/src/browser/react/use-href.tsx +20 -188
  66. package/src/browser/react/use-link-status.ts +6 -5
  67. package/src/browser/react/use-mount.ts +31 -0
  68. package/src/browser/react/use-navigation.ts +27 -78
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +111 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +83 -0
  79. package/src/browser/server-action-bridge.ts +504 -584
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +92 -57
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +438 -0
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +35 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +10 -15
  114. package/src/client.tsx +114 -135
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +34 -19
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +165 -0
  124. package/src/host/errors.ts +97 -0
  125. package/src/host/index.ts +53 -0
  126. package/src/host/pattern-matcher.ts +214 -0
  127. package/src/host/router.ts +352 -0
  128. package/src/host/testing.ts +79 -0
  129. package/src/host/types.ts +146 -0
  130. package/src/host/utils.ts +25 -0
  131. package/src/href-client.ts +135 -49
  132. package/src/index.rsc.ts +182 -17
  133. package/src/index.ts +238 -24
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +27 -142
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +37 -0
  140. package/src/prerender/store.ts +185 -0
  141. package/src/prerender.ts +463 -0
  142. package/src/reverse.ts +330 -0
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +9 -11
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1388
  151. package/src/route-map-builder.ts +241 -112
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +70 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +371 -81
  159. package/src/router/intercept-resolution.ts +395 -0
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +155 -32
  164. package/src/router/match-api.ts +620 -0
  165. package/src/router/match-context.ts +5 -3
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +382 -9
  169. package/src/router/match-middleware/cache-store.ts +51 -22
  170. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  171. package/src/router/match-middleware/segment-resolution.ts +24 -6
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -29
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +321 -30
  179. package/src/router/prerender-match.ts +400 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1241 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -0
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +239 -0
  197. package/src/router/types.ts +77 -3
  198. package/src/router.ts +688 -3656
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +786 -760
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +5 -25
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +235 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +40 -14
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +57 -61
  217. package/src/server/context.ts +202 -51
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +422 -70
  223. package/src/server.ts +36 -120
  224. package/src/ssr/index.tsx +157 -26
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1577
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -726
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -782
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -3
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/href.ts +0 -255
  294. package/src/vite/expose-handle-id.ts +0 -209
  295. package/src/vite/expose-loader-id.ts +0 -357
  296. package/src/vite/expose-location-state-id.ts +0 -177
  297. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/server.ts CHANGED
@@ -1,135 +1,51 @@
1
1
  /**
2
- * rsc-router/server
2
+ * @rangojs/router/server — Internal subpath
3
3
  *
4
- * Server-only exports for route definition and building
5
- * These should only be imported in server-side handler files
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.
6
9
  */
7
10
 
8
- // Route definition helpers (server-only)
9
- export {
10
- createLoader,
11
- redirect,
12
- type RouteHelpers,
13
- type RouteHandlers,
14
- } from "./route-definition.js";
11
+ // Router registry (used by Vite plugin for build-time discovery)
12
+ export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
15
13
 
16
- // Django-style URL patterns (server-only)
14
+ // Host router registry (used by Vite plugin for host-router lazy discovery)
17
15
  export {
18
- urls,
19
- type PathHelpers,
20
- type PathOptions,
21
- type UrlPatterns,
22
- type IncludeOptions,
23
- } from "./urls.js";
16
+ HostRouterRegistry,
17
+ type HostRouterRegistryEntry,
18
+ } from "./host/router.js";
24
19
 
25
- // Re-export IncludeItem from route-types
26
- export type { IncludeItem } from "./route-types.js";
27
-
28
- // Core router (server-only)
20
+ // Route map builder (Vite plugin injects these via virtual modules)
29
21
  export {
30
- createRouter,
31
- type RSCRouter,
32
- type RSCRouterOptions,
33
- type RootLayoutProps,
34
- } from "./router.js";
35
-
36
- // Type-safe href utilities
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)
37
39
  export {
38
- createHref,
39
- type HrefFunction,
40
- type PrefixedRoutes,
41
- type PrefixRoutePatterns,
42
- type ParamsFor,
43
- type SanitizePrefix,
44
- type MergeRoutes,
45
- } from "./href.js";
46
-
47
- // Segment system (server-only)
48
- export { renderSegments } from "./segment-system.js";
49
-
50
- // Performance tracking (server-only)
51
- export { track } from "./server/context.js";
52
-
53
- // Handle API (works in both server and client contexts)
54
- export { createHandle, isHandle, type Handle } from "./handle.js";
55
-
56
- // Built-in handles
57
- export { Meta } from "./handles/meta.js";
40
+ registerLoaderById,
41
+ setLoaderImports,
42
+ } from "./server/loader-registry.js";
58
43
 
59
- // Loader registry (for GET-based loader fetching)
60
- export { registerLoaderById, setLoaderImports } from "./server/loader-registry.js";
61
-
62
- // Request context (for accessing request data in server components/actions)
44
+ // Request context creation (used by RSC handler, not user-facing)
63
45
  export {
64
- getRequestContext,
65
- requireRequestContext,
66
46
  createRequestContext,
67
- type RequestContext,
68
47
  type CreateRequestContextOptions,
69
48
  } from "./server/request-context.js";
70
49
 
71
- // Meta types
72
- export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
73
-
74
- // Middleware context types (Middleware type is exported from types.ts)
75
- export type {
76
- MiddlewareContext,
77
- CookieOptions,
78
- } from "./router/middleware.js";
79
-
80
- // Error classes and utilities
81
- export {
82
- RouteNotFoundError,
83
- DataNotFoundError,
84
- notFound,
85
- MiddlewareError,
86
- HandlerError,
87
- BuildError,
88
- InvalidHandlerError,
89
- sanitizeError,
90
- } from "./errors.js";
91
-
92
- // Component utilities
93
- export {
94
- isClientComponent,
95
- assertClientComponent,
96
- } from "./component-utils.js";
97
-
98
- // Types (re-exported for convenience - user-facing only)
99
- export type {
100
- // Configuration types
101
- RouterEnv,
102
- DefaultEnv,
103
- RouteDefinition,
104
- RouteConfig,
105
- RouteDefinitionOptions,
106
- TrailingSlashMode,
107
- // Handler types
108
- Handler, // Supports params object, path pattern, or route name
109
- HandlerContext,
110
- ExtractParams,
111
- GenericParams,
112
- // Middleware types (also exported from router/middleware.js above)
113
- Middleware, // Supports env type and optional route name for params
114
- // Revalidation types
115
- RevalidateParams,
116
- Revalidate,
117
- RouteKeys,
118
- // Loader types
119
- LoaderDefinition,
120
- LoaderFn,
121
- LoaderContext,
122
- // Error boundary types
123
- ErrorInfo,
124
- ErrorBoundaryFallbackProps,
125
- ErrorBoundaryHandler,
126
- ClientErrorBoundaryFallbackProps,
127
- // NotFound boundary types
128
- NotFoundInfo,
129
- NotFoundBoundaryFallbackProps,
130
- NotFoundBoundaryHandler,
131
- // Error handling callback types
132
- ErrorPhase,
133
- OnErrorContext,
134
- OnErrorCallback,
135
- } from "./types.js";
50
+ // Component utilities (used internally for server/client boundary checks)
51
+ export { isClientComponent, assertClientComponent } from "./component-utils.js";
package/src/ssr/index.tsx CHANGED
@@ -1,11 +1,18 @@
1
1
  import React from "react";
2
- import { initHandleDataSync } from "../browser/react/use-handle.js";
3
- import { initSegmentsSync } from "../browser/react/use-segments.js";
4
- import { initThemeConfigSync } from "../theme/theme-context.js";
2
+ import { renderSegments } from "../segment-system.js";
3
+ import { filterSegmentOrder } from "../browser/react/filter-segment-order.js";
5
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";
6
8
  import type { HandleData } from "../browser/types.js";
7
9
  import type { ErrorPhase } from "../types.js";
10
+ import type { ResolvedSegment } from "../types.js";
8
11
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
12
+ import type {
13
+ EventController,
14
+ DerivedNavigationState,
15
+ } from "../browser/event-controller.js";
9
16
 
10
17
  /**
11
18
  * Options for injectRSCPayload
@@ -26,6 +33,13 @@ interface RenderToReadableStreamOptions {
26
33
  formState?: unknown;
27
34
  }
28
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
+
29
43
  /**
30
44
  * Options for the renderHTML function
31
45
  */
@@ -42,6 +56,14 @@ export interface SSRRenderOptions {
42
56
  * Nonce for Content Security Policy (CSP)
43
57
  */
44
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;
45
67
  }
46
68
 
47
69
  /**
@@ -51,22 +73,24 @@ export interface SSRDependencies<TEnv = unknown> {
51
73
  /**
52
74
  * createFromReadableStream from @vitejs/plugin-rsc/ssr
53
75
  */
54
- createFromReadableStream: <T>(stream: ReadableStream<Uint8Array>) => Promise<T>;
76
+ createFromReadableStream: <T>(
77
+ stream: ReadableStream<Uint8Array>,
78
+ ) => Promise<T>;
55
79
 
56
80
  /**
57
81
  * renderToReadableStream from react-dom/server.edge
58
82
  */
59
83
  renderToReadableStream: (
60
84
  element: React.ReactNode,
61
- options?: RenderToReadableStreamOptions
62
- ) => Promise<ReadableStream<Uint8Array>>;
85
+ options?: RenderToReadableStreamOptions,
86
+ ) => Promise<ReactDOMReadableStream>;
63
87
 
64
88
  /**
65
89
  * injectRSCPayload from rsc-html-stream/server
66
90
  */
67
91
  injectRSCPayload: (
68
92
  rscStream: ReadableStream<Uint8Array>,
69
- options?: InjectRSCPayloadOptions
93
+ options?: InjectRSCPayloadOptions,
70
94
  ) => TransformStream<Uint8Array, Uint8Array>;
71
95
 
72
96
  /**
@@ -98,13 +122,16 @@ export interface SSRDependencies<TEnv = unknown> {
98
122
  * RSC payload type (minimal interface for SSR)
99
123
  */
100
124
  interface RscPayload {
101
- root: React.ReactNode;
102
125
  metadata?: {
126
+ segments?: ResolvedSegment[];
127
+ rootLayout?: React.ComponentType<{ children: React.ReactNode }>;
103
128
  handles?: AsyncGenerator<HandleData, void, unknown>;
104
129
  matched?: string[];
105
130
  pathname?: string;
131
+ params?: Record<string, string>;
106
132
  themeConfig?: ResolvedThemeConfig | null;
107
133
  initialTheme?: Theme;
134
+ version?: string;
108
135
  };
109
136
  }
110
137
 
@@ -113,7 +140,7 @@ interface RscPayload {
113
140
  * Used for SSR where we need to await all handle data before rendering.
114
141
  */
115
142
  async function consumeAsyncGenerator(
116
- generator: AsyncGenerator<HandleData, void, unknown>
143
+ generator: AsyncGenerator<HandleData, void, unknown>,
117
144
  ): Promise<HandleData> {
118
145
  let lastData: HandleData = {};
119
146
  for await (const data of generator) {
@@ -122,6 +149,64 @@ async function consumeAsyncGenerator(
122
149
  return lastData;
123
150
  }
124
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
+
125
210
  /**
126
211
  * Create an SSR handler that converts RSC streams to HTML.
127
212
  *
@@ -158,9 +243,9 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
158
243
  */
159
244
  return async function renderHTML(
160
245
  rscStream: ReadableStream<Uint8Array>,
161
- options?: SSRRenderOptions
246
+ options?: SSRRenderOptions,
162
247
  ): Promise<ReadableStream<Uint8Array>> {
163
- const { nonce, formState } = options ?? {};
248
+ const { nonce, formState, streamMode } = options ?? {};
164
249
 
165
250
  try {
166
251
  // Tee the stream:
@@ -171,37 +256,75 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
171
256
  // Deserialize RSC stream to React tree
172
257
  let payload: Promise<RscPayload> | undefined;
173
258
  let handlesPromise: Promise<HandleData> | undefined;
259
+ let ssrContextValue: NavigationStoreContextValue | undefined;
174
260
  function SsrRoot() {
175
261
  payload ??= createFromReadableStream<RscPayload>(rscStream1);
176
262
  const resolved = React.use(payload);
177
-
178
- // Initialize segments state before children render (for useSegments hook)
179
- initSegmentsSync(resolved.metadata?.matched, resolved.metadata?.pathname);
180
-
181
- // Initialize theme config for MetaTags to render theme script
182
263
  const themeConfig = resolved.metadata?.themeConfig ?? null;
183
- initThemeConfigSync(themeConfig);
264
+ const pathname = resolved.metadata?.pathname ?? "/";
184
265
 
185
- // Await handles and initialize state before children render
266
+ // Await handles before creating SSR event controller so hooks can
267
+ // read request-local handle data via NavigationStoreContext.
186
268
  // The handles property is an async generator that yields on each push
187
269
  // Memoize the promise since async generators can only be iterated once
270
+ let handleData: HandleData = {};
188
271
  if (resolved.metadata?.handles) {
189
272
  handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
190
- const handleData = React.use(handlesPromise);
191
- initHandleDataSync(handleData, resolved.metadata.matched);
273
+ handleData = React.use(handlesPromise);
192
274
  }
193
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
+
194
303
  // Wrap content with ThemeProvider if theme is enabled
195
- // This provides the theme context for client components that use useTheme
196
304
  if (themeConfig) {
197
- return (
198
- <ThemeProvider config={themeConfig} initialTheme={resolved.metadata?.initialTheme}>
199
- {resolved.root}
305
+ content = (
306
+ <ThemeProvider
307
+ config={themeConfig}
308
+ initialTheme={resolved.metadata?.initialTheme}
309
+ >
310
+ {content}
200
311
  </ThemeProvider>
201
312
  );
202
313
  }
203
314
 
204
- return resolved.root;
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
+ );
205
328
  }
206
329
 
207
330
  // Get bootstrap script content
@@ -216,12 +339,20 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
216
339
  nonce,
217
340
  });
218
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
+
219
349
  // Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
220
350
  return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
221
351
  } catch (error) {
222
352
  // Invoke onError callback if provided
223
353
  if (onError) {
224
- const errorObj = error instanceof Error ? error : new Error(String(error));
354
+ const errorObj =
355
+ error instanceof Error ? error : new Error(String(error));
225
356
  try {
226
357
  onError(errorObj, { phase: "rendering" });
227
358
  } catch (callbackError) {
@@ -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
+ }
@@ -11,7 +11,13 @@
11
11
  * - Handles SSR hydration by deferring system theme detection
12
12
  */
13
13
 
14
- import React, { useCallback, useEffect, useMemo, useState, useRef } from "react";
14
+ import React, {
15
+ useCallback,
16
+ useEffect,
17
+ useMemo,
18
+ useState,
19
+ useRef,
20
+ } from "react";
15
21
  import { ThemeContext } from "./theme-context.js";
16
22
  import type {
17
23
  ResolvedTheme,
@@ -44,7 +50,12 @@ function readThemeFromCookie(storageKey: string): string | null {
44
50
  for (const cookie of cookies) {
45
51
  const [name, ...rest] = cookie.trim().split("=");
46
52
  if (name === storageKey) {
47
- return decodeURIComponent(rest.join("="));
53
+ const raw = rest.join("=");
54
+ try {
55
+ return decodeURIComponent(raw);
56
+ } catch {
57
+ return raw;
58
+ }
48
59
  }
49
60
  }
50
61
  return null;
@@ -90,15 +101,13 @@ function writeThemeToStorage(storageKey: string, theme: Theme): void {
90
101
  /**
91
102
  * Apply theme to HTML element
92
103
  */
93
- function applyThemeToDocument(
94
- theme: Theme,
95
- config: ResolvedThemeConfig
96
- ): void {
104
+ function applyThemeToDocument(theme: Theme, config: ResolvedThemeConfig): void {
97
105
  if (typeof document === "undefined") return;
98
106
 
99
- const resolved = theme === "system" && config.enableSystem
100
- ? getSystemTheme()
101
- : (theme as ResolvedTheme);
107
+ const resolved =
108
+ theme === "system" && config.enableSystem
109
+ ? getSystemTheme()
110
+ : (theme as ResolvedTheme);
102
111
 
103
112
  const value = config.value[resolved] || resolved;
104
113
  const el = document.documentElement;
@@ -188,7 +197,7 @@ export function ThemeProvider({
188
197
  writeThemeToStorage(config.storageKey, newTheme);
189
198
  applyThemeToDocument(newTheme, config);
190
199
  },
191
- [config]
200
+ [config],
192
201
  );
193
202
 
194
203
  // Listen for system preference changes
@@ -227,10 +236,7 @@ export function ThemeProvider({
227
236
  if (!newTheme) return;
228
237
 
229
238
  // Validate and apply
230
- if (
231
- newTheme === "system" ||
232
- config.themes.includes(newTheme)
233
- ) {
239
+ if (newTheme === "system" || config.themes.includes(newTheme)) {
234
240
  setThemeState(newTheme as Theme);
235
241
  applyThemeToDocument(newTheme as Theme, config);
236
242
  }
@@ -280,7 +286,7 @@ export function ThemeProvider({
280
286
  themes,
281
287
  config,
282
288
  }),
283
- [theme, setTheme, resolvedTheme, systemTheme, themes, config, mounted]
289
+ [theme, setTheme, resolvedTheme, systemTheme, themes, config, mounted],
284
290
  );
285
291
 
286
292
  return (
@@ -49,13 +49,13 @@ export interface ThemeScriptProps {
49
49
  * This renders a synchronous inline script that applies the theme
50
50
  * to the HTML element before React hydration, preventing FOUC.
51
51
  */
52
- export function ThemeScript({ config, nonce }: ThemeScriptProps): React.ReactNode {
52
+ export function ThemeScript({
53
+ config,
54
+ nonce,
55
+ }: ThemeScriptProps): React.ReactNode {
53
56
  const scriptContent = generateThemeScript(config);
54
57
 
55
58
  return (
56
- <script
57
- nonce={nonce}
58
- dangerouslySetInnerHTML={{ __html: scriptContent }}
59
- />
59
+ <script nonce={nonce} dangerouslySetInnerHTML={{ __html: scriptContent }} />
60
60
  );
61
61
  }