vinext 0.0.38 → 0.0.39

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 (226) hide show
  1. package/README.md +33 -20
  2. package/dist/build/nitro-route-rules.d.ts +50 -0
  3. package/dist/build/nitro-route-rules.js +81 -0
  4. package/dist/build/nitro-route-rules.js.map +1 -0
  5. package/dist/build/precompress.d.ts +17 -0
  6. package/dist/build/precompress.js +102 -0
  7. package/dist/build/precompress.js.map +1 -0
  8. package/dist/build/prerender.d.ts +27 -22
  9. package/dist/build/prerender.js +17 -17
  10. package/dist/build/prerender.js.map +1 -1
  11. package/dist/build/report.d.ts +3 -4
  12. package/dist/build/report.js.map +1 -1
  13. package/dist/build/run-prerender.d.ts +3 -4
  14. package/dist/build/run-prerender.js.map +1 -1
  15. package/dist/build/standalone.d.ts +32 -0
  16. package/dist/build/standalone.js +199 -0
  17. package/dist/build/standalone.js.map +1 -0
  18. package/dist/build/static-export.d.ts +17 -29
  19. package/dist/build/static-export.js.map +1 -1
  20. package/dist/check.d.ts +4 -4
  21. package/dist/check.js +1 -1
  22. package/dist/check.js.map +1 -1
  23. package/dist/cli.js +31 -4
  24. package/dist/cli.js.map +1 -1
  25. package/dist/client/instrumentation-client.d.ts +2 -2
  26. package/dist/client/instrumentation-client.js.map +1 -1
  27. package/dist/client/vinext-next-data.d.ts +5 -8
  28. package/dist/cloudflare/index.js +1 -1
  29. package/dist/cloudflare/kv-cache-handler.d.ts +5 -3
  30. package/dist/cloudflare/kv-cache-handler.js +1 -1
  31. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  32. package/dist/cloudflare/tpr.d.ts +35 -27
  33. package/dist/cloudflare/tpr.js +36 -12
  34. package/dist/cloudflare/tpr.js.map +1 -1
  35. package/dist/config/config-matchers.d.ts +2 -2
  36. package/dist/config/config-matchers.js +1 -1
  37. package/dist/config/config-matchers.js.map +1 -1
  38. package/dist/config/dotenv.d.ts +4 -4
  39. package/dist/config/dotenv.js.map +1 -1
  40. package/dist/config/next-config.d.ts +40 -61
  41. package/dist/config/next-config.js +5 -4
  42. package/dist/config/next-config.js.map +1 -1
  43. package/dist/deploy.d.ts +25 -41
  44. package/dist/deploy.js +1 -1
  45. package/dist/deploy.js.map +1 -1
  46. package/dist/entries/app-rsc-entry.d.ts +6 -10
  47. package/dist/entries/app-rsc-entry.js +4 -6
  48. package/dist/entries/app-rsc-entry.js.map +1 -1
  49. package/dist/entries/pages-server-entry.js +1 -3
  50. package/dist/entries/pages-server-entry.js.map +1 -1
  51. package/dist/index.d.ts +23 -33
  52. package/dist/index.js +165 -84
  53. package/dist/index.js.map +1 -1
  54. package/dist/init.d.ts +14 -26
  55. package/dist/init.js +8 -2
  56. package/dist/init.js.map +1 -1
  57. package/dist/plugins/client-reference-dedup.js.map +1 -1
  58. package/dist/plugins/fix-use-server-closure-collision.js.map +1 -1
  59. package/dist/plugins/fonts.d.ts +18 -1
  60. package/dist/plugins/fonts.js +107 -8
  61. package/dist/plugins/fonts.js.map +1 -1
  62. package/dist/plugins/optimize-imports.d.ts +2 -2
  63. package/dist/plugins/optimize-imports.js +4 -4
  64. package/dist/plugins/optimize-imports.js.map +1 -1
  65. package/dist/plugins/server-externals-manifest.d.ts +27 -0
  66. package/dist/plugins/server-externals-manifest.js +76 -0
  67. package/dist/plugins/server-externals-manifest.js.map +1 -0
  68. package/dist/routing/app-router.d.ts +29 -55
  69. package/dist/routing/app-router.js.map +1 -1
  70. package/dist/routing/file-matcher.d.ts +2 -2
  71. package/dist/routing/file-matcher.js.map +1 -1
  72. package/dist/routing/pages-router.d.ts +6 -11
  73. package/dist/routing/pages-router.js.map +1 -1
  74. package/dist/routing/route-trie.d.ts +2 -2
  75. package/dist/routing/route-trie.js.map +1 -1
  76. package/dist/server/api-handler.js.map +1 -1
  77. package/dist/server/app-browser-entry.js +270 -39
  78. package/dist/server/app-browser-entry.js.map +1 -1
  79. package/dist/server/app-browser-stream.d.ts +6 -6
  80. package/dist/server/app-browser-stream.js.map +1 -1
  81. package/dist/server/app-page-boundary-render.d.ts +8 -8
  82. package/dist/server/app-page-boundary-render.js +2 -2
  83. package/dist/server/app-page-boundary-render.js.map +1 -1
  84. package/dist/server/app-page-boundary.d.ts +13 -11
  85. package/dist/server/app-page-boundary.js +1 -1
  86. package/dist/server/app-page-boundary.js.map +1 -1
  87. package/dist/server/app-page-cache.d.ts +10 -10
  88. package/dist/server/app-page-cache.js.map +1 -1
  89. package/dist/server/app-page-execution.d.ts +10 -10
  90. package/dist/server/app-page-execution.js.map +1 -1
  91. package/dist/server/app-page-probe.d.ts +2 -2
  92. package/dist/server/app-page-probe.js.map +1 -1
  93. package/dist/server/app-page-render.d.ts +4 -4
  94. package/dist/server/app-page-render.js.map +1 -1
  95. package/dist/server/app-page-request.d.ts +12 -12
  96. package/dist/server/app-page-request.js.map +1 -1
  97. package/dist/server/app-page-response.d.ts +18 -18
  98. package/dist/server/app-page-response.js.map +1 -1
  99. package/dist/server/app-page-stream.d.ts +18 -18
  100. package/dist/server/app-page-stream.js.map +1 -1
  101. package/dist/server/app-route-handler-cache.d.ts +2 -2
  102. package/dist/server/app-route-handler-cache.js.map +1 -1
  103. package/dist/server/app-route-handler-execution.d.ts +6 -6
  104. package/dist/server/app-route-handler-execution.js.map +1 -1
  105. package/dist/server/app-route-handler-policy.d.ts +8 -8
  106. package/dist/server/app-route-handler-policy.js.map +1 -1
  107. package/dist/server/app-route-handler-response.d.ts +6 -6
  108. package/dist/server/app-route-handler-response.js.map +1 -1
  109. package/dist/server/app-route-handler-runtime.d.ts +4 -4
  110. package/dist/server/app-route-handler-runtime.js.map +1 -1
  111. package/dist/server/app-ssr-entry.d.ts +4 -4
  112. package/dist/server/app-ssr-entry.js.map +1 -1
  113. package/dist/server/app-ssr-stream.d.ts +2 -2
  114. package/dist/server/app-ssr-stream.js +1 -3
  115. package/dist/server/app-ssr-stream.js.map +1 -1
  116. package/dist/server/dev-module-runner.d.ts +2 -2
  117. package/dist/server/dev-module-runner.js.map +1 -1
  118. package/dist/server/dev-server.js +5 -7
  119. package/dist/server/dev-server.js.map +1 -1
  120. package/dist/server/image-optimization.d.ts +7 -12
  121. package/dist/server/image-optimization.js.map +1 -1
  122. package/dist/server/instrumentation.d.ts +8 -12
  123. package/dist/server/instrumentation.js +1 -1
  124. package/dist/server/instrumentation.js.map +1 -1
  125. package/dist/server/isr-cache.d.ts +2 -2
  126. package/dist/server/isr-cache.js.map +1 -1
  127. package/dist/server/metadata-routes.d.ts +14 -19
  128. package/dist/server/metadata-routes.js.map +1 -1
  129. package/dist/server/middleware.d.ts +9 -17
  130. package/dist/server/middleware.js +1 -1
  131. package/dist/server/middleware.js.map +1 -1
  132. package/dist/server/pages-api-route.d.ts +6 -6
  133. package/dist/server/pages-api-route.js.map +1 -1
  134. package/dist/server/pages-i18n.d.ts +4 -4
  135. package/dist/server/pages-i18n.js.map +1 -1
  136. package/dist/server/pages-node-compat.d.ts +10 -10
  137. package/dist/server/pages-node-compat.js.map +1 -1
  138. package/dist/server/pages-page-data.d.ts +22 -22
  139. package/dist/server/pages-page-data.js.map +1 -1
  140. package/dist/server/pages-page-response.d.ts +8 -8
  141. package/dist/server/pages-page-response.js.map +1 -1
  142. package/dist/server/prod-server.d.ts +20 -15
  143. package/dist/server/prod-server.js +170 -53
  144. package/dist/server/prod-server.js.map +1 -1
  145. package/dist/server/seed-cache.js.map +1 -1
  146. package/dist/server/static-file-cache.d.ts +57 -0
  147. package/dist/server/static-file-cache.js +219 -0
  148. package/dist/server/static-file-cache.js.map +1 -0
  149. package/dist/shims/app.d.ts +2 -2
  150. package/dist/shims/cache-runtime.d.ts +6 -9
  151. package/dist/shims/cache-runtime.js.map +1 -1
  152. package/dist/shims/cache.d.ts +28 -31
  153. package/dist/shims/cache.js.map +1 -1
  154. package/dist/shims/config.d.ts +2 -2
  155. package/dist/shims/config.js.map +1 -1
  156. package/dist/shims/dynamic.d.ts +2 -2
  157. package/dist/shims/dynamic.js +5 -7
  158. package/dist/shims/dynamic.js.map +1 -1
  159. package/dist/shims/error-boundary.d.ts +7 -7
  160. package/dist/shims/error-boundary.js.map +1 -1
  161. package/dist/shims/error.d.ts +2 -2
  162. package/dist/shims/error.js.map +1 -1
  163. package/dist/shims/fetch-cache.d.ts +4 -4
  164. package/dist/shims/fetch-cache.js.map +1 -1
  165. package/dist/shims/font-google-base.d.ts +4 -4
  166. package/dist/shims/font-google-base.js.map +1 -1
  167. package/dist/shims/font-local.d.ts +6 -6
  168. package/dist/shims/font-local.js.map +1 -1
  169. package/dist/shims/form.d.ts +4 -8
  170. package/dist/shims/form.js +4 -6
  171. package/dist/shims/form.js.map +1 -1
  172. package/dist/shims/head-state.d.ts +2 -2
  173. package/dist/shims/head-state.js.map +1 -1
  174. package/dist/shims/head.d.ts +2 -2
  175. package/dist/shims/head.js +18 -20
  176. package/dist/shims/head.js.map +1 -1
  177. package/dist/shims/headers.d.ts +4 -4
  178. package/dist/shims/headers.js.map +1 -1
  179. package/dist/shims/i18n-context.d.ts +2 -2
  180. package/dist/shims/i18n-context.js.map +1 -1
  181. package/dist/shims/i18n-state.d.ts +2 -2
  182. package/dist/shims/i18n-state.js.map +1 -1
  183. package/dist/shims/image-config.d.ts +2 -2
  184. package/dist/shims/image-config.js.map +1 -1
  185. package/dist/shims/image.d.ts +5 -6
  186. package/dist/shims/image.js.map +1 -1
  187. package/dist/shims/internal/app-router-context.d.ts +6 -6
  188. package/dist/shims/internal/app-router-context.js.map +1 -1
  189. package/dist/shims/internal/utils.d.ts +2 -2
  190. package/dist/shims/internal/utils.js.map +1 -1
  191. package/dist/shims/layout-segment-context.d.ts +12 -5
  192. package/dist/shims/layout-segment-context.js +9 -4
  193. package/dist/shims/layout-segment-context.js.map +1 -1
  194. package/dist/shims/legacy-image.d.ts +5 -8
  195. package/dist/shims/legacy-image.js.map +1 -1
  196. package/dist/shims/link.d.ts +21 -31
  197. package/dist/shims/link.js +4 -58
  198. package/dist/shims/link.js.map +1 -1
  199. package/dist/shims/metadata.d.ts +23 -31
  200. package/dist/shims/metadata.js.map +1 -1
  201. package/dist/shims/navigation-state.d.ts +2 -2
  202. package/dist/shims/navigation-state.js.map +1 -1
  203. package/dist/shims/navigation.d.ts +102 -17
  204. package/dist/shims/navigation.js +359 -113
  205. package/dist/shims/navigation.js.map +1 -1
  206. package/dist/shims/request-context.d.ts +2 -2
  207. package/dist/shims/request-context.js.map +1 -1
  208. package/dist/shims/router-state.d.ts +4 -4
  209. package/dist/shims/router-state.js.map +1 -1
  210. package/dist/shims/router.d.ts +28 -47
  211. package/dist/shims/router.js.map +1 -1
  212. package/dist/shims/script.d.ts +16 -31
  213. package/dist/shims/script.js.map +1 -1
  214. package/dist/shims/server.d.ts +10 -10
  215. package/dist/shims/server.js.map +1 -1
  216. package/dist/shims/unified-request-context.d.ts +3 -5
  217. package/dist/shims/unified-request-context.js.map +1 -1
  218. package/dist/shims/web-vitals.d.ts +2 -2
  219. package/dist/shims/web-vitals.js.map +1 -1
  220. package/dist/utils/lazy-chunks.d.ts +34 -0
  221. package/dist/utils/lazy-chunks.js +50 -0
  222. package/dist/utils/lazy-chunks.js.map +1 -0
  223. package/dist/utils/vinext-root.d.ts +24 -0
  224. package/dist/utils/vinext-root.js +31 -0
  225. package/dist/utils/vinext-root.js.map +1 -0
  226. package/package.json +1 -1
