vinext 0.0.52 → 0.0.53

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 (238) hide show
  1. package/README.md +1 -1
  2. package/dist/build/clean-output.d.ts +14 -0
  3. package/dist/build/clean-output.js +36 -0
  4. package/dist/build/clean-output.js.map +1 -0
  5. package/dist/build/prerender.d.ts +6 -2
  6. package/dist/build/prerender.js +49 -11
  7. package/dist/build/prerender.js.map +1 -1
  8. package/dist/build/run-prerender.js +10 -1
  9. package/dist/build/run-prerender.js.map +1 -1
  10. package/dist/build/static-export.d.ts +5 -0
  11. package/dist/build/static-export.js +8 -3
  12. package/dist/build/static-export.js.map +1 -1
  13. package/dist/cli.js +19 -4
  14. package/dist/cli.js.map +1 -1
  15. package/dist/client/instrumentation-client-inject.d.ts +34 -0
  16. package/dist/client/instrumentation-client-inject.js +57 -0
  17. package/dist/client/instrumentation-client-inject.js.map +1 -0
  18. package/dist/client/navigation-runtime.d.ts +14 -1
  19. package/dist/client/navigation-runtime.js +16 -1
  20. package/dist/client/navigation-runtime.js.map +1 -1
  21. package/dist/client/vinext-next-data.d.ts +2 -1
  22. package/dist/client/vinext-next-data.js.map +1 -1
  23. package/dist/client/window-next.d.ts +10 -2
  24. package/dist/client/window-next.js.map +1 -1
  25. package/dist/cloudflare/tpr.js +1 -1
  26. package/dist/cloudflare/tpr.js.map +1 -1
  27. package/dist/config/config-matchers.js +2 -1
  28. package/dist/config/config-matchers.js.map +1 -1
  29. package/dist/config/next-config.d.ts +12 -3
  30. package/dist/config/next-config.js +44 -14
  31. package/dist/config/next-config.js.map +1 -1
  32. package/dist/deploy.js +29 -7
  33. package/dist/deploy.js.map +1 -1
  34. package/dist/entries/app-rsc-entry.d.ts +4 -2
  35. package/dist/entries/app-rsc-entry.js +23 -3
  36. package/dist/entries/app-rsc-entry.js.map +1 -1
  37. package/dist/entries/pages-client-entry.js +22 -1
  38. package/dist/entries/pages-client-entry.js.map +1 -1
  39. package/dist/entries/pages-server-entry.js +211 -31
  40. package/dist/entries/pages-server-entry.js.map +1 -1
  41. package/dist/index.js +29 -6
  42. package/dist/index.js.map +1 -1
  43. package/dist/plugins/fonts.js +25 -2
  44. package/dist/plugins/fonts.js.map +1 -1
  45. package/dist/routing/route-trie.js +13 -18
  46. package/dist/routing/route-trie.js.map +1 -1
  47. package/dist/routing/utils.d.ts +11 -1
  48. package/dist/routing/utils.js +15 -1
  49. package/dist/routing/utils.js.map +1 -1
  50. package/dist/server/api-handler.js +18 -9
  51. package/dist/server/api-handler.js.map +1 -1
  52. package/dist/server/app-browser-action-result.d.ts +16 -1
  53. package/dist/server/app-browser-action-result.js +15 -1
  54. package/dist/server/app-browser-action-result.js.map +1 -1
  55. package/dist/server/app-browser-entry.js +22 -12
  56. package/dist/server/app-browser-entry.js.map +1 -1
  57. package/dist/server/app-elements.js +1 -1
  58. package/dist/server/app-fallback-renderer.d.ts +12 -3
  59. package/dist/server/app-fallback-renderer.js +10 -5
  60. package/dist/server/app-fallback-renderer.js.map +1 -1
  61. package/dist/server/app-history-state.js +6 -2
  62. package/dist/server/app-history-state.js.map +1 -1
  63. package/dist/server/app-interception-context-header.d.ts +33 -0
  64. package/dist/server/app-interception-context-header.js +44 -0
  65. package/dist/server/app-interception-context-header.js.map +1 -0
  66. package/dist/server/app-mounted-slots-header.d.ts +19 -0
  67. package/dist/server/app-mounted-slots-header.js +40 -1
  68. package/dist/server/app-mounted-slots-header.js.map +1 -1
  69. package/dist/server/app-optimistic-routing.js +26 -18
  70. package/dist/server/app-optimistic-routing.js.map +1 -1
  71. package/dist/server/app-page-boundary-render.d.ts +1 -0
  72. package/dist/server/app-page-boundary-render.js +2 -0
  73. package/dist/server/app-page-boundary-render.js.map +1 -1
  74. package/dist/server/app-page-boundary.d.ts +1 -0
  75. package/dist/server/app-page-boundary.js +2 -0
  76. package/dist/server/app-page-boundary.js.map +1 -1
  77. package/dist/server/app-page-cache.d.ts +2 -0
  78. package/dist/server/app-page-cache.js +7 -1
  79. package/dist/server/app-page-cache.js.map +1 -1
  80. package/dist/server/app-page-dispatch.d.ts +3 -0
  81. package/dist/server/app-page-dispatch.js +11 -4
  82. package/dist/server/app-page-dispatch.js.map +1 -1
  83. package/dist/server/app-page-element-builder.d.ts +2 -1
  84. package/dist/server/app-page-element-builder.js +5 -2
  85. package/dist/server/app-page-element-builder.js.map +1 -1
  86. package/dist/server/app-page-execution.d.ts +1 -0
  87. package/dist/server/app-page-execution.js +2 -0
  88. package/dist/server/app-page-execution.js.map +1 -1
  89. package/dist/server/app-page-head.d.ts +1 -0
  90. package/dist/server/app-page-head.js +8 -0
  91. package/dist/server/app-page-head.js.map +1 -1
  92. package/dist/server/app-page-render-observation.js +1 -1
  93. package/dist/server/app-page-render.d.ts +1 -0
  94. package/dist/server/app-page-render.js +5 -2
  95. package/dist/server/app-page-render.js.map +1 -1
  96. package/dist/server/app-page-response.d.ts +11 -1
  97. package/dist/server/app-page-response.js +14 -2
  98. package/dist/server/app-page-response.js.map +1 -1
  99. package/dist/server/app-page-route-wiring.d.ts +1 -0
  100. package/dist/server/app-page-route-wiring.js +19 -6
  101. package/dist/server/app-page-route-wiring.js.map +1 -1
  102. package/dist/server/app-page-stream.d.ts +1 -0
  103. package/dist/server/app-page-stream.js +2 -0
  104. package/dist/server/app-page-stream.js.map +1 -1
  105. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  106. package/dist/server/app-route-handler-dispatch.js +3 -0
  107. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  108. package/dist/server/app-route-handler-execution.d.ts +1 -0
  109. package/dist/server/app-route-handler-execution.js +1 -0
  110. package/dist/server/app-route-handler-execution.js.map +1 -1
  111. package/dist/server/app-route-handler-response.js +1 -1
  112. package/dist/server/app-rsc-handler.d.ts +2 -0
  113. package/dist/server/app-rsc-handler.js +18 -9
  114. package/dist/server/app-rsc-handler.js.map +1 -1
  115. package/dist/server/app-rsc-request-normalization.js +3 -2
  116. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  117. package/dist/server/app-segment-config.d.ts +4 -1
  118. package/dist/server/app-segment-config.js +6 -1
  119. package/dist/server/app-segment-config.js.map +1 -1
  120. package/dist/server/app-server-action-execution.d.ts +1 -0
  121. package/dist/server/app-server-action-execution.js +4 -0
  122. package/dist/server/app-server-action-execution.js.map +1 -1
  123. package/dist/server/app-ssr-entry.js +39 -3
  124. package/dist/server/app-ssr-entry.js.map +1 -1
  125. package/dist/server/app-ssr-stream.d.ts +24 -1
  126. package/dist/server/app-ssr-stream.js +78 -5
  127. package/dist/server/app-ssr-stream.js.map +1 -1
  128. package/dist/server/app-static-generation.d.ts +1 -0
  129. package/dist/server/app-static-generation.js +2 -1
  130. package/dist/server/app-static-generation.js.map +1 -1
  131. package/dist/server/default-not-found-module.d.ts +20 -0
  132. package/dist/server/default-not-found-module.js +20 -0
  133. package/dist/server/default-not-found-module.js.map +1 -0
  134. package/dist/server/dev-server.d.ts +1 -1
  135. package/dist/server/dev-server.js +23 -7
  136. package/dist/server/dev-server.js.map +1 -1
  137. package/dist/server/headers.d.ts +5 -1
  138. package/dist/server/headers.js +5 -1
  139. package/dist/server/headers.js.map +1 -1
  140. package/dist/server/image-optimization.d.ts +13 -4
  141. package/dist/server/image-optimization.js +15 -4
  142. package/dist/server/image-optimization.js.map +1 -1
  143. package/dist/server/middleware.js +1 -1
  144. package/dist/server/middleware.js.map +1 -1
  145. package/dist/server/pages-api-route.d.ts +18 -0
  146. package/dist/server/pages-api-route.js +3 -1
  147. package/dist/server/pages-api-route.js.map +1 -1
  148. package/dist/server/pages-body-parser-config.d.ts +60 -0
  149. package/dist/server/pages-body-parser-config.js +79 -0
  150. package/dist/server/pages-body-parser-config.js.map +1 -0
  151. package/dist/server/pages-data-route.js +1 -0
  152. package/dist/server/pages-data-route.js.map +1 -1
  153. package/dist/server/pages-default-404.d.ts +31 -0
  154. package/dist/server/pages-default-404.js +40 -0
  155. package/dist/server/pages-default-404.js.map +1 -0
  156. package/dist/server/pages-node-compat.d.ts +10 -0
  157. package/dist/server/pages-node-compat.js +12 -1
  158. package/dist/server/pages-node-compat.js.map +1 -1
  159. package/dist/server/pages-page-data.d.ts +40 -0
  160. package/dist/server/pages-page-data.js +16 -14
  161. package/dist/server/pages-page-data.js.map +1 -1
  162. package/dist/server/pages-page-response.d.ts +2 -0
  163. package/dist/server/pages-page-response.js +11 -8
  164. package/dist/server/pages-page-response.js.map +1 -1
  165. package/dist/server/prerender-route-params.d.ts +14 -0
  166. package/dist/server/prerender-route-params.js +94 -0
  167. package/dist/server/prerender-route-params.js.map +1 -0
  168. package/dist/server/prod-server.d.ts +3 -23
  169. package/dist/server/prod-server.js +40 -57
  170. package/dist/server/prod-server.js.map +1 -1
  171. package/dist/server/proxy-trust.d.ts +41 -0
  172. package/dist/server/proxy-trust.js +70 -0
  173. package/dist/server/proxy-trust.js.map +1 -0
  174. package/dist/server/request-pipeline.d.ts +3 -3
  175. package/dist/server/request-pipeline.js +5 -4
  176. package/dist/server/request-pipeline.js.map +1 -1
  177. package/dist/server/seed-cache.js +12 -6
  178. package/dist/server/seed-cache.js.map +1 -1
  179. package/dist/server/static-file-cache.js +1 -1
  180. package/dist/server/static-file-cache.js.map +1 -1
  181. package/dist/server/streaming-metadata.d.ts +5 -0
  182. package/dist/server/streaming-metadata.js +10 -0
  183. package/dist/server/streaming-metadata.js.map +1 -0
  184. package/dist/shims/app-router-scroll-state.d.ts +12 -0
  185. package/dist/shims/app-router-scroll-state.js +38 -0
  186. package/dist/shims/app-router-scroll-state.js.map +1 -0
  187. package/dist/shims/app-router-scroll.d.ts +14 -0
  188. package/dist/shims/app-router-scroll.js +100 -0
  189. package/dist/shims/app-router-scroll.js.map +1 -0
  190. package/dist/shims/before-interactive-context.d.ts +30 -0
  191. package/dist/shims/before-interactive-context.js +10 -0
  192. package/dist/shims/before-interactive-context.js.map +1 -0
  193. package/dist/shims/cache-runtime.d.ts +1 -1
  194. package/dist/shims/cache-runtime.js +14 -1
  195. package/dist/shims/cache-runtime.js.map +1 -1
  196. package/dist/shims/default-not-found.d.ts +12 -0
  197. package/dist/shims/default-not-found.js +61 -0
  198. package/dist/shims/default-not-found.js.map +1 -0
  199. package/dist/shims/font-local.d.ts +5 -0
  200. package/dist/shims/font-local.js +6 -2
  201. package/dist/shims/font-local.js.map +1 -1
  202. package/dist/shims/head.js +4 -4
  203. package/dist/shims/head.js.map +1 -1
  204. package/dist/shims/headers.d.ts +6 -2
  205. package/dist/shims/headers.js +64 -21
  206. package/dist/shims/headers.js.map +1 -1
  207. package/dist/shims/image.d.ts +1 -1
  208. package/dist/shims/image.js +4 -4
  209. package/dist/shims/image.js.map +1 -1
  210. package/dist/shims/internal/pages-data-target.d.ts +58 -0
  211. package/dist/shims/internal/pages-data-target.js +91 -0
  212. package/dist/shims/internal/pages-data-target.js.map +1 -0
  213. package/dist/shims/internal/pages-data-url.d.ts +42 -0
  214. package/dist/shims/internal/pages-data-url.js +73 -0
  215. package/dist/shims/internal/pages-data-url.js.map +1 -0
  216. package/dist/shims/link.js +59 -9
  217. package/dist/shims/link.js.map +1 -1
  218. package/dist/shims/metadata.d.ts +2 -1
  219. package/dist/shims/metadata.js +61 -2
  220. package/dist/shims/metadata.js.map +1 -1
  221. package/dist/shims/navigation.js +32 -9
  222. package/dist/shims/navigation.js.map +1 -1
  223. package/dist/shims/router.js +376 -77
  224. package/dist/shims/router.js.map +1 -1
  225. package/dist/shims/script.js +86 -12
  226. package/dist/shims/script.js.map +1 -1
  227. package/dist/shims/server.js +1 -0
  228. package/dist/shims/server.js.map +1 -1
  229. package/dist/shims/url-utils.d.ts +2 -1
  230. package/dist/shims/url-utils.js +15 -4
  231. package/dist/shims/url-utils.js.map +1 -1
  232. package/dist/utils/html-limited-bots.d.ts +5 -0
  233. package/dist/utils/html-limited-bots.js +15 -0
  234. package/dist/utils/html-limited-bots.js.map +1 -0
  235. package/dist/utils/query.d.ts +6 -0
  236. package/dist/utils/query.js +10 -1
  237. package/dist/utils/query.js.map +1 -1
  238. package/package.json +1 -1
