@rangojs/router 0.0.0-experimental.0f44aca1

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 +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -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 +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -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 +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -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 +538 -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 +469 -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 +540 -0
  105. package/src/cache/cf/index.ts +25 -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 +43 -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 +275 -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 +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -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 +192 -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 +748 -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 +316 -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 +1239 -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 +289 -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 +1002 -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 +235 -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 +914 -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 +102 -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 +110 -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 +131 -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 +365 -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 +254 -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 +510 -0
  298. package/src/vite/router-discovery.ts +785 -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,246 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Component to render collected meta descriptors in the document head.
5
+ *
6
+ * Supports both sync and async meta descriptors. Async descriptors
7
+ * (Promise<MetaDescriptorBase>) are resolved using React's use() hook.
8
+ *
9
+ * When theme is enabled in the router config, MetaTags also renders
10
+ * the theme initialization script to prevent FOUC (flash of unstyled content).
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function RootLayout() {
15
+ * return (
16
+ * <html lang="en" suppressHydrationWarning>
17
+ * <head>
18
+ * <MetaTags />
19
+ * </head>
20
+ * <body>...</body>
21
+ * </html>
22
+ * );
23
+ * }
24
+ * ```
25
+ */
26
+
27
+ import { use } from "react";
28
+ import { useHandle } from "../browser/react/use-handle.js";
29
+ import { Meta } from "./meta.js";
30
+ import type { MetaDescriptor, MetaDescriptorBase } from "../router/types.js";
31
+ import { useThemeContext } from "../theme/theme-context.js";
32
+ import { generateThemeScript } from "../theme/theme-script.js";
33
+ import { useNonce } from "../browser/react/nonce-context.js";
34
+
35
+ // Type guards for MetaDescriptorBase variants
36
+ function hasCharSet(d: MetaDescriptorBase): d is { charSet: "utf-8" } {
37
+ return "charSet" in d && d.charSet === "utf-8";
38
+ }
39
+
40
+ function hasTitle(d: MetaDescriptorBase): d is { title: string } {
41
+ return "title" in d && typeof (d as { title?: unknown }).title === "string";
42
+ }
43
+
44
+ function hasNameContent(
45
+ d: MetaDescriptorBase,
46
+ ): d is { name: string; content: string } {
47
+ return (
48
+ "name" in d &&
49
+ "content" in d &&
50
+ typeof (d as { name?: unknown }).name === "string" &&
51
+ typeof (d as { content?: unknown }).content === "string"
52
+ );
53
+ }
54
+
55
+ function hasPropertyContent(
56
+ d: MetaDescriptorBase,
57
+ ): d is { property: string; content: string } {
58
+ return (
59
+ "property" in d &&
60
+ "content" in d &&
61
+ typeof (d as { property?: unknown }).property === "string" &&
62
+ typeof (d as { content?: unknown }).content === "string"
63
+ );
64
+ }
65
+
66
+ function hasHttpEquivContent(
67
+ d: MetaDescriptorBase,
68
+ ): d is { httpEquiv: string; content: string } {
69
+ return (
70
+ "httpEquiv" in d &&
71
+ "content" in d &&
72
+ typeof (d as { httpEquiv?: unknown }).httpEquiv === "string" &&
73
+ typeof (d as { content?: unknown }).content === "string"
74
+ );
75
+ }
76
+
77
+ function hasScriptLdJson(
78
+ d: MetaDescriptorBase,
79
+ ): d is { "script:ld+json": object } {
80
+ return "script:ld+json" in d;
81
+ }
82
+
83
+ function hasTagName(
84
+ d: MetaDescriptorBase,
85
+ ): d is { tagName: "meta" | "link"; [name: string]: string } {
86
+ return (
87
+ "tagName" in d &&
88
+ ((d as { tagName?: unknown }).tagName === "meta" ||
89
+ (d as { tagName?: unknown }).tagName === "link")
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Check if a value is a Promise.
95
+ */
96
+ function isPromise(value: unknown): value is Promise<unknown> {
97
+ return value !== null && typeof value === "object" && "then" in value;
98
+ }
99
+
100
+ /**
101
+ * Render a single meta descriptor as a React element.
102
+ */
103
+ function renderMetaDescriptor(
104
+ descriptor: MetaDescriptorBase,
105
+ index: number,
106
+ ): React.ReactNode {
107
+ // charset
108
+ if (hasCharSet(descriptor)) {
109
+ return <meta key="charSet" charSet={descriptor.charSet} />;
110
+ }
111
+
112
+ // title
113
+ if (hasTitle(descriptor)) {
114
+ return <title key="title">{descriptor.title}</title>;
115
+ }
116
+
117
+ // name + content (description, viewport, etc.)
118
+ if (hasNameContent(descriptor)) {
119
+ return (
120
+ <meta
121
+ key={`name-${descriptor.name}`}
122
+ name={descriptor.name}
123
+ content={descriptor.content}
124
+ />
125
+ );
126
+ }
127
+
128
+ // property + content (Open Graph, etc.)
129
+ if (hasPropertyContent(descriptor)) {
130
+ return (
131
+ <meta
132
+ key={`property-${descriptor.property}`}
133
+ property={descriptor.property}
134
+ content={descriptor.content}
135
+ />
136
+ );
137
+ }
138
+
139
+ // http-equiv + content
140
+ if (hasHttpEquivContent(descriptor)) {
141
+ return (
142
+ <meta
143
+ key={`httpEquiv-${descriptor.httpEquiv}`}
144
+ httpEquiv={descriptor.httpEquiv}
145
+ content={descriptor.content}
146
+ />
147
+ );
148
+ }
149
+
150
+ // JSON-LD structured data
151
+ if (hasScriptLdJson(descriptor)) {
152
+ const json = JSON.stringify(descriptor["script:ld+json"]);
153
+ return (
154
+ <script
155
+ key={`ld-json-${index}`}
156
+ type="application/ld+json"
157
+ dangerouslySetInnerHTML={{ __html: json }}
158
+ />
159
+ );
160
+ }
161
+
162
+ // Custom tagName (meta or link with arbitrary attributes)
163
+ if (hasTagName(descriptor)) {
164
+ const { tagName, ...rest } = descriptor;
165
+ if (tagName === "link") {
166
+ return (
167
+ <link
168
+ key={`link-${index}`}
169
+ {...(rest as React.LinkHTMLAttributes<HTMLLinkElement>)}
170
+ />
171
+ );
172
+ }
173
+ if (tagName === "meta") {
174
+ return (
175
+ <meta
176
+ key={`meta-${index}`}
177
+ {...(rest as React.MetaHTMLAttributes<HTMLMetaElement>)}
178
+ />
179
+ );
180
+ }
181
+ }
182
+
183
+ // Fallback: treat as meta attributes
184
+ return (
185
+ <meta
186
+ key={`meta-fallback-${index}`}
187
+ {...(descriptor as React.MetaHTMLAttributes<HTMLMetaElement>)}
188
+ />
189
+ );
190
+ }
191
+
192
+ /**
193
+ * Wrapper component to resolve a Promise<MetaDescriptorBase> using use().
194
+ */
195
+ function AsyncMetaTag({
196
+ promise,
197
+ index,
198
+ }: {
199
+ promise: Promise<MetaDescriptorBase>;
200
+ index: number;
201
+ }): React.ReactNode {
202
+ const resolved = use(promise);
203
+ return renderMetaDescriptor(resolved, index);
204
+ }
205
+
206
+ /**
207
+ * Renders all collected meta descriptors from route handlers.
208
+ *
209
+ * Place this component inside the `<head>` element of your document.
210
+ * It will automatically update when meta descriptors change during navigation.
211
+ *
212
+ * When theme is enabled in router config, also renders the theme initialization
213
+ * script to prevent FOUC (flash of unstyled content).
214
+ *
215
+ * Async meta descriptors (Promise<MetaDescriptorBase>) are resolved using
216
+ * React's use() hook. RSC streaming handles the Promise resolution.
217
+ */
218
+ export function MetaTags(): React.ReactNode {
219
+ const descriptors = useHandle(Meta) as MetaDescriptor[];
220
+ const themeConfig = useThemeContext()?.config ?? null;
221
+ const nonce = useNonce();
222
+
223
+ return (
224
+ <>
225
+ {/* Theme script must be first to prevent FOUC */}
226
+ {themeConfig && (
227
+ <script
228
+ nonce={nonce}
229
+ dangerouslySetInnerHTML={{ __html: generateThemeScript(themeConfig) }}
230
+ />
231
+ )}
232
+ {descriptors.map((descriptor, index) => {
233
+ if (isPromise(descriptor)) {
234
+ return (
235
+ <AsyncMetaTag
236
+ key={`async-${index}`}
237
+ promise={descriptor}
238
+ index={index}
239
+ />
240
+ );
241
+ }
242
+ return renderMetaDescriptor(descriptor, index);
243
+ })}
244
+ </>
245
+ );
246
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Built-in Breadcrumbs handle for accumulating breadcrumb items across route segments.
3
+ *
4
+ * Each layout/route pushes breadcrumb items via `ctx.use(Breadcrumbs)`.
5
+ * Items are collected in parent-to-child order with automatic deduplication
6
+ * by `href` (last item for each href wins).
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // In route handler
11
+ * route("/blog/:slug", (ctx) => {
12
+ * const breadcrumb = ctx.use(Breadcrumbs);
13
+ * breadcrumb({ label: "Blog", href: "/blog" });
14
+ * breadcrumb({ label: post.title, href: `/blog/${ctx.params.slug}` });
15
+ * });
16
+ *
17
+ * // In client component (consume with useHandle)
18
+ * const crumbs = useHandle(Breadcrumbs);
19
+ * crumbs.map((c) => <a href={c.href}>{c.label}</a>);
20
+ * ```
21
+ */
22
+
23
+ import type { ReactNode } from "react";
24
+ import { createHandle, type Handle } from "../handle.js";
25
+
26
+ /**
27
+ * A single breadcrumb item.
28
+ *
29
+ * @property label - Display text for the breadcrumb
30
+ * @property href - URL the breadcrumb links to
31
+ * @property content - Optional extra content (sync or async) rendered alongside the label
32
+ */
33
+ export interface BreadcrumbItem {
34
+ label: string;
35
+ href: string;
36
+ content?: ReactNode | Promise<ReactNode>;
37
+ }
38
+
39
+ /**
40
+ * Collect function for Breadcrumbs handle.
41
+ * Flattens segments in parent-to-child order with deduplication by href
42
+ * (last item for each href wins).
43
+ */
44
+ function collectBreadcrumbs(segments: BreadcrumbItem[][]): BreadcrumbItem[] {
45
+ const all = segments.flat();
46
+ const seen = new Map<string, number>();
47
+
48
+ for (let i = 0; i < all.length; i++) {
49
+ seen.set(all[i].href, i);
50
+ }
51
+
52
+ // Return items in order, keeping only the last occurrence per href
53
+ return all.filter((item, index) => seen.get(item.href) === index);
54
+ }
55
+
56
+ /**
57
+ * Built-in handle for accumulating breadcrumb navigation items.
58
+ *
59
+ * Use `ctx.use(Breadcrumbs)` in route handlers to push breadcrumb items.
60
+ * Use `useHandle(Breadcrumbs)` in client components to consume them.
61
+ */
62
+ export const Breadcrumbs: Handle<BreadcrumbItem, BreadcrumbItem[]> =
63
+ createHandle<BreadcrumbItem, BreadcrumbItem[]>(
64
+ collectBreadcrumbs,
65
+ "__rsc_router_breadcrumbs__",
66
+ );
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Built-in handles for rsc-router.
3
+ */
4
+
5
+ export { Meta } from "./meta.ts";
6
+ export { MetaTags } from "./MetaTags.tsx";
7
+ export { Breadcrumbs, type BreadcrumbItem } from "./breadcrumbs.ts";
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Built-in Meta handle for managing document metadata across route segments.
3
+ *
4
+ * Provides automatic deduplication: later routes override earlier ones
5
+ * for the same meta key (title, name, property, etc.)
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // In route handler
10
+ * route("product/:id", (ctx) => {
11
+ * const meta = ctx.use(Meta);
12
+ * meta({ title: "Product Details" });
13
+ * meta({ name: "description", content: "..." });
14
+ * meta({ property: "og:title", content: "..." });
15
+ * });
16
+ *
17
+ * // In layout (renders the collected meta tags)
18
+ * function RootLayout() {
19
+ * return (
20
+ * <html>
21
+ * <head>
22
+ * <MetaTags />
23
+ * </head>
24
+ * ...
25
+ * </html>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+
31
+ import { createHandle, type Handle } from "../handle.js";
32
+ import type {
33
+ MetaDescriptor,
34
+ TitleDescriptor,
35
+ UnsetDescriptor,
36
+ } from "../router/types.js";
37
+
38
+ /**
39
+ * Type guard for unset descriptor
40
+ */
41
+ function isUnsetDescriptor(
42
+ descriptor: MetaDescriptor,
43
+ ): descriptor is UnsetDescriptor {
44
+ return (
45
+ typeof descriptor === "object" &&
46
+ descriptor !== null &&
47
+ "unset" in descriptor &&
48
+ typeof (descriptor as UnsetDescriptor).unset === "string"
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Type guard for title descriptor (any form)
54
+ */
55
+ function isTitleDescriptor(
56
+ descriptor: MetaDescriptor,
57
+ ): descriptor is { title: TitleDescriptor } {
58
+ return (
59
+ typeof descriptor === "object" &&
60
+ descriptor !== null &&
61
+ "title" in descriptor
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Type guard for title template descriptor
67
+ */
68
+ function isTitleTemplate(
69
+ title: TitleDescriptor,
70
+ ): title is { template: string; default: string } {
71
+ return (
72
+ typeof title === "object" &&
73
+ title !== null &&
74
+ "template" in title &&
75
+ "default" in title
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Type guard for absolute title descriptor
81
+ */
82
+ function isAbsoluteTitle(
83
+ title: TitleDescriptor,
84
+ ): title is { absolute: string } {
85
+ return typeof title === "object" && title !== null && "absolute" in title;
86
+ }
87
+
88
+ /**
89
+ * Get a unique key for a meta descriptor for deduplication.
90
+ * Returns undefined for descriptors that shouldn't be deduplicated.
91
+ */
92
+ function getMetaKey(descriptor: MetaDescriptor): string | undefined {
93
+ // Skip unset descriptors - they are processed separately
94
+ if (isUnsetDescriptor(descriptor)) {
95
+ return undefined;
96
+ }
97
+ if ("charSet" in descriptor) {
98
+ return "charSet";
99
+ }
100
+ if ("title" in descriptor) {
101
+ return "title";
102
+ }
103
+ if ("name" in descriptor && "content" in descriptor) {
104
+ return `name:${descriptor.name}`;
105
+ }
106
+ if ("property" in descriptor && "content" in descriptor) {
107
+ return `property:${descriptor.property}`;
108
+ }
109
+ if ("httpEquiv" in descriptor && "content" in descriptor) {
110
+ return `httpEquiv:${descriptor.httpEquiv}`;
111
+ }
112
+ if ("script:ld+json" in descriptor) {
113
+ // JSON-LD scripts can have multiple, don't dedupe by default
114
+ return undefined;
115
+ }
116
+ if ("tagName" in descriptor) {
117
+ // For link tags, dedupe by rel if present
118
+ if (descriptor.tagName === "link" && "rel" in descriptor) {
119
+ // Some link rels should be unique (canonical), others not (stylesheet)
120
+ const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
121
+ if (uniqueRels.includes(descriptor.rel as string)) {
122
+ return `link:${descriptor.rel}`;
123
+ }
124
+ }
125
+ return undefined;
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ /**
131
+ * Default meta descriptors included automatically.
132
+ * These can be overridden by route handlers.
133
+ */
134
+ const defaultMetaDescriptors: MetaDescriptor[] = [
135
+ { charSet: "utf-8" },
136
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
137
+ ];
138
+
139
+ /**
140
+ * Helper to add or replace a descriptor in the result array
141
+ */
142
+ function addOrReplace(
143
+ result: MetaDescriptor[],
144
+ keyToIndex: Map<string, number>,
145
+ descriptor: MetaDescriptor,
146
+ key: string | undefined,
147
+ ): void {
148
+ if (key !== undefined && keyToIndex.has(key)) {
149
+ result[keyToIndex.get(key)!] = descriptor;
150
+ } else {
151
+ if (key !== undefined) {
152
+ keyToIndex.set(key, result.length);
153
+ }
154
+ result.push(descriptor);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Helper to update indices after removing an element
160
+ */
161
+ function updateIndicesAfterRemoval(
162
+ keyToIndex: Map<string, number>,
163
+ removedIndex: number,
164
+ ): void {
165
+ for (const [key, index] of keyToIndex) {
166
+ if (index > removedIndex) {
167
+ keyToIndex.set(key, index - 1);
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Collect function for Meta handle.
174
+ * Includes default meta descriptors, then deduplicates by key with later routes overriding earlier ones.
175
+ * Supports title templates, absolute titles, and unset descriptors.
176
+ */
177
+ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
178
+ const result: MetaDescriptor[] = [];
179
+ const keyToIndex = new Map<string, number>();
180
+ let titleTemplate: string | undefined;
181
+
182
+ // Add defaults first so they can be overridden
183
+ for (const descriptor of defaultMetaDescriptors) {
184
+ const key = getMetaKey(descriptor);
185
+ if (key !== undefined) {
186
+ keyToIndex.set(key, result.length);
187
+ }
188
+ result.push(descriptor);
189
+ }
190
+
191
+ for (const descriptors of segments) {
192
+ for (const descriptor of descriptors) {
193
+ // Handle unset descriptors
194
+ if (isUnsetDescriptor(descriptor)) {
195
+ const keyToRemove = descriptor.unset;
196
+ if (keyToIndex.has(keyToRemove)) {
197
+ const idx = keyToIndex.get(keyToRemove)!;
198
+ result.splice(idx, 1);
199
+ keyToIndex.delete(keyToRemove);
200
+ updateIndicesAfterRemoval(keyToIndex, idx);
201
+ }
202
+ continue;
203
+ }
204
+
205
+ // Handle title descriptors with template/absolute support
206
+ if (isTitleDescriptor(descriptor)) {
207
+ const titleValue = descriptor.title;
208
+
209
+ if (isTitleTemplate(titleValue)) {
210
+ // Store template for subsequent title descriptors in child segments
211
+ titleTemplate = titleValue.template;
212
+ // Set the default title
213
+ addOrReplace(
214
+ result,
215
+ keyToIndex,
216
+ { title: titleValue.default },
217
+ "title",
218
+ );
219
+ continue;
220
+ }
221
+
222
+ if (isAbsoluteTitle(titleValue)) {
223
+ // Absolute title bypasses any template
224
+ addOrReplace(
225
+ result,
226
+ keyToIndex,
227
+ { title: titleValue.absolute },
228
+ "title",
229
+ );
230
+ continue;
231
+ }
232
+
233
+ // String title - apply template if one exists
234
+ const finalTitle = titleTemplate
235
+ ? titleTemplate.replace("%s", titleValue as string)
236
+ : titleValue;
237
+ addOrReplace(
238
+ result,
239
+ keyToIndex,
240
+ { title: finalTitle as string },
241
+ "title",
242
+ );
243
+ continue;
244
+ }
245
+
246
+ // Handle all other descriptors
247
+ const key = getMetaKey(descriptor);
248
+ addOrReplace(result, keyToIndex, descriptor, key);
249
+ }
250
+ }
251
+
252
+ return result;
253
+ }
254
+
255
+ /**
256
+ * Built-in handle for managing document metadata.
257
+ *
258
+ * Use `ctx.use(Meta)` in route handlers to push meta descriptors.
259
+ * Use `<MetaTags />` component to render them in the document head.
260
+ */
261
+ export const Meta: Handle<MetaDescriptor, MetaDescriptor[]> = createHandle<
262
+ MetaDescriptor,
263
+ MetaDescriptor[]
264
+ >(collectMeta, "__rsc_router_meta__");