@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,386 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ useState,
5
+ useEffect,
6
+ useCallback,
7
+ useMemo,
8
+ use,
9
+ type ReactNode,
10
+ } from "react";
11
+ import {
12
+ NavigationStoreContext,
13
+ type NavigationStoreContextValue,
14
+ } from "./context.js";
15
+ import type {
16
+ NavigationStore,
17
+ NavigationUpdate,
18
+ NavigateOptions,
19
+ NavigationBridge,
20
+ } from "../types.js";
21
+ import type { EventController } from "../event-controller.js";
22
+ import { RootErrorBoundary } from "../../root-error-boundary.js";
23
+ import type { HandleData } from "../types.js";
24
+ import { ThemeProvider } from "../../theme/ThemeProvider.js";
25
+ import { NonceContext } from "./nonce-context.js";
26
+ import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
27
+ import { cancelAllPrefetches } from "../prefetch/queue.js";
28
+
29
+ /**
30
+ * Process handles from an async generator, updating the event controller
31
+ * and cache as data streams in.
32
+ *
33
+ * This handles:
34
+ * 1. Consuming the async generator and calling setHandleData on each yield
35
+ * 2. Stopping early if user navigates away (historyKey changes)
36
+ * 3. Cleaning up stale data when generator yields nothing
37
+ * 4. Updating the cache after processing completes (if still on same page)
38
+ */
39
+ async function processHandles(
40
+ handlesGenerator: AsyncGenerator<HandleData>,
41
+ opts: {
42
+ eventController: EventController;
43
+ store: NavigationStore;
44
+ matched?: string[];
45
+ isPartial?: boolean;
46
+ historyKey: string;
47
+ },
48
+ ): Promise<void> {
49
+ const { eventController, store, matched, isPartial, historyKey } = opts;
50
+
51
+ let yieldCount = 0;
52
+ for await (const handleData of handlesGenerator) {
53
+ // Check if user navigated away before each update.
54
+ // This prevents handle data from cancelled navigations polluting
55
+ // the current route's breadcrumbs (e.g., quick popstate after clicking a link).
56
+ if (historyKey !== store.getHistoryKey()) {
57
+ console.log(
58
+ "[NavigationProvider] Stopping handle processing - user navigated away",
59
+ );
60
+ return;
61
+ }
62
+
63
+ yieldCount++;
64
+ eventController.setHandleData(handleData, matched, isPartial);
65
+ }
66
+
67
+ // Check again before final updates
68
+ if (historyKey !== store.getHistoryKey()) {
69
+ return;
70
+ }
71
+
72
+ // For partial updates where the generator yielded nothing (cached handlers),
73
+ // we still need to update the segment order to clean up stale handle data.
74
+ // This happens when navigating away from a route - the handlers for the new
75
+ // route might not push any breadcrumbs, but we still need to remove the old ones.
76
+ if (yieldCount === 0 && matched) {
77
+ eventController.setHandleData({}, matched, true);
78
+ }
79
+
80
+ // After handles processing completes, update the cache's handleData.
81
+ // This fixes a race condition where commit() caches stale handleData before
82
+ // the async handles processing completes.
83
+ // Only update if we're still on the same page (historyKey matches).
84
+ if (historyKey === store.getHistoryKey()) {
85
+ const finalHandleData = eventController.getHandleState().data;
86
+ store.updateCacheHandleData(historyKey, finalHandleData);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Props for NavigationProvider
92
+ */
93
+ export interface NavigationProviderProps {
94
+ /**
95
+ * Navigation store instance (for cache/segment management)
96
+ */
97
+ store: NavigationStore;
98
+
99
+ /**
100
+ * Event controller instance (for navigation/action state)
101
+ */
102
+ eventController: EventController;
103
+
104
+ /**
105
+ * Initial rendered tree + metadata from server payload
106
+ */
107
+ initialPayload: NavigationUpdate;
108
+
109
+ /**
110
+ * Navigation bridge for handling navigation
111
+ */
112
+ bridge: NavigationBridge;
113
+
114
+ /**
115
+ * Theme configuration (null if theme not enabled)
116
+ * When provided, wraps content in ThemeProvider
117
+ */
118
+ themeConfig?: ResolvedThemeConfig | null;
119
+
120
+ /**
121
+ * Initial theme from server (from cookie)
122
+ * Only used when themeConfig is provided
123
+ */
124
+ initialTheme?: Theme;
125
+
126
+ /**
127
+ * Whether connection warmup is enabled.
128
+ * When true, keeps TLS alive by sending HEAD requests after idle periods.
129
+ */
130
+ warmupEnabled?: boolean;
131
+
132
+ /**
133
+ * App version from server payload (stable, immutable).
134
+ * Forwarded to prefetch requests for version mismatch detection.
135
+ */
136
+ version?: string;
137
+ }
138
+
139
+ /**
140
+ * Navigation provider component
141
+ *
142
+ * Provides navigation context to the component tree and handles:
143
+ * - Providing stable store and event controller references (never re-renders consumers)
144
+ * - Subscribing to UI updates to re-render the tree
145
+ * - Providing navigate/refresh methods (delegated to bridge)
146
+ *
147
+ * State subscriptions happen via useNavigation hook (via event controller), not via context.
148
+ * This means context consumers don't re-render on state changes.
149
+ *
150
+ * @example
151
+ * ```tsx
152
+ * <NavigationProvider
153
+ * store={store}
154
+ * eventController={eventController}
155
+ * initialPayload={payload}
156
+ * bridge={navigationBridge}
157
+ * />
158
+ * ```
159
+ */
160
+ export function NavigationProvider({
161
+ store,
162
+ eventController,
163
+ initialPayload,
164
+ bridge,
165
+ themeConfig,
166
+ initialTheme,
167
+ warmupEnabled,
168
+ version,
169
+ }: NavigationProviderProps): ReactNode {
170
+ // Track current payload for rendering (this triggers re-renders)
171
+ const [payload, setPayload] = useState(initialPayload);
172
+
173
+ /**
174
+ * Navigate to a URL (delegates to bridge)
175
+ */
176
+ const navigate = useCallback(
177
+ async (url: string, options?: NavigateOptions): Promise<void> => {
178
+ await bridge.navigate(url, options);
179
+ },
180
+ [],
181
+ );
182
+
183
+ /**
184
+ * Refresh current route (delegates to bridge)
185
+ */
186
+ const refresh = useCallback(async (): Promise<void> => {
187
+ await bridge.refresh();
188
+ }, []);
189
+
190
+ // Context value is stable (store, eventController, navigate, refresh never change)
191
+ const contextValue = useMemo<NavigationStoreContextValue>(
192
+ () => ({
193
+ store,
194
+ eventController,
195
+ navigate,
196
+ refresh,
197
+ version,
198
+ }),
199
+ [],
200
+ );
201
+
202
+ // Connection warmup: keep TLS alive after idle periods.
203
+ // After 60s of no user interaction, marks connection as "cold".
204
+ // On next interaction or visibility change, sends a HEAD request to warm TLS
205
+ // before the user actually clicks a link.
206
+ useEffect(() => {
207
+ if (!warmupEnabled) return;
208
+
209
+ const IDLE_TIMEOUT = 60_000;
210
+ const DEBOUNCE_DELAY = 150;
211
+
212
+ let idleTimer: ReturnType<typeof setTimeout> | undefined;
213
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
214
+ let isCold = false;
215
+ let warmupListenersAttached = false;
216
+
217
+ function sendWarmup() {
218
+ isCold = false;
219
+ fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
220
+ }
221
+
222
+ function triggerWarmup() {
223
+ if (!isCold) return;
224
+ clearTimeout(debounceTimer);
225
+ debounceTimer = setTimeout(() => {
226
+ sendWarmup();
227
+ detachWarmupListeners();
228
+ resetIdleTimer();
229
+ }, DEBOUNCE_DELAY);
230
+ }
231
+
232
+ function onVisibilityChange() {
233
+ if (document.visibilityState === "visible" && isCold) {
234
+ triggerWarmup();
235
+ }
236
+ }
237
+
238
+ function attachWarmupListeners() {
239
+ if (warmupListenersAttached) return;
240
+ warmupListenersAttached = true;
241
+ document.addEventListener("visibilitychange", onVisibilityChange);
242
+ document.addEventListener("mousemove", triggerWarmup, { once: true });
243
+ document.addEventListener("touchstart", triggerWarmup, { once: true });
244
+ }
245
+
246
+ function detachWarmupListeners() {
247
+ warmupListenersAttached = false;
248
+ document.removeEventListener("visibilitychange", onVisibilityChange);
249
+ document.removeEventListener("mousemove", triggerWarmup);
250
+ document.removeEventListener("touchstart", triggerWarmup);
251
+ }
252
+
253
+ function markCold() {
254
+ isCold = true;
255
+ attachWarmupListeners();
256
+ }
257
+
258
+ function resetIdleTimer() {
259
+ clearTimeout(idleTimer);
260
+ isCold = false;
261
+ idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
262
+ }
263
+
264
+ // Activity events that reset the idle timer
265
+ const activityEvents = [
266
+ "mousemove",
267
+ "keydown",
268
+ "touchstart",
269
+ "scroll",
270
+ ] as const;
271
+ const activityOptions: AddEventListenerOptions = { passive: true };
272
+
273
+ for (const event of activityEvents) {
274
+ document.addEventListener(event, resetIdleTimer, activityOptions);
275
+ }
276
+
277
+ resetIdleTimer();
278
+
279
+ return () => {
280
+ clearTimeout(idleTimer);
281
+ clearTimeout(debounceTimer);
282
+ detachWarmupListeners();
283
+ for (const event of activityEvents) {
284
+ document.removeEventListener(event, resetIdleTimer);
285
+ }
286
+ };
287
+ }, [warmupEnabled]);
288
+
289
+ // Cancel speculative prefetches when navigation starts.
290
+ // Viewport/render prefetches should not compete with navigation fetches.
291
+ useEffect(() => {
292
+ let wasIdle = true;
293
+ const unsub = eventController.subscribe(() => {
294
+ const state = eventController.getState();
295
+ const isIdle = state.state === "idle" && !state.isStreaming;
296
+ if (wasIdle && !isIdle) {
297
+ cancelAllPrefetches();
298
+ }
299
+ wasIdle = isIdle;
300
+ });
301
+ return unsub;
302
+ }, [eventController]);
303
+
304
+ // Subscribe to UI updates (for re-rendering the tree)
305
+ useEffect(() => {
306
+ const unsubscribe = store.onUpdate((update) => {
307
+ setPayload({
308
+ root: update.root,
309
+ metadata: update.metadata,
310
+ });
311
+
312
+ // Update route params
313
+ eventController.setParams(update.metadata.params ?? {});
314
+
315
+ // Update handle data progressively as it streams in
316
+ if (update.metadata.handles) {
317
+ // Capture historyKey now - by the time async processing completes,
318
+ // the user might have navigated elsewhere
319
+ const historyKey = store.getHistoryKey();
320
+
321
+ processHandles(update.metadata.handles, {
322
+ eventController,
323
+ store,
324
+ matched: update.metadata.matched,
325
+ isPartial: update.metadata.isPartial,
326
+ historyKey,
327
+ }).catch((err) =>
328
+ console.error("[NavigationProvider] Error consuming handles:", err),
329
+ );
330
+ } else if (update.metadata.cachedHandleData) {
331
+ // For back/forward navigation from cache, restore the cached handleData
332
+ // This restores breadcrumbs to the exact state they were when the page was cached
333
+ eventController.setHandleData(
334
+ update.metadata.cachedHandleData,
335
+ update.metadata.matched,
336
+ false, // full replace - restore entire cached state
337
+ );
338
+ } else if (update.metadata.matched) {
339
+ // For cached navigations without handleData, update segmentOrder to clean up stale data
340
+ eventController.setHandleData(
341
+ {}, // Empty data - all existing data not in matched will be cleaned up
342
+ update.metadata.matched,
343
+ true, // partial update - will clean up segments not in matched
344
+ );
345
+ }
346
+ });
347
+
348
+ return unsubscribe;
349
+ }, []);
350
+
351
+ // Handle promise case - use() will suspend until resolved
352
+ const root =
353
+ payload.root instanceof Promise ? use(payload.root) : payload.root;
354
+
355
+ // Wrap content in RootErrorBoundary to catch:
356
+ // 1. Errors from NetworkErrorThrower (rendered during network failures)
357
+ // 2. Client component errors that occur before/outside the segment tree's error boundary
358
+ // 3. Errors during promise resolution or navigation state updates
359
+ // This acts as a safety net - the segment tree has its own RootErrorBoundary that
360
+ // catches most errors, but this outer boundary catches anything that slips through.
361
+
362
+ // Build the content tree
363
+ let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
364
+
365
+ // Wrap with ThemeProvider when theme is enabled
366
+ if (themeConfig) {
367
+ content = (
368
+ <ThemeProvider config={themeConfig} initialTheme={initialTheme}>
369
+ {content}
370
+ </ThemeProvider>
371
+ );
372
+ }
373
+
374
+ // Match SSR tree shape: NonceContext.Provider is always present so
375
+ // hydration sees the same component tree. Value is undefined on the
376
+ // client — CSP nonces are a server-side HTML concern.
377
+ content = (
378
+ <NonceContext.Provider value={undefined}>{content}</NonceContext.Provider>
379
+ );
380
+
381
+ return (
382
+ <NavigationStoreContext.Provider value={contextValue}>
383
+ {content}
384
+ </NavigationStoreContext.Provider>
385
+ );
386
+ }
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { initScrollRestoration } from "../scroll-restoration.js";
5
+
6
+ /**
7
+ * Props for ScrollRestoration component
8
+ */
9
+ export interface ScrollRestorationProps {
10
+ /**
11
+ * Custom function to determine the scroll restoration key.
12
+ * By default, uses a unique key per history entry (location.key).
13
+ *
14
+ * Return location.pathname to restore scroll based on path
15
+ * (useful for keeping scroll position on the same page).
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // Restore based on pathname (same URL = same scroll)
20
+ * <ScrollRestoration
21
+ * getKey={(location) => location.pathname}
22
+ * />
23
+ *
24
+ * // Restore based on unique history entry (default)
25
+ * <ScrollRestoration
26
+ * getKey={(location) => location.key}
27
+ * />
28
+ * ```
29
+ */
30
+ getKey?: (location: {
31
+ pathname: string;
32
+ search: string;
33
+ hash: string;
34
+ key: string;
35
+ }) => string;
36
+ }
37
+
38
+ /**
39
+ * ScrollRestoration component
40
+ *
41
+ * Enables scroll position restoration across navigations:
42
+ * - Saves scroll positions to sessionStorage
43
+ * - Restores scroll on back/forward navigation
44
+ * - Scrolls to top on new navigation
45
+ * - Supports hash link scrolling
46
+ *
47
+ * Should be rendered once in your app, typically in the root layout.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * // In your root layout
52
+ * export default function RootLayout({ children }) {
53
+ * return (
54
+ * <html>
55
+ * <body>
56
+ * <ScrollRestoration />
57
+ * {children}
58
+ * </body>
59
+ * </html>
60
+ * );
61
+ * }
62
+ * ```
63
+ */
64
+ export function ScrollRestoration({ getKey }: ScrollRestorationProps) {
65
+ useEffect(() => {
66
+ const cleanup = initScrollRestoration({ getKey });
67
+ return cleanup;
68
+ }, [getKey]);
69
+
70
+ // This component doesn't render anything
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Hook to initialize scroll restoration
76
+ *
77
+ * Alternative to the ScrollRestoration component for more control.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * function App() {
82
+ * useScrollRestoration();
83
+ * return <div>...</div>;
84
+ * }
85
+ * ```
86
+ */
87
+ export function useScrollRestoration(options?: {
88
+ getKey?: ScrollRestorationProps["getKey"];
89
+ }): void {
90
+ useEffect(() => {
91
+ const cleanup = initScrollRestoration({ getKey: options?.getKey });
92
+ return cleanup;
93
+ }, [options?.getKey]);
94
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { createContext, type Context } from "react";
4
+ import type { NavigationStore, NavigateOptions } from "../types.js";
5
+ import type { EventController } from "../event-controller.js";
6
+
7
+ /**
8
+ * Navigation context value provided by NavigationProvider
9
+ *
10
+ * This context provides a STABLE reference to the store, event controller, and methods.
11
+ * The store itself never changes, so context consumers don't re-render
12
+ * when navigation state changes.
13
+ *
14
+ * Components subscribe to state changes via eventController.subscribe() in useNavigation.
15
+ */
16
+ export interface NavigationStoreContextValue {
17
+ /**
18
+ * The navigation store instance (stable reference)
19
+ * Used for cache/segment management
20
+ */
21
+ store: NavigationStore;
22
+
23
+ /**
24
+ * The event controller instance (stable reference)
25
+ * Used for navigation/action state
26
+ */
27
+ eventController: EventController;
28
+
29
+ /**
30
+ * Navigate to a new URL
31
+ *
32
+ * @param url - The URL to navigate to
33
+ * @param options - Navigation options (replace, scroll)
34
+ * @returns Promise that resolves when navigation is complete
35
+ */
36
+ navigate: (url: string, options?: NavigateOptions) => Promise<void>;
37
+
38
+ /**
39
+ * Refresh the current route
40
+ *
41
+ * @returns Promise that resolves when refresh is complete
42
+ */
43
+ refresh: () => Promise<void>;
44
+
45
+ /**
46
+ * App version from server payload (stable, immutable).
47
+ * Used in prefetch requests for version mismatch detection.
48
+ */
49
+ version: string | undefined;
50
+ }
51
+
52
+ /**
53
+ * React context for navigation store
54
+ *
55
+ * Provides stable reference to the store - does NOT re-render on state changes.
56
+ * Use useNavigation hook for reactive state access.
57
+ */
58
+ export const NavigationStoreContext: Context<NavigationStoreContextValue | null> =
59
+ createContext<NavigationStoreContextValue | null>(null);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Filter segment IDs to only include routes and layouts.
3
+ * Excludes parallels (contain .@) and loaders (contain D followed by digit).
4
+ */
5
+ export function filterSegmentOrder(matched: string[]): string[] {
6
+ return matched.filter((id) => {
7
+ if (id.includes(".@")) return false;
8
+ if (/D\d+\./.test(id)) return false;
9
+ return true;
10
+ });
11
+ }
@@ -0,0 +1,52 @@
1
+ // React exports for browser navigation
2
+
3
+ // Hook with Zustand-style selectors
4
+ export { useNavigation } from "./use-navigation.js";
5
+
6
+ // Router actions hook (stable reference, no re-renders)
7
+ export { useRouter } from "./use-router.js";
8
+
9
+ // URL hooks
10
+ export { usePathname } from "./use-pathname.js";
11
+ export { useSearchParams } from "./use-search-params.js";
12
+ export { useParams } from "./use-params.js";
13
+
14
+ // Action state tracking hook
15
+ export { useAction, type TrackedActionState } from "./use-action.js";
16
+
17
+ // Segments state hook
18
+ export { useSegments, type SegmentsState } from "./use-segments.js";
19
+
20
+ // Handle data hook
21
+ export { useHandle } from "./use-handle.js";
22
+
23
+ // Client cache controls hook
24
+ export {
25
+ useClientCache,
26
+ type ClientCacheControls,
27
+ } from "./use-client-cache.js";
28
+
29
+ // Provider
30
+ export {
31
+ NavigationProvider,
32
+ type NavigationProviderProps,
33
+ } from "./NavigationProvider.js";
34
+
35
+ // Context (for advanced usage)
36
+ export {
37
+ NavigationStoreContext,
38
+ type NavigationStoreContextValue,
39
+ } from "./context.js";
40
+
41
+ // Link component
42
+ export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
43
+
44
+ // Link status hook
45
+ export { useLinkStatus, type LinkStatus } from "./use-link-status.js";
46
+
47
+ // Scroll restoration
48
+ export {
49
+ ScrollRestoration,
50
+ useScrollRestoration,
51
+ type ScrollRestorationProps,
52
+ } from "./ScrollRestoration.js";