@@ -1,13 +1,16 @@
1
1
  import { stripBasePath } from "../utils/base-path.js";
2
2
  import { assertSafeNavigationUrl } from "./url-safety.js";
3
- import { addQueryParam, appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.js";
3
+ import { addQueryParam, appendSearchParamsToUrl, mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
4
4
  import { matchRoutePattern, routePatternParts } from "../routing/route-pattern.js";
5
+ import { isUnknownRecord } from "../utils/record.js";
5
6
  import { RouterContext } from "./internal/router-context.js";
6
7
  import { applyVinextLocaleGlobals, extractVinextNextDataJson, parseVinextNextDataJson } from "../client/vinext-next-data.js";
7
8
  import { isValidModulePath } from "../client/validate-module-path.js";
8
- import { installWindowNext } from "../client/window-next.js";
9
- import { isAbsoluteOrProtocolRelativeUrl, isHashOnlyBrowserUrlChange, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath } from "./url-utils.js";
10
9
  import { addLocalePrefix, getDomainLocaleUrl, getLocalePathPrefix } from "../utils/domain-locale.js";
10
+ import { buildPagesDataHref } from "./internal/pages-data-url.js";
11
+ import { prefetchPagesData, resolvePagesDataNavigationTarget } from "./internal/pages-data-target.js";
12
+ import { installWindowNext } from "../client/window-next.js";
13
+ import { getWindowOrigin, isAbsoluteOrProtocolRelativeUrl, isHashOnlyBrowserUrlChange, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath } from "./url-utils.js";
11
14
  import { scrollToHashTarget } from "./hash-scroll.js";
12
15
  import { setPagesRouterPopStateHandler } from "./pages-router-runtime.js";
13
16
  import { getCurrentBrowserLocale } from "./client-locale.js";
@@ -55,6 +58,8 @@ function resolveUrl(url) {
55
58
  * data fetching, as for the browser URL). We collapse them because vinext's
56
59
  * navigateClient() fetches HTML from the target URL, so `as` must be a
57
60
  * server-resolvable path. Purely decorative `as` values are not supported.
61
+ * Pages error routes are handled as a narrow exception below because Next.js
62
+ * treats their href as the component route while preserving `as` in history.
58
63
  */
59
64
  function resolveNavigationTarget(url, as, locale) {
60
65
  return applyNavigationLocale(as ?? resolveUrl(url), locale);
@@ -66,6 +71,40 @@ function getCurrentUrlLocale() {
66
71
  hostname: getCurrentHostname()
67
72
  });
68
73
  }
74
+ function getLocalPathname(url) {
75
+ if (typeof window === "undefined") return null;
76
+ if (isAbsoluteOrProtocolRelativeUrl(url)) {
77
+ const localPath = toSameOriginAppPath(url, __basePath);
78
+ if (localPath == null) return null;
79
+ return stripBasePath(new URL(localPath, window.location.href).pathname, __basePath);
80
+ }
81
+ try {
82
+ return stripBasePath(new URL(url, window.location.href).pathname, __basePath);
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+ function resolvePagesErrorHtmlFetchUrl(url, locale) {
88
+ const href = resolveUrl(url);
89
+ const errorRoutePathname = getLocalPathname(href);
90
+ if (errorRoutePathname !== "/404" && errorRoutePathname !== "/_error") return null;
91
+ const resolvedUrl = applyNavigationLocale(errorRoutePathname === "/_error" ? replaceUrlPathname(href, "/404") : href, locale);
92
+ let parsed;
93
+ try {
94
+ parsed = new URL(resolvedUrl, window.location.href);
95
+ } catch {
96
+ return null;
97
+ }
98
+ return normalizePathTrailingSlash(toBrowserNavigationHref(`${stripBasePath(parsed.pathname, __basePath)}${parsed.search}${parsed.hash}`, window.location.href, __basePath), __trailingSlash);
99
+ }
100
+ function replaceUrlPathname(url, pathname) {
101
+ try {
102
+ const parsed = new URL(url, window.location.href);
103
+ return `${pathname}${parsed.search}${parsed.hash}`;
104
+ } catch {
105
+ return pathname;
106
+ }
107
+ }
69
108
  function resolveTransitionLocale(locale) {
70
109
  if (typeof window === "undefined") return void 0;
71
110
  if (locale === false) return window.__VINEXT_DEFAULT_LOCALE__;
@@ -329,21 +368,286 @@ function scheduleHardNavigationAndThrow(url, message) {
329
368
  window.location.href = url;
330
369
  throw new HardNavigationScheduledError(message);
331
370
  }
371
+ function isPageComponent(value) {
372
+ if (typeof value === "function") return true;
373
+ if (!isUnknownRecord(value)) return false;
374
+ return value.$$typeof === Symbol.for("react.forward_ref") || value.$$typeof === Symbol.for("react.memo");
375
+ }
376
+ function isAppComponent(value) {
377
+ return isPageComponent(value);
378
+ }
379
+ function resolveSameOriginRedirectedUrl(responseUrl) {
380
+ const appPath = toSameOriginAppPath(responseUrl, __basePath);
381
+ if (appPath === null) return null;
382
+ return normalizePathTrailingSlash(toBrowserNavigationHref(appPath, window.location.href, __basePath), __trailingSlash);
383
+ }
384
+ function stripLocalePrefixForApiRedirect(appPath) {
385
+ const locales = window.__VINEXT_LOCALES__;
386
+ if (!locales || locales.length === 0) return appPath;
387
+ try {
388
+ const parsed = new URL(appPath, "http://vinext.local");
389
+ const pathname = stripBasePath(parsed.pathname, __basePath);
390
+ const firstSegment = pathname.split("/")[1];
391
+ if (!firstSegment || !locales.includes(firstSegment)) return appPath;
392
+ const withoutLocale = pathname.slice(firstSegment.length + 1) || "/";
393
+ if (withoutLocale !== "/api" && !withoutLocale.startsWith("/api/")) return appPath;
394
+ return `${withoutLocale}${parsed.search}${parsed.hash}`;
395
+ } catch {
396
+ return appPath;
397
+ }
398
+ }
399
+ function resolveLocalRedirectUrl(location) {
400
+ let appPath;
401
+ if (location.startsWith("/") && !location.startsWith("//")) try {
402
+ const parsed = new URL(location, "http://vinext.local");
403
+ appPath = stripBasePath(parsed.pathname, __basePath) + parsed.search + parsed.hash;
404
+ } catch {
405
+ appPath = location;
406
+ }
407
+ else appPath = toSameOriginAppPath(location, __basePath);
408
+ if (appPath === null) return null;
409
+ return normalizePathTrailingSlash(toBrowserNavigationHref(stripLocalePrefixForApiRedirect(appPath), window.location.href, __basePath), __trailingSlash);
410
+ }
411
+ function hasVinextMiddleware(nextData) {
412
+ if (!isUnknownRecord(nextData)) return false;
413
+ const vinext = nextData.__vinext;
414
+ return isUnknownRecord(vinext) && vinext.hasMiddleware === true;
415
+ }
416
+ function getMiddlewarePagesDataFetchUrl(browserUrl) {
417
+ const nextData = window.__NEXT_DATA__;
418
+ if (!nextData || !hasVinextMiddleware(nextData)) return null;
419
+ const buildId = nextData.buildId;
420
+ if (typeof buildId !== "string" || buildId.length === 0) return null;
421
+ let parsed;
422
+ try {
423
+ parsed = new URL(browserUrl, window.location.href);
424
+ } catch {
425
+ return null;
426
+ }
427
+ if (parsed.origin !== getWindowOrigin()) return null;
428
+ return buildPagesDataHref(__basePath, buildId, stripBasePath(parsed.pathname, __basePath), parsed.search);
429
+ }
430
+ async function resolveMiddlewareDataRedirect(browserUrl, signal) {
431
+ const dataUrl = getMiddlewarePagesDataFetchUrl(browserUrl);
432
+ if (!dataUrl) return null;
433
+ try {
434
+ return (await fetch(dataUrl, {
435
+ headers: {
436
+ Accept: "application/json",
437
+ "x-nextjs-data": "1"
438
+ },
439
+ signal
440
+ })).headers.get("x-nextjs-redirect");
441
+ } catch (err) {
442
+ if (err instanceof DOMException && err.name === "AbortError") throw err;
443
+ return null;
444
+ }
445
+ }
332
446
  /**
333
- * Perform client-side navigation: fetch the target page's HTML,
334
- * extract __NEXT_DATA__, and re-render the React root.
447
+ * Perform client-side navigation via the `/_next/data/<id>/<page>.json`
448
+ * endpoint. Used when `__VINEXT_PAGE_LOADERS__` has a matching code-split
449
+ * loader for the target pattern (the prod hot path). Falls back to the
450
+ * HTML extraction path (`navigateClientHtml`) when this returns `null`.
451
+ *
452
+ * Failure modes (404, 5xx, network, parse, missing loader, soft redirect)
453
+ * all queue a hard navigation and throw `HardNavigationScheduledError`,
454
+ * mirroring the existing HTML-path failure protocol. The hard reload is
455
+ * the deploy-skew safety net: when the server's buildId has rotated, the
456
+ * data endpoint returns 404 and the client lands on the new build via a
457
+ * full document load.
458
+ */
459
+ async function navigateClientData(url, target, controller, navId, assertStillCurrent) {
460
+ const root = window.__VINEXT_ROOT__;
461
+ if (!root) {
462
+ window.location.href = url;
463
+ return;
464
+ }
465
+ let res;
466
+ try {
467
+ res = await fetch(target.dataHref, {
468
+ headers: {
469
+ Accept: "application/json",
470
+ "x-nextjs-data": "1"
471
+ },
472
+ signal: controller.signal
473
+ });
474
+ } catch (err) {
475
+ if (err instanceof DOMException && err.name === "AbortError") throw new NavigationCancelledError(url);
476
+ throw err;
477
+ }
478
+ assertStillCurrent();
479
+ const softRedirect = res.headers.get("x-nextjs-redirect");
480
+ if (softRedirect) {
481
+ const redirectedUrl = resolveLocalRedirectUrl(softRedirect);
482
+ if (!redirectedUrl) scheduleHardNavigationAndThrow(softRedirect, "Navigation redirected externally");
483
+ window.history.replaceState(window.history.state ?? {}, "", redirectedUrl);
484
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
485
+ await navigateClientHtml(redirectedUrl, redirectedUrl, controller, navId, assertStillCurrent);
486
+ return;
487
+ }
488
+ if (!res.ok) scheduleHardNavigationAndThrow(url, `Data navigation failed: ${res.status} ${res.statusText}`);
489
+ let body;
490
+ try {
491
+ body = await res.json();
492
+ } catch {
493
+ scheduleHardNavigationAndThrow(url, "Data navigation failed: invalid JSON response");
494
+ }
495
+ assertStillCurrent();
496
+ const pageProps = body.pageProps && typeof body.pageProps === "object" ? body.pageProps : {};
497
+ let pageModule;
498
+ try {
499
+ pageModule = await target.loader();
500
+ } catch (err) {
501
+ console.error("[vinext] Page loader threw during navigation:", err);
502
+ scheduleHardNavigationAndThrow(url, "Data navigation failed: page loader threw");
503
+ }
504
+ assertStillCurrent();
505
+ const PageComponent = pageModule.default;
506
+ if (!isPageComponent(PageComponent)) scheduleHardNavigationAndThrow(url, "Data navigation failed: page module default export is not a component");
507
+ let AppComponent = window.__VINEXT_APP__;
508
+ if (!AppComponent && typeof window.__VINEXT_APP_LOADER__ === "function") try {
509
+ const appModule = await window.__VINEXT_APP_LOADER__();
510
+ AppComponent = isAppComponent(appModule.default) ? appModule.default : void 0;
511
+ if (AppComponent) window.__VINEXT_APP__ = AppComponent;
512
+ } catch {}
513
+ assertStillCurrent();
514
+ const React = (await import("react")).default;
515
+ assertStillCurrent();
516
+ let element;
517
+ if (AppComponent) element = React.createElement(AppComponent, {
518
+ Component: PageComponent,
519
+ pageProps
520
+ });
521
+ else element = React.createElement(PageComponent, pageProps);
522
+ element = wrapWithRouterContext(element);
523
+ const mergedQuery = mergeRouteParamsIntoQuery(parseQueryString(target.search), target.params);
524
+ const prev = window.__NEXT_DATA__;
525
+ const nextLocale = (window.__VINEXT_LOCALES__?.length ?? 0) > 0 ? target.locale ?? window.__VINEXT_DEFAULT_LOCALE__ : prev?.locale;
526
+ const nextData = {
527
+ ...prev,
528
+ props: { pageProps },
529
+ page: target.pattern,
530
+ query: mergedQuery,
531
+ buildId: target.buildId,
532
+ isFallback: false,
533
+ ...nextLocale !== void 0 ? { locale: nextLocale } : {}
534
+ };
535
+ window.__NEXT_DATA__ = nextData;
536
+ applyVinextLocaleGlobals(window, nextData);
537
+ root.render(element);
538
+ }
539
+ /**
540
+ * Perform client-side navigation by fetching the page's full HTML and
541
+ * extracting `__NEXT_DATA__` plus the page module URL. Used in dev (where
542
+ * the per-page inline hydration script does not populate the loader map) and
543
+ * as a generic fallback when the data path is not available.
335
544
  *
336
545
  * Throws NavigationCancelledError if a newer navigation supersedes this one.
337
546
  * Throws on hard-navigation failures (non-OK response, missing data) so the
338
547
  * caller can distinguish success from failure for event emission.
339
548
  */
340
- async function navigateClient(url, fetchUrl = url) {
341
- if (typeof window === "undefined") return;
549
+ async function navigateClientHtml(url, fetchUrl, controller, navId, assertStillCurrent, options = {}) {
550
+ let browserUrl = url;
551
+ let pendingRedirectHistoryUrl = fetchUrl === url ? null : url;
342
552
  const root = window.__VINEXT_ROOT__;
343
553
  if (!root) {
344
- window.location.href = url;
554
+ window.location.href = browserUrl;
345
555
  return;
346
556
  }
557
+ let res;
558
+ try {
559
+ res = await fetch(fetchUrl, {
560
+ headers: { Accept: "text/html" },
561
+ signal: controller.signal
562
+ });
563
+ } catch (err) {
564
+ if (err instanceof DOMException && err.name === "AbortError") throw new NavigationCancelledError(url);
565
+ throw err;
566
+ }
567
+ assertStillCurrent();
568
+ if (res.redirected && res.url) {
569
+ const redirectedUrl = resolveSameOriginRedirectedUrl(res.url);
570
+ if (redirectedUrl) {
571
+ browserUrl = redirectedUrl;
572
+ pendingRedirectHistoryUrl = redirectedUrl;
573
+ }
574
+ }
575
+ if (!res.ok && !(options.allowNotFoundResponse === true && res.status === 404)) scheduleHardNavigationAndThrow(browserUrl, `Navigation failed: ${res.status} ${res.statusText}`);
576
+ const html = await res.text();
577
+ assertStillCurrent();
578
+ const nextDataJson = extractVinextNextDataJson(html);
579
+ if (!nextDataJson) scheduleHardNavigationAndThrow(url, "Navigation failed: missing __NEXT_DATA__ in response");
580
+ const nextData = parseVinextNextDataJson(nextDataJson);
581
+ const { pageProps } = nextData.props;
582
+ let pageModuleUrl = nextData.__vinext?.pageModuleUrl;
583
+ if (!pageModuleUrl) {
584
+ const moduleMatch = html.match(/import\("([^"]+)"\);\s*\n\s*const PageComponent/);
585
+ const altMatch = html.match(/await import\("([^"]+pages\/[^"]+)"\)/);
586
+ pageModuleUrl = moduleMatch?.[1] ?? altMatch?.[1] ?? void 0;
587
+ }
588
+ let pageModule;
589
+ if (!pageModuleUrl) {
590
+ const loader = window.__VINEXT_PAGE_LOADERS__?.[nextData.page];
591
+ if (!loader) scheduleHardNavigationAndThrow(browserUrl, "Navigation failed: no page module URL found");
592
+ pageModule = await loader();
593
+ } else {
594
+ if (!isValidModulePath(pageModuleUrl)) {
595
+ console.error("[vinext] Blocked import of invalid page module path:", pageModuleUrl);
596
+ scheduleHardNavigationAndThrow(browserUrl, "Navigation failed: invalid page module path");
597
+ }
598
+ pageModule = await import(
599
+ /* @vite-ignore */
600
+ pageModuleUrl
601
+ );
602
+ }
603
+ assertStillCurrent();
604
+ const PageComponent = pageModule.default;
605
+ if (!isPageComponent(PageComponent)) scheduleHardNavigationAndThrow(browserUrl, "Navigation failed: page module default export is not a component");
606
+ const React = (await import("react")).default;
607
+ assertStillCurrent();
608
+ let AppComponent = window.__VINEXT_APP__;
609
+ const appModuleUrl = nextData.__vinext?.appModuleUrl;
610
+ if (!AppComponent && appModuleUrl) if (!isValidModulePath(appModuleUrl)) console.error("[vinext] Blocked import of invalid app module path:", appModuleUrl);
611
+ else try {
612
+ const appModule = await import(
613
+ /* @vite-ignore */
614
+ appModuleUrl
615
+ );
616
+ AppComponent = isAppComponent(appModule.default) ? appModule.default : void 0;
617
+ window.__VINEXT_APP__ = AppComponent;
618
+ } catch {}
619
+ assertStillCurrent();
620
+ let element;
621
+ if (AppComponent) element = React.createElement(AppComponent, {
622
+ Component: PageComponent,
623
+ pageProps
624
+ });
625
+ else element = React.createElement(PageComponent, pageProps);
626
+ element = wrapWithRouterContext(element);
627
+ if (pendingRedirectHistoryUrl) {
628
+ window.history.replaceState(window.history.state ?? {}, "", pendingRedirectHistoryUrl);
629
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
630
+ }
631
+ window.__NEXT_DATA__ = nextData;
632
+ applyVinextLocaleGlobals(window, nextData);
633
+ root.render(element);
634
+ }
635
+ /**
636
+ * Perform client-side navigation. Prefers the JSON data endpoint when the
637
+ * client has a registered code-split loader for the target route (the prod
638
+ * hot path); otherwise falls back to fetching the page's full HTML (dev and
639
+ * any unmapped route).
640
+ *
641
+ * Throws NavigationCancelledError if a newer navigation supersedes this one.
642
+ * Throws on hard-navigation failures (non-OK response, missing data) so the
643
+ * caller can distinguish success from failure for event emission.
644
+ *
645
+ * `fetchUrl` is the HTML-path fetch URL (already includes locale-root
646
+ * fixups). The JSON path derives its own URL from the browser-facing `url`
647
+ * because the data endpoint speaks the unprefixed path.
648
+ */
649
+ async function navigateClient(url, fetchUrl = url, options = {}) {
650
+ if (typeof window === "undefined") return;
347
651
  _activeAbortController?.abort();
348
652
  const controller = new AbortController();
349
653
  _activeAbortController = controller;
@@ -353,65 +657,32 @@ async function navigateClient(url, fetchUrl = url) {
353
657
  if (navId !== _navigationId) throw new NavigationCancelledError(url);
354
658
  }
355
659
  try {
356
- let res;
357
- try {
358
- res = await fetch(fetchUrl, {
359
- headers: { Accept: "text/html" },
360
- signal: controller.signal
361
- });
362
- } catch (err) {
363
- if (err instanceof DOMException && err.name === "AbortError") throw new NavigationCancelledError(url);
364
- throw err;
365
- }
366
- assertStillCurrent();
367
- if (!res.ok) scheduleHardNavigationAndThrow(url, `Navigation failed: ${res.status} ${res.statusText}`);
368
- const html = await res.text();
369
- assertStillCurrent();
370
- const nextDataJson = extractVinextNextDataJson(html);
371
- if (!nextDataJson) scheduleHardNavigationAndThrow(url, "Navigation failed: missing __NEXT_DATA__ in response");
372
- const nextData = parseVinextNextDataJson(nextDataJson);
373
- const { pageProps } = nextData.props;
374
- let pageModuleUrl = nextData.__vinext?.pageModuleUrl;
375
- if (!pageModuleUrl) {
376
- const moduleMatch = html.match(/import\("([^"]+)"\);\s*\n\s*const PageComponent/);
377
- const altMatch = html.match(/await import\("([^"]+pages\/[^"]+)"\)/);
378
- pageModuleUrl = moduleMatch?.[1] ?? altMatch?.[1] ?? void 0;
660
+ if (options.allowNotFoundResponse === true) await navigateClientHtml(url, fetchUrl, controller, navId, assertStillCurrent, options);
661
+ else {
662
+ let browserUrl = url;
663
+ let htmlFetchUrl = fetchUrl;
664
+ const dataTarget = resolvePagesDataNavigationTarget(browserUrl, __basePath);
665
+ if (!dataTarget) {
666
+ let redirectLocation;
667
+ try {
668
+ redirectLocation = await resolveMiddlewareDataRedirect(browserUrl, controller.signal);
669
+ } catch (err) {
670
+ if (err instanceof DOMException && err.name === "AbortError") throw new NavigationCancelledError(browserUrl);
671
+ throw err;
672
+ }
673
+ assertStillCurrent();
674
+ if (redirectLocation) {
675
+ const redirectedUrl = resolveLocalRedirectUrl(redirectLocation);
676
+ if (!redirectedUrl) scheduleHardNavigationAndThrow(redirectLocation, "Navigation redirected externally");
677
+ window.history.replaceState(window.history.state ?? {}, "", redirectedUrl);
678
+ _lastPathnameAndSearch = window.location.pathname + window.location.search;
679
+ browserUrl = redirectedUrl;
680
+ htmlFetchUrl = redirectedUrl;
681
+ }
682
+ }
683
+ if (dataTarget) await navigateClientData(browserUrl, dataTarget, controller, navId, assertStillCurrent);
684
+ else await navigateClientHtml(browserUrl, htmlFetchUrl, controller, navId, assertStillCurrent, options);
379
685
  }
380
- if (!pageModuleUrl) scheduleHardNavigationAndThrow(url, "Navigation failed: no page module URL found");
381
- if (!isValidModulePath(pageModuleUrl)) {
382
- console.error("[vinext] Blocked import of invalid page module path:", pageModuleUrl);
383
- scheduleHardNavigationAndThrow(url, "Navigation failed: invalid page module path");
384
- }
385
- const pageModule = await import(
386
- /* @vite-ignore */
387
- pageModuleUrl
388
- );
389
- assertStillCurrent();
390
- const PageComponent = pageModule.default;
391
- if (!PageComponent) scheduleHardNavigationAndThrow(url, "Navigation failed: page module has no default export");
392
- const React = (await import("react")).default;
393
- assertStillCurrent();
394
- let AppComponent = window.__VINEXT_APP__;
395
- const appModuleUrl = nextData.__vinext?.appModuleUrl;
396
- if (!AppComponent && appModuleUrl) if (!isValidModulePath(appModuleUrl)) console.error("[vinext] Blocked import of invalid app module path:", appModuleUrl);
397
- else try {
398
- AppComponent = (await import(
399
- /* @vite-ignore */
400
- appModuleUrl
401
- )).default;
402
- window.__VINEXT_APP__ = AppComponent;
403
- } catch {}
404
- assertStillCurrent();
405
- let element;
406
- if (AppComponent) element = React.createElement(AppComponent, {
407
- Component: PageComponent,
408
- pageProps
409
- });
410
- else element = React.createElement(PageComponent, pageProps);
411
- element = wrapWithRouterContext(element);
412
- window.__NEXT_DATA__ = nextData;
413
- applyVinextLocaleGlobals(window, nextData);
414
- root.render(element);
415
686
  } finally {
416
687
  if (navId === _navigationId) _activeAbortController = null;
417
688
  }
@@ -428,9 +699,9 @@ async function navigateClient(url, fetchUrl = url) {
428
699
  * - "failed" — genuine error, caller should return false (hard nav is already
429
700
  * scheduled as recovery)
430
701
  */
431
- async function runNavigateClient(fullUrl, resolvedUrl, fetchUrl = fullUrl) {
702
+ async function runNavigateClient(fullUrl, resolvedUrl, fetchUrl = fullUrl, options = {}) {
432
703
  try {
433
- await navigateClient(fullUrl, fetchUrl);
704
+ await navigateClient(fullUrl, fetchUrl, options);
434
705
  return "completed";
435
706
  } catch (err) {
436
707
  routerEvents.emit("routeChangeError", err, resolvedUrl, { shallow: false });
@@ -530,7 +801,9 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
530
801
  }
531
802
  resolved = normalizePathTrailingSlash(resolved, __trailingSlash);
532
803
  const full = normalizePathTrailingSlash(toBrowserNavigationHref(resolved, window.location.href, __basePath), __trailingSlash);
533
- const htmlFetchUrl = getPagesHtmlFetchUrl(full, navigationLocale);
804
+ const errorRouteHtmlFetchUrl = resolvePagesErrorHtmlFetchUrl(url, navigationLocale);
805
+ const htmlFetchUrl = errorRouteHtmlFetchUrl ?? getPagesHtmlFetchUrl(full, navigationLocale);
806
+ const navigateOptions = errorRouteHtmlFetchUrl ? { allowNotFoundResponse: true } : {};
534
807
  const shallow = options?.shallow ?? false;
535
808
  const doScroll = options?.scroll !== false;
536
809
  if (isHashOnlyChange(full)) {
@@ -548,7 +821,7 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
548
821
  routerEvents.emit("beforeHistoryChange", resolved, { shallow });
549
822
  updateHistory(mode, full);
550
823
  if (!shallow) {
551
- const result = await runNavigateClient(full, resolved, htmlFetchUrl);
824
+ const result = await runNavigateClient(full, resolved, htmlFetchUrl, navigateOptions);
552
825
  if (result === "cancelled") return true;
553
826
  if (result === "failed") return false;
554
827
  }
@@ -560,15 +833,37 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
560
833
  dispatchNavigateEvent();
561
834
  return true;
562
835
  }
563
- /** Inject a `<link rel="prefetch">` for the target page. */
836
+ /**
837
+ * Prefetch the resources needed for a future Pages Router navigation.
838
+ *
839
+ * When the client has a registered code-split loader for the target route
840
+ * (the prod hot path), we prefetch in parallel:
841
+ * 1. The `/_next/data/<buildId>/<page>.json` payload — same URL the actual
842
+ * navigation will request, so a cache hit is automatic.
843
+ * 2. The page's JS chunk — by invoking the loader thunk now. Vite's
844
+ * dynamic `import()` machinery is responsible for fetching + caching;
845
+ * the returned Promise is intentionally discarded.
846
+ *
847
+ * When no loader is registered (dev server, or an unmapped route), we fall
848
+ * back to the legacy `<link rel="prefetch" as="document">` hint, which lets
849
+ * the browser preload the HTML document. This matches the pre-`_next/data`
850
+ * behaviour so dev doesn't regress.
851
+ *
852
+ * Ported from Next.js: `packages/next/src/client/page-loader.ts` `prefetch`
853
+ * (the data + chunk parallel prefetch shape).
854
+ */
564
855
  async function prefetchUrl(url) {
565
- if (typeof document !== "undefined") {
566
- const link = document.createElement("link");
567
- link.rel = "prefetch";
568
- link.href = url;
569
- link.as = "document";
570
- document.head.appendChild(link);
856
+ if (typeof document === "undefined") return;
857
+ const dataTarget = resolvePagesDataNavigationTarget(url, __basePath);
858
+ if (dataTarget) {
859
+ prefetchPagesData(dataTarget);
860
+ return;
571
861
  }
862
+ const link = document.createElement("link");
863
+ link.rel = "prefetch";
864
+ link.href = url;
865
+ link.as = "document";
866
+ document.head.appendChild(link);
572
867
  }
573
868
  /**
574
869
  * useRouter hook - Pages Router compatible.
@@ -684,10 +979,14 @@ function withRouter(ComposedComponent) {
684
979
  const Router = Object.defineProperties({
685
980
  push: (url, as, options) => {
686
981
  if (typeof window === "undefined") throwNoRouterInstance();
982
+ assertSafeNavigationUrl(resolveUrl(url));
983
+ if (as !== void 0) assertSafeNavigationUrl(String(as));
687
984
  return performNavigation(url, as, options, "push");
688
985
  },
689
986
  replace: (url, as, options) => {
690
987
  if (typeof window === "undefined") throwNoRouterInstance();
988
+ assertSafeNavigationUrl(resolveUrl(url));
989
+ if (as !== void 0) assertSafeNavigationUrl(String(as));
691
990
  return performNavigation(url, as, options, "replace");
692
991
  },
693
992
  back: () => {