@@ -27,18 +27,19 @@ const ServerInsertedHTMLContext = getServerInsertedHTMLContext();
27
27
  function getLayoutSegmentContext() {
28
28
  if (typeof React$1.createContext !== "function") return null;
29
29
  const globalState = globalThis;
30
- if (!globalState[_LAYOUT_SEGMENT_CTX_KEY]) globalState[_LAYOUT_SEGMENT_CTX_KEY] = React$1.createContext([]);
30
+ if (!globalState[_LAYOUT_SEGMENT_CTX_KEY]) globalState[_LAYOUT_SEGMENT_CTX_KEY] = React$1.createContext({ children: [] });
31
31
  return globalState[_LAYOUT_SEGMENT_CTX_KEY] ?? null;
32
32
  }
33
33
  /**
34
- * Read the child segments below the current layout from context.
35
- * Returns [] if no context is available (RSC environment, outside React tree).
34
+ * Read the child segments for a parallel route below the current layout.
35
+ * Returns [] if no context is available (RSC environment, outside React tree)
36
+ * or if the requested key is not present in the segment map.
36
37
  */
37
- function useChildSegments() {
38
+ function useChildSegments(parallelRoutesKey = "children") {
38
39
  const ctx = getLayoutSegmentContext();
39
40
  if (!ctx) return [];
40
41
  try {
41
- return React$1.useContext(ctx);
42
+ return React$1.useContext(ctx)[parallelRoutesKey] ?? [];
42
43
  } catch {
43
44
  return [];
44
45
  }
@@ -130,91 +131,300 @@ function getPrefetchedUrls() {
130
131
  return window.__VINEXT_RSC_PREFETCHED_URLS__;
131
132
  }
132
133
  /**
133
- * Store a prefetched RSC response in the cache.
134
- * Enforces a maximum cache size to prevent unbounded memory growth on
135
- * link-heavy pages.
134
+ * Evict prefetch cache entries if at capacity.
135
+ * First sweeps expired entries, then falls back to FIFO eviction.
136
136
  */
137
- function storePrefetchResponse(rscUrl, response) {
137
+ function evictPrefetchCacheIfNeeded() {
138
138
  const cache = getPrefetchCache();
139
+ if (cache.size < 50) return;
139
140
  const now = Date.now();
140
- if (cache.size >= 50) {
141
- const prefetched = getPrefetchedUrls();
142
- for (const [key, entry] of cache) if (now - entry.timestamp >= 3e4) {
143
- cache.delete(key);
144
- prefetched.delete(key);
145
- }
141
+ const prefetched = getPrefetchedUrls();
142
+ for (const [key, entry] of cache) if (now - entry.timestamp >= 3e4) {
143
+ cache.delete(key);
144
+ prefetched.delete(key);
146
145
  }
147
- if (cache.size >= 50) {
146
+ while (cache.size >= 50) {
148
147
  const oldest = cache.keys().next().value;
149
148
  if (oldest !== void 0) {
150
149
  cache.delete(oldest);
151
- getPrefetchedUrls().delete(oldest);
152
- }
150
+ prefetched.delete(oldest);
151
+ } else break;
153
152
  }
154
- cache.set(rscUrl, {
155
- response,
156
- timestamp: now
153
+ }
154
+ /**
155
+ * Store a prefetched RSC response in the cache by snapshotting it to an
156
+ * ArrayBuffer. The snapshot completes asynchronously; during that window
157
+ * the entry is marked `pending` so consumePrefetchResponse() will skip it
158
+ * (the caller falls back to a fresh fetch, which is acceptable).
159
+ *
160
+ * Prefer prefetchRscResponse() for new call-sites — it handles the full
161
+ * prefetch lifecycle including dedup. storePrefetchResponse() is kept for
162
+ * backward compatibility and test helpers.
163
+ *
164
+ * NB: Caller is responsible for managing getPrefetchedUrls() — this
165
+ * function only stores the response in the prefetch cache.
166
+ */
167
+ function storePrefetchResponse(rscUrl, response) {
168
+ evictPrefetchCacheIfNeeded();
169
+ const entry = { timestamp: Date.now() };
170
+ entry.pending = snapshotRscResponse(response).then((snapshot) => {
171
+ entry.snapshot = snapshot;
172
+ }).catch(() => {
173
+ getPrefetchCache().delete(rscUrl);
174
+ }).finally(() => {
175
+ entry.pending = void 0;
157
176
  });
177
+ getPrefetchCache().set(rscUrl, entry);
158
178
  }
159
- const _listeners = /* @__PURE__ */ new Set();
160
- function notifyListeners() {
161
- for (const fn of _listeners) fn();
179
+ /**
180
+ * Snapshot an RSC response to an ArrayBuffer for caching and replay.
181
+ * Consumes the response body and stores it with content-type and URL metadata.
182
+ */
183
+ async function snapshotRscResponse(response) {
184
+ return {
185
+ buffer: await response.arrayBuffer(),
186
+ contentType: response.headers.get("content-type") ?? "text/x-component",
187
+ paramsHeader: response.headers.get("X-Vinext-Params"),
188
+ url: response.url
189
+ };
190
+ }
191
+ /**
192
+ * Reconstruct a Response from a cached RSC snapshot.
193
+ * Creates a new Response with the original ArrayBuffer so createFromFetch
194
+ * can consume the stream from scratch.
195
+ *
196
+ * NOTE: The reconstructed Response always has `url === ""` — the Response
197
+ * constructor does not accept a `url` option, and `response.url` is read-only
198
+ * set by the fetch infrastructure. Callers that need the original URL should
199
+ * read it from `cached.url` directly rather than from the restored Response.
200
+ *
201
+ * @param copy - When true (default), copies the ArrayBuffer so the cached
202
+ * snapshot remains replayable (needed for the visited-response cache).
203
+ * Pass false for single-consumption paths (e.g. prefetch cache entries
204
+ * that are deleted after consumption) to avoid the extra allocation.
205
+ */
206
+ function restoreRscResponse(cached, copy = true) {
207
+ const headers = new Headers({ "content-type": cached.contentType });
208
+ if (cached.paramsHeader != null) headers.set("X-Vinext-Params", cached.paramsHeader);
209
+ return new Response(copy ? cached.buffer.slice(0) : cached.buffer, {
210
+ status: 200,
211
+ headers
212
+ });
213
+ }
214
+ /**
215
+ * Prefetch an RSC response and snapshot it for later consumption.
216
+ * Stores the in-flight promise so immediate clicks can await it instead
217
+ * of firing a duplicate fetch.
218
+ * Enforces a maximum cache size to prevent unbounded memory growth on
219
+ * link-heavy pages.
220
+ */
221
+ function prefetchRscResponse(rscUrl, fetchPromise) {
222
+ const cache = getPrefetchCache();
223
+ const prefetched = getPrefetchedUrls();
224
+ const entry = { timestamp: Date.now() };
225
+ entry.pending = fetchPromise.then(async (response) => {
226
+ if (response.ok) entry.snapshot = await snapshotRscResponse(response);
227
+ else {
228
+ prefetched.delete(rscUrl);
229
+ cache.delete(rscUrl);
230
+ }
231
+ }).catch(() => {
232
+ prefetched.delete(rscUrl);
233
+ cache.delete(rscUrl);
234
+ }).finally(() => {
235
+ entry.pending = void 0;
236
+ });
237
+ cache.set(rscUrl, entry);
238
+ evictPrefetchCacheIfNeeded();
239
+ }
240
+ /**
241
+ * Consume a prefetched response for a given rscUrl.
242
+ * Only returns settled (non-pending) snapshots synchronously.
243
+ * Returns null if the entry is still in flight or doesn't exist.
244
+ */
245
+ function consumePrefetchResponse(rscUrl) {
246
+ const cache = getPrefetchCache();
247
+ const entry = cache.get(rscUrl);
248
+ if (!entry) return null;
249
+ if (entry.pending) return null;
250
+ cache.delete(rscUrl);
251
+ getPrefetchedUrls().delete(rscUrl);
252
+ if (entry.snapshot) {
253
+ if (Date.now() - entry.timestamp >= 3e4) return null;
254
+ return entry.snapshot;
255
+ }
256
+ return null;
257
+ }
258
+ const _CLIENT_NAV_STATE_KEY = Symbol.for("vinext.clientNavigationState");
259
+ function getClientNavigationState() {
260
+ if (isServer) return null;
261
+ const globalState = window;
262
+ globalState[_CLIENT_NAV_STATE_KEY] ??= {
263
+ listeners: /* @__PURE__ */ new Set(),
264
+ cachedSearch: window.location.search,
265
+ cachedReadonlySearchParams: new ReadonlyURLSearchParams(window.location.search),
266
+ cachedPathname: stripBasePath(window.location.pathname, __basePath),
267
+ clientParams: {},
268
+ clientParamsJson: "{}",
269
+ pendingClientParams: null,
270
+ pendingClientParamsJson: null,
271
+ originalPushState: window.history.pushState.bind(window.history),
272
+ originalReplaceState: window.history.replaceState.bind(window.history),
273
+ patchInstalled: false,
274
+ hasPendingNavigationUpdate: false,
275
+ suppressUrlNotifyCount: 0,
276
+ navigationSnapshotActiveCount: 0
277
+ };
278
+ return globalState[_CLIENT_NAV_STATE_KEY];
279
+ }
280
+ function notifyNavigationListeners() {
281
+ const state = getClientNavigationState();
282
+ if (!state) return;
283
+ for (const fn of state.listeners) fn();
162
284
  }
163
- let _cachedSearch = !isServer ? window.location.search : "";
164
- let _cachedReadonlySearchParams = new ReadonlyURLSearchParams(_cachedSearch);
165
285
  let _cachedEmptyServerSearchParams = null;
166
- let _cachedPathname = !isServer ? stripBasePath(window.location.pathname, __basePath) : "/";
286
+ /**
287
+ * Get cached pathname snapshot for useSyncExternalStore.
288
+ * Note: Returns cached value from ClientNavigationState, not live window.location.
289
+ * The cache is updated by syncCommittedUrlStateFromLocation() after navigation commits.
290
+ * This ensures referential stability and prevents infinite re-renders.
291
+ * External pushState/replaceState while URL notifications are suppressed won't
292
+ * be visible until the next commit.
293
+ */
167
294
  function getPathnameSnapshot() {
168
- const current = stripBasePath(window.location.pathname, __basePath);
169
- if (current !== _cachedPathname) _cachedPathname = current;
170
- return _cachedPathname;
295
+ return getClientNavigationState()?.cachedPathname ?? "/";
171
296
  }
297
+ let _cachedEmptyClientSearchParams = null;
298
+ /**
299
+ * Get cached search params snapshot for useSyncExternalStore.
300
+ * Note: Returns cached value from ClientNavigationState, not live window.location.search.
301
+ * The cache is updated by syncCommittedUrlStateFromLocation() after navigation commits.
302
+ * This ensures referential stability and prevents infinite re-renders.
303
+ * External pushState/replaceState while URL notifications are suppressed won't
304
+ * be visible until the next commit.
305
+ */
172
306
  function getSearchParamsSnapshot() {
173
- const current = window.location.search;
174
- if (current !== _cachedSearch) {
175
- _cachedSearch = current;
176
- _cachedReadonlySearchParams = new ReadonlyURLSearchParams(current);
307
+ const cached = getClientNavigationState()?.cachedReadonlySearchParams;
308
+ if (cached) return cached;
309
+ if (_cachedEmptyClientSearchParams === null) _cachedEmptyClientSearchParams = new ReadonlyURLSearchParams();
310
+ return _cachedEmptyClientSearchParams;
311
+ }
312
+ function syncCommittedUrlStateFromLocation() {
313
+ const state = getClientNavigationState();
314
+ if (!state) return false;
315
+ let changed = false;
316
+ const pathname = stripBasePath(window.location.pathname, __basePath);
317
+ if (pathname !== state.cachedPathname) {
318
+ state.cachedPathname = pathname;
319
+ changed = true;
320
+ }
321
+ const search = window.location.search;
322
+ if (search !== state.cachedSearch) {
323
+ state.cachedSearch = search;
324
+ state.cachedReadonlySearchParams = new ReadonlyURLSearchParams(search);
325
+ changed = true;
177
326
  }
178
- return _cachedReadonlySearchParams;
327
+ return changed;
179
328
  }
180
329
  function getServerSearchParamsSnapshot() {
181
330
  const ctx = _getServerContext();
182
- if (ctx != null) {
183
- const searchParams = ctx.searchParams;
184
- if (ctx[_READONLY_SEARCH_PARAMS_SOURCE] !== searchParams) {
185
- ctx[_READONLY_SEARCH_PARAMS_SOURCE] = searchParams;
186
- ctx[_READONLY_SEARCH_PARAMS] = new ReadonlyURLSearchParams(searchParams);
187
- }
188
- return ctx[_READONLY_SEARCH_PARAMS];
331
+ if (!ctx) {
332
+ if (_cachedEmptyServerSearchParams === null) _cachedEmptyServerSearchParams = new ReadonlyURLSearchParams();
333
+ return _cachedEmptyServerSearchParams;
189
334
  }
190
- if (_cachedEmptyServerSearchParams === null) _cachedEmptyServerSearchParams = new ReadonlyURLSearchParams();
191
- return _cachedEmptyServerSearchParams;
335
+ const source = ctx.searchParams;
336
+ const cached = ctx[_READONLY_SEARCH_PARAMS];
337
+ const cachedSource = ctx[_READONLY_SEARCH_PARAMS_SOURCE];
338
+ if (cached && cachedSource === source) return cached;
339
+ const readonly = new ReadonlyURLSearchParams(source);
340
+ ctx[_READONLY_SEARCH_PARAMS] = readonly;
341
+ ctx[_READONLY_SEARCH_PARAMS_SOURCE] = source;
342
+ return readonly;
343
+ }
344
+ /**
345
+ * Mark a navigation snapshot as active. Called before startTransition
346
+ * in renderNavigationPayload. While active, hooks prefer the snapshot
347
+ * context value over useSyncExternalStore. Uses a counter (not boolean)
348
+ * to handle overlapping navigations — rapid clicks can interleave
349
+ * activate/deactivate if multiple transitions are in flight.
350
+ */
351
+ function activateNavigationSnapshot() {
352
+ const state = getClientNavigationState();
353
+ if (state) state.navigationSnapshotActiveCount++;
192
354
  }
193
355
  const _EMPTY_PARAMS = {};
194
- let _clientParams = _EMPTY_PARAMS;
195
- let _clientParamsJson = "{}";
356
+ const _CLIENT_NAV_RENDER_CTX_KEY = Symbol.for("vinext.clientNavigationRenderContext");
357
+ function getClientNavigationRenderContext() {
358
+ if (typeof React$1.createContext !== "function") return null;
359
+ const globalState = globalThis;
360
+ if (!globalState[_CLIENT_NAV_RENDER_CTX_KEY]) globalState[_CLIENT_NAV_RENDER_CTX_KEY] = React$1.createContext(null);
361
+ return globalState[_CLIENT_NAV_RENDER_CTX_KEY] ?? null;
362
+ }
363
+ function useClientNavigationRenderSnapshot() {
364
+ const ctx = getClientNavigationRenderContext();
365
+ if (!ctx || typeof React$1.useContext !== "function") return null;
366
+ try {
367
+ return React$1.useContext(ctx);
368
+ } catch {
369
+ return null;
370
+ }
371
+ }
372
+ function createClientNavigationRenderSnapshot(href, params) {
373
+ const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
374
+ const url = new URL(href, origin);
375
+ return {
376
+ pathname: stripBasePath(url.pathname, __basePath),
377
+ searchParams: new ReadonlyURLSearchParams(url.search),
378
+ params
379
+ };
380
+ }
381
+ let _fallbackClientParams = _EMPTY_PARAMS;
382
+ let _fallbackClientParamsJson = "{}";
196
383
  function setClientParams(params) {
384
+ const state = getClientNavigationState();
385
+ if (!state) {
386
+ const json = JSON.stringify(params);
387
+ if (json !== _fallbackClientParamsJson) {
388
+ _fallbackClientParams = params;
389
+ _fallbackClientParamsJson = json;
390
+ }
391
+ return;
392
+ }
393
+ const json = JSON.stringify(params);
394
+ if (json !== state.clientParamsJson) {
395
+ state.clientParams = params;
396
+ state.clientParamsJson = json;
397
+ state.pendingClientParams = null;
398
+ state.pendingClientParamsJson = null;
399
+ notifyNavigationListeners();
400
+ }
401
+ }
402
+ function replaceClientParamsWithoutNotify(params) {
403
+ const state = getClientNavigationState();
404
+ if (!state) return;
197
405
  const json = JSON.stringify(params);
198
- if (json !== _clientParamsJson) {
199
- _clientParams = params;
200
- _clientParamsJson = json;
201
- notifyListeners();
406
+ if (json !== state.clientParamsJson && json !== state.pendingClientParamsJson) {
407
+ state.pendingClientParams = params;
408
+ state.pendingClientParamsJson = json;
409
+ state.hasPendingNavigationUpdate = true;
202
410
  }
203
411
  }
204
412
  /** Get the current client params (for testing referential stability). */
205
413
  function getClientParams() {
206
- return _clientParams;
414
+ return getClientNavigationState()?.clientParams ?? _fallbackClientParams;
207
415
  }
208
416
  function getClientParamsSnapshot() {
209
- return _clientParams;
417
+ return getClientNavigationState()?.clientParams ?? _EMPTY_PARAMS;
210
418
  }
211
419
  function getServerParamsSnapshot() {
212
420
  return _getServerContext()?.params ?? _EMPTY_PARAMS;
213
421
  }
214
422
  function subscribeToNavigation(cb) {
215
- _listeners.add(cb);
423
+ const state = getClientNavigationState();
424
+ if (!state) return () => {};
425
+ state.listeners.add(cb);
216
426
  return () => {
217
- _listeners.delete(cb);
427
+ state.listeners.delete(cb);
218
428
  };
219
429
  }
220
430
  /**
@@ -223,21 +433,30 @@ function subscribeToNavigation(cb) {
223
433
  */
224
434
  function usePathname() {
225
435
  if (isServer) return _getServerContext()?.pathname ?? "/";
226
- return React$1.useSyncExternalStore(subscribeToNavigation, getPathnameSnapshot, () => _getServerContext()?.pathname ?? "/");
436
+ const renderSnapshot = useClientNavigationRenderSnapshot();
437
+ const pathname = React$1.useSyncExternalStore(subscribeToNavigation, getPathnameSnapshot, () => _getServerContext()?.pathname ?? "/");
438
+ if (renderSnapshot && (getClientNavigationState()?.navigationSnapshotActiveCount ?? 0) > 0) return renderSnapshot.pathname;
439
+ return pathname;
227
440
  }
228
441
  /**
229
442
  * Returns the current search params as a read-only URLSearchParams.
230
443
  */
231
444
  function useSearchParams() {
232
445
  if (isServer) return getServerSearchParamsSnapshot();
233
- return React$1.useSyncExternalStore(subscribeToNavigation, getSearchParamsSnapshot, getServerSearchParamsSnapshot);
446
+ const renderSnapshot = useClientNavigationRenderSnapshot();
447
+ const searchParams = React$1.useSyncExternalStore(subscribeToNavigation, getSearchParamsSnapshot, getServerSearchParamsSnapshot);
448
+ if (renderSnapshot && (getClientNavigationState()?.navigationSnapshotActiveCount ?? 0) > 0) return renderSnapshot.searchParams;
449
+ return searchParams;
234
450
  }
235
451
  /**
236
452
  * Returns the dynamic params for the current route.
237
453
  */
238
454
  function useParams() {
239
455
  if (isServer) return _getServerContext()?.params ?? _EMPTY_PARAMS;
240
- return React$1.useSyncExternalStore(subscribeToNavigation, getClientParamsSnapshot, getServerParamsSnapshot);
456
+ const renderSnapshot = useClientNavigationRenderSnapshot();
457
+ const params = React$1.useSyncExternalStore(subscribeToNavigation, getClientParamsSnapshot, getServerParamsSnapshot);
458
+ if (renderSnapshot && (getClientNavigationState()?.navigationSnapshotActiveCount ?? 0) > 0) return renderSnapshot.params;
459
+ return params;
241
460
  }
242
461
  /**
243
462
  * Check if a href is an external URL (any URL scheme per RFC 3986, or protocol-relative).
@@ -254,7 +473,7 @@ function isHashOnlyChange(href) {
254
473
  try {
255
474
  const current = new URL(window.location.href);
256
475
  const next = new URL(href, window.location.href);
257
- return current.pathname === next.pathname && current.search === next.search && next.hash !== "";
476
+ return stripBasePath(current.pathname, __basePath) === stripBasePath(next.pathname, __basePath) && current.search === next.search && next.hash !== "";
258
477
  } catch {
259
478
  return false;
260
479
  }
@@ -271,25 +490,52 @@ function scrollToHash(hash) {
271
490
  const element = document.getElementById(id);
272
491
  if (element) element.scrollIntoView({ behavior: "auto" });
273
492
  }
274
- /**
275
- * Reference to the native history.replaceState before patching.
276
- * Used internally to avoid triggering the interception for internal operations
277
- * (e.g. saving scroll position shouldn't cause re-renders).
278
- * Captured before the history method patching at the bottom of this module.
279
- */
280
- const _nativeReplaceState = !isServer ? window.history.replaceState.bind(window.history) : null;
493
+ function withSuppressedUrlNotifications(fn) {
494
+ const state = getClientNavigationState();
495
+ if (!state) return fn();
496
+ state.suppressUrlNotifyCount += 1;
497
+ try {
498
+ return fn();
499
+ } finally {
500
+ state.suppressUrlNotifyCount -= 1;
501
+ }
502
+ }
503
+ function commitClientNavigationState() {
504
+ if (isServer) return;
505
+ const state = getClientNavigationState();
506
+ if (!state) return;
507
+ if (state.navigationSnapshotActiveCount > 0) state.navigationSnapshotActiveCount -= 1;
508
+ const urlChanged = syncCommittedUrlStateFromLocation();
509
+ if (state.pendingClientParams !== null && state.pendingClientParamsJson !== null) {
510
+ state.clientParams = state.pendingClientParams;
511
+ state.clientParamsJson = state.pendingClientParamsJson;
512
+ state.pendingClientParams = null;
513
+ state.pendingClientParamsJson = null;
514
+ }
515
+ const shouldNotify = urlChanged || state.hasPendingNavigationUpdate;
516
+ state.hasPendingNavigationUpdate = false;
517
+ if (shouldNotify) notifyNavigationListeners();
518
+ }
519
+ function pushHistoryStateWithoutNotify(data, unused, url) {
520
+ withSuppressedUrlNotifications(() => {
521
+ getClientNavigationState()?.originalPushState.call(window.history, data, unused, url);
522
+ });
523
+ }
524
+ function replaceHistoryStateWithoutNotify(data, unused, url) {
525
+ withSuppressedUrlNotifications(() => {
526
+ getClientNavigationState()?.originalReplaceState.call(window.history, data, unused, url);
527
+ });
528
+ }
281
529
  /**
282
530
  * Save the current scroll position into the current history state.
283
531
  * Called before every navigation to enable scroll restoration on back/forward.
284
532
  *
285
- * Uses _nativeReplaceState to avoid triggering the history.replaceState
286
- * interception (which would cause spurious re-renders from notifyListeners).
533
+ * Uses replaceHistoryStateWithoutNotify to avoid triggering the patched
534
+ * history.replaceState interception (which would cause spurious re-renders).
287
535
  */
288
536
  function saveScrollPosition() {
289
- if (!_nativeReplaceState) return;
290
- const state = window.history.state ?? {};
291
- _nativeReplaceState.call(window.history, {
292
- ...state,
537
+ replaceHistoryStateWithoutNotify({
538
+ ...window.history.state ?? {},
293
539
  __vinext_scrollX: window.scrollX,
294
540
  __vinext_scrollY: window.scrollY
295
541
  }, "");
@@ -306,7 +552,8 @@ function saveScrollPosition() {
306
552
  * This handler fires before the browser entry's popstate handler (because
307
553
  * navigation.ts is loaded before hydration completes), so we defer via a
308
554
  * microtask to give the browser entry handler a chance to set
309
- * __VINEXT_RSC_PENDING__ first.
555
+ * __VINEXT_RSC_PENDING__. Promise.resolve() schedules a microtask
556
+ * that runs after all synchronous event listeners have completed.
310
557
  */
311
558
  function restoreScrollPosition(state) {
312
559
  if (state && typeof state === "object" && "__vinext_scrollY" in state) {
@@ -327,7 +574,7 @@ function restoreScrollPosition(state) {
327
574
  /**
328
575
  * Navigate to a URL, handling external URLs, hash-only changes, and RSC navigation.
329
576
  */
330
- async function navigateImpl(href, mode, scroll) {
577
+ async function navigateClientSide(href, mode, scroll) {
331
578
  let normalizedHref = href;
332
579
  if (isExternalUrl(href)) {
333
580
  const localPath = toSameOriginAppPath(href, __basePath);
@@ -343,29 +590,31 @@ async function navigateImpl(href, mode, scroll) {
343
590
  if (mode === "push") saveScrollPosition();
344
591
  if (isHashOnlyChange(fullHref)) {
345
592
  const hash = fullHref.includes("#") ? fullHref.slice(fullHref.indexOf("#")) : "";
346
- if (mode === "replace") window.history.replaceState(null, "", fullHref);
347
- else window.history.pushState(null, "", fullHref);
348
- notifyListeners();
593
+ if (mode === "replace") replaceHistoryStateWithoutNotify(null, "", fullHref);
594
+ else pushHistoryStateWithoutNotify(null, "", fullHref);
595
+ commitClientNavigationState();
349
596
  if (scroll) scrollToHash(hash);
350
597
  return;
351
598
  }
352
599
  const hashIdx = fullHref.indexOf("#");
353
600
  const hash = hashIdx !== -1 ? fullHref.slice(hashIdx) : "";
354
- if (mode === "replace") window.history.replaceState(null, "", fullHref);
355
- else window.history.pushState(null, "", fullHref);
356
- notifyListeners();
357
- if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") await window.__VINEXT_RSC_NAVIGATE__(fullHref);
601
+ if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") await window.__VINEXT_RSC_NAVIGATE__(fullHref, 0, "navigate", mode);
602
+ else {
603
+ if (mode === "replace") replaceHistoryStateWithoutNotify(null, "", fullHref);
604
+ else pushHistoryStateWithoutNotify(null, "", fullHref);
605
+ commitClientNavigationState();
606
+ }
358
607
  if (scroll) if (hash) scrollToHash(hash);
359
608
  else window.scrollTo(0, 0);
360
609
  }
361
610
  const _appRouter = {
362
611
  push(href, options) {
363
612
  if (isServer) return;
364
- navigateImpl(href, "push", options?.scroll !== false);
613
+ navigateClientSide(href, "push", options?.scroll !== false);
365
614
  },
366
615
  replace(href, options) {
367
616
  if (isServer) return;
368
- navigateImpl(href, "replace", options?.scroll !== false);
617
+ navigateClientSide(href, "replace", options?.scroll !== false);
369
618
  },
370
619
  back() {
371
620
  if (isServer) return;
@@ -377,7 +626,7 @@ const _appRouter = {
377
626
  },
378
627
  refresh() {
379
628
  if (isServer) return;
380
- if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") window.__VINEXT_RSC_NAVIGATE__(window.location.href);
629
+ if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") window.__VINEXT_RSC_NAVIGATE__(window.location.href, 0, "refresh");
381
630
  },
382
631
  prefetch(href) {
383
632
  if (isServer) return;
@@ -385,16 +634,11 @@ const _appRouter = {
385
634
  const prefetched = getPrefetchedUrls();
386
635
  if (prefetched.has(rscUrl)) return;
387
636
  prefetched.add(rscUrl);
388
- fetch(rscUrl, {
637
+ prefetchRscResponse(rscUrl, fetch(rscUrl, {
389
638
  headers: { Accept: "text/x-component" },
390
639
  credentials: "include",
391
640
  priority: "low"
392
- }).then((response) => {
393
- if (response.ok) storePrefetchResponse(rscUrl, response);
394
- else prefetched.delete(rscUrl);
395
- }).catch(() => {
396
- prefetched.delete(rscUrl);
397
- });
641
+ }));
398
642
  }
399
643
  };
400
644
  /**
@@ -417,23 +661,21 @@ function useRouter() {
417
661
  *
418
662
  * @param parallelRoutesKey - Which parallel route to read (default: "children")
419
663
  */
420
- function useSelectedLayoutSegment(_parallelRoutesKey) {
421
- const segments = useSelectedLayoutSegments(_parallelRoutesKey);
664
+ function useSelectedLayoutSegment(parallelRoutesKey) {
665
+ const segments = useSelectedLayoutSegments(parallelRoutesKey);
422
666
  return segments.length > 0 ? segments[0] : null;
423
667
  }
424
668
  /**
425
669
  * Returns all active segments below the layout where it's called.
426
670
  *
427
671
  * Each layout in the App Router tree wraps its children with a
428
- * LayoutSegmentProvider whose value is the remaining route tree segments
429
- * (including route groups, with dynamic params resolved to actual values
430
- * and catch-all segments joined with "/"). This hook reads those segments
431
- * directly from context.
672
+ * LayoutSegmentProvider whose value is a map of parallel route key to
673
+ * segment arrays. The "children" key is the default parallel route.
432
674
  *
433
675
  * @param parallelRoutesKey - Which parallel route to read (default: "children")
434
676
  */
435
- function useSelectedLayoutSegments(_parallelRoutesKey) {
436
- return useChildSegments();
677
+ function useSelectedLayoutSegments(parallelRoutesKey) {
678
+ return useChildSegments(parallelRoutesKey);
437
679
  }
438
680
  /**
439
681
  * useServerInsertedHTML — inject HTML during SSR from client components.
@@ -561,22 +803,26 @@ function unauthorized() {
561
803
  throw new VinextNavigationError("NEXT_UNAUTHORIZED", `${HTTP_ERROR_FALLBACK_ERROR_CODE};401`);
562
804
  }
563
805
  if (!isServer) {
564
- window.addEventListener("popstate", (event) => {
565
- notifyListeners();
566
- restoreScrollPosition(event.state);
567
- });
568
- const originalPushState = window.history.pushState.bind(window.history);
569
- const originalReplaceState = window.history.replaceState.bind(window.history);
570
- window.history.pushState = function patchedPushState(data, unused, url) {
571
- originalPushState(data, unused, url);
572
- notifyListeners();
573
- };
574
- window.history.replaceState = function patchedReplaceState(data, unused, url) {
575
- originalReplaceState(data, unused, url);
576
- notifyListeners();
577
- };
806
+ const state = getClientNavigationState();
807
+ if (state && !state.patchInstalled) {
808
+ state.patchInstalled = true;
809
+ window.addEventListener("popstate", (event) => {
810
+ if (typeof window.__VINEXT_RSC_NAVIGATE__ !== "function") {
811
+ commitClientNavigationState();
812
+ restoreScrollPosition(event.state);
813
+ }
814
+ });
815
+ window.history.pushState = function patchedPushState(data, unused, url) {
816
+ state.originalPushState.call(window.history, data, unused, url);
817
+ if (state.suppressUrlNotifyCount === 0) commitClientNavigationState();
818
+ };
819
+ window.history.replaceState = function patchedReplaceState(data, unused, url) {
820
+ state.originalReplaceState.call(window.history, data, unused, url);
821
+ if (state.suppressUrlNotifyCount === 0) commitClientNavigationState();
822
+ };
823
+ }
578
824
  }
579
825
  //#endregion
580
- export { GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, PREFETCH_CACHE_TTL, ReadonlyURLSearchParams, RedirectType, ServerInsertedHTMLContext, _registerStateAccessors, clearServerInsertedHTML, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getClientParams, getLayoutSegmentContext, getNavigationContext, getPrefetchCache, getPrefetchedUrls, isHTTPAccessFallbackError, notFound, permanentRedirect, redirect, setClientParams, setNavigationContext, storePrefetchResponse, toRscUrl, unauthorized, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
826
+ export { GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, PREFETCH_CACHE_TTL, ReadonlyURLSearchParams, RedirectType, ServerInsertedHTMLContext, __basePath, _registerStateAccessors, activateNavigationSnapshot, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, createClientNavigationRenderSnapshot, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getClientNavigationRenderContext, getClientParams, getLayoutSegmentContext, getNavigationContext, getPrefetchCache, getPrefetchedUrls, isHTTPAccessFallbackError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, restoreRscResponse, setClientParams, setNavigationContext, snapshotRscResponse, storePrefetchResponse, toRscUrl, unauthorized, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
581
827
 
582
828
  //# sourceMappingURL=navigation.js.map