vinext 0.1.3 → 0.1.4

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 (82) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/prerender.js +1 -0
  4. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  5. package/dist/client/pages-router-link-navigation.js +32 -2
  6. package/dist/client/vinext-next-data.js +2 -0
  7. package/dist/config/config-matchers.d.ts +11 -1
  8. package/dist/config/config-matchers.js +14 -2
  9. package/dist/config/tsconfig-paths.js +14 -1
  10. package/dist/deploy.js +20 -13
  11. package/dist/entries/app-rsc-entry.js +3 -2
  12. package/dist/entries/pages-client-entry.js +14 -13
  13. package/dist/entries/pages-server-entry.js +6 -26
  14. package/dist/index.js +217 -40
  15. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  16. package/dist/plugins/fonts.js +5 -4
  17. package/dist/plugins/strip-server-exports.d.ts +9 -7
  18. package/dist/plugins/strip-server-exports.js +493 -46
  19. package/dist/routing/app-route-graph.js +2 -2
  20. package/dist/server/app-browser-action-result.js +1 -1
  21. package/dist/server/app-browser-entry.js +8 -1
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-state.d.ts +1 -1
  24. package/dist/server/app-browser-state.js +19 -11
  25. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  26. package/dist/server/app-pages-bridge.d.ts +5 -1
  27. package/dist/server/app-pages-bridge.js +5 -13
  28. package/dist/server/app-rsc-handler.d.ts +3 -0
  29. package/dist/server/app-rsc-handler.js +51 -15
  30. package/dist/server/app-rsc-route-matching.js +6 -2
  31. package/dist/server/app-server-action-execution.js +5 -2
  32. package/dist/server/app-ssr-entry.js +1 -29
  33. package/dist/server/before-interactive-head.d.ts +17 -0
  34. package/dist/server/before-interactive-head.js +35 -0
  35. package/dist/server/csp.js +1 -4
  36. package/dist/server/dev-server.js +81 -36
  37. package/dist/server/middleware-matcher.js +12 -3
  38. package/dist/server/middleware-runtime.d.ts +3 -4
  39. package/dist/server/middleware-runtime.js +2 -0
  40. package/dist/server/navigation-planner.d.ts +3 -12
  41. package/dist/server/navigation-planner.js +24 -0
  42. package/dist/server/navigation-trace.d.ts +2 -1
  43. package/dist/server/navigation-trace.js +1 -0
  44. package/dist/server/operation-token.d.ts +40 -0
  45. package/dist/server/operation-token.js +85 -0
  46. package/dist/server/pages-data-route.d.ts +1 -1
  47. package/dist/server/pages-data-route.js +7 -4
  48. package/dist/server/pages-dev-module-url.d.ts +4 -0
  49. package/dist/server/pages-dev-module-url.js +15 -0
  50. package/dist/server/pages-document-initial-props.d.ts +4 -15
  51. package/dist/server/pages-document-initial-props.js +27 -56
  52. package/dist/server/pages-i18n.js +2 -2
  53. package/dist/server/pages-page-data.js +3 -1
  54. package/dist/server/pages-page-handler.js +3 -1
  55. package/dist/server/pages-page-response.d.ts +2 -0
  56. package/dist/server/pages-page-response.js +4 -4
  57. package/dist/server/pages-readiness.js +1 -1
  58. package/dist/server/pages-request-pipeline.d.ts +7 -7
  59. package/dist/server/pages-request-pipeline.js +63 -21
  60. package/dist/server/prod-server.d.ts +3 -1
  61. package/dist/server/prod-server.js +41 -10
  62. package/dist/server/static-file-cache.js +16 -4
  63. package/dist/shims/before-interactive-context.d.ts +14 -3
  64. package/dist/shims/document.d.ts +15 -20
  65. package/dist/shims/document.js +5 -8
  66. package/dist/shims/image.js +9 -2
  67. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  68. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  69. package/dist/shims/internal/pages-data-target.js +1 -1
  70. package/dist/shims/link.js +37 -16
  71. package/dist/shims/metadata.js +4 -4
  72. package/dist/shims/navigation.js +2 -0
  73. package/dist/shims/router.d.ts +6 -2
  74. package/dist/shims/router.js +99 -20
  75. package/dist/shims/script.js +8 -4
  76. package/dist/utils/has-trailing-comma.d.ts +24 -0
  77. package/dist/utils/has-trailing-comma.js +62 -0
  78. package/dist/utils/text-stream.d.ts +1 -1
  79. package/dist/utils/text-stream.js +2 -2
  80. package/dist/utils/vite-version.d.ts +12 -1
  81. package/dist/utils/vite-version.js +9 -1
  82. package/package.json +1 -1
@@ -34,25 +34,23 @@ declare function Main(): React.JSX.Element;
34
34
  */
35
35
  declare function NextScript(): React.JSX.Element;
36
36
  /**
37
- * Loose stand-ins for Next.js's `DocumentContext` / `DocumentInitialProps`.
38
- * The shim doesn't currently invoke `getInitialProps` on user `_document.tsx`
39
- * files (separate gap), but the signatures here match Next.js's so subclasses
40
- * that delegate via `await Document.getInitialProps(ctx)` typecheck against
41
- * the same shape they'd see under real Next.js.
37
+ * Stand-ins for Next.js's `DocumentContext` / `DocumentInitialProps`.
38
+ * The signatures match Next.js so custom `_document.tsx` subclasses can use
39
+ * `ctx.renderPage()` enhancers and delegate through
40
+ * `await Document.getInitialProps(ctx)` with the expected public types.
42
41
  *
43
42
  * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/utils.ts
44
43
  */
45
44
  type DocumentContext = {
46
- renderPage?: (options?: {
45
+ renderPage: (options?: {
47
46
  enhanceApp?: (App: React.ComponentType<{
48
47
  children?: React.ReactNode;
49
- }>) => unknown;
50
- enhanceComponent?: (Comp: React.ComponentType<unknown>) => unknown;
51
- }) => {
52
- html: string;
53
- head?: ReadonlyArray<React.ReactElement>;
54
- };
55
- defaultGetInitialProps?: (ctx: DocumentContext, options?: {
48
+ }>) => React.ComponentType<{
49
+ children?: React.ReactNode;
50
+ }>;
51
+ enhanceComponent?: (Comp: React.ComponentType<unknown>) => React.ComponentType<unknown>;
52
+ } | ((Comp: React.ComponentType<unknown>) => React.ComponentType<unknown>)) => DocumentInitialProps | Promise<DocumentInitialProps>;
53
+ defaultGetInitialProps: (ctx: DocumentContext, options?: {
56
54
  nonce?: string;
57
55
  }) => Promise<DocumentInitialProps>;
58
56
  pathname?: string;
@@ -83,14 +81,11 @@ declare class Document<P = {}> extends React.Component<P & {
83
81
  children?: React.ReactNode;
84
82
  }> {
85
83
  /**
86
- * `getInitialProps` is invoked by the SSR pipeline. The default implementation
87
- * is a stub: vinext does not yet plumb the Pages Router `renderPage` /
88
- * `defaultGetInitialProps` chain into the SSR entry, so subclasses that
89
- * delegate via `await Document.getInitialProps(ctx)` receive an empty shell
90
- * (`html: ""`). This matches the runtime contract user code expects without
91
- * pretending the chain is wired up.
84
+ * `getInitialProps` is invoked by the SSR pipeline. The runtime-provided
85
+ * `ctx.defaultGetInitialProps()` owns the page render and style collection,
86
+ * matching Next.js's canonical CSS-in-JS integration path.
92
87
  */
93
- static getInitialProps(_ctx: DocumentContext): Promise<DocumentInitialProps>;
88
+ static getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps>;
94
89
  render(): React.ReactNode;
95
90
  }
96
91
  //#endregion
@@ -61,15 +61,12 @@ function NextScript() {
61
61
  */
62
62
  var Document = class extends React.Component {
63
63
  /**
64
- * `getInitialProps` is invoked by the SSR pipeline. The default implementation
65
- * is a stub: vinext does not yet plumb the Pages Router `renderPage` /
66
- * `defaultGetInitialProps` chain into the SSR entry, so subclasses that
67
- * delegate via `await Document.getInitialProps(ctx)` receive an empty shell
68
- * (`html: ""`). This matches the runtime contract user code expects without
69
- * pretending the chain is wired up.
64
+ * `getInitialProps` is invoked by the SSR pipeline. The runtime-provided
65
+ * `ctx.defaultGetInitialProps()` owns the page render and style collection,
66
+ * matching Next.js's canonical CSS-in-JS integration path.
70
67
  */
71
- static async getInitialProps(_ctx) {
72
- return { html: "" };
68
+ static getInitialProps(ctx) {
69
+ return ctx.defaultGetInitialProps(ctx);
73
70
  }
74
71
  render() {
75
72
  return /* @__PURE__ */ jsxs(Html, { children: [/* @__PURE__ */ jsx(Head, {}), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx(Main, {}), /* @__PURE__ */ jsx(NextScript, {})] })] });
@@ -175,6 +175,13 @@ function sanitizeBlurDataURL(url) {
175
175
  function isRemoteUrl(src) {
176
176
  return src.startsWith("http://") || src.startsWith("https://") || src.startsWith("//");
177
177
  }
178
+ function isSvgUrl(src) {
179
+ try {
180
+ return new URL(src, "http://vinext.local").pathname.toLowerCase().endsWith(".svg");
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
178
185
  function getFillStyle(style, backgroundStyle) {
179
186
  return {
180
187
  position: "absolute",
@@ -414,7 +421,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
414
421
  }
415
422
  }
416
423
  const imgQuality = quality ?? 75;
417
- const isSvg = src.endsWith(".svg");
424
+ const isSvg = isSvgUrl(src);
418
425
  const skipOptimization = _unoptimized === true || isSvg && !__dangerouslyAllowSVG;
419
426
  const srcSet = imgWidth && !fill && !skipOptimization ? generateSrcSet(src, imgWidth, imgQuality) : imgWidth && !fill ? RESPONSIVE_WIDTHS.filter((w) => w <= imgWidth * 2).map((w) => `${src} ${w}w`).join(", ") || `${src} ${imgWidth}w` : void 0;
420
427
  const optimizedSrc = skipOptimization ? src : imgWidth ? imageOptimizationUrl(src, imgWidth, imgQuality) : imageOptimizationUrl(src, RESPONSIVE_WIDTHS[0], imgQuality);
@@ -483,7 +490,7 @@ function getImageProps(props) {
483
490
  width: imgWidth ?? 0,
484
491
  quality: imgQuality
485
492
  }) : src;
486
- const isSvg = resolvedSrc.endsWith(".svg");
493
+ const isSvg = isSvgUrl(resolvedSrc);
487
494
  const skipOpt = _unoptimized === true || isSvg && !__dangerouslyAllowSVG || blockedInProd || !!loader || isRemoteUrl(resolvedSrc);
488
495
  const optimizedSrc = skipOpt ? resolvedSrc : imgWidth ? imageOptimizationUrl(resolvedSrc, imgWidth, imgQuality) : imageOptimizationUrl(resolvedSrc, RESPONSIVE_WIDTHS[0], imgQuality);
489
496
  const srcSet = imgWidth && !fill && !isRemoteUrl(resolvedSrc) && !loader && !skipOpt ? generateSrcSet(resolvedSrc, imgWidth, imgQuality) : void 0;
@@ -24,19 +24,18 @@
24
24
  * directly by anyone, which keeps subsequent clones legal even after one
25
25
  * caller has consumed its copy.
26
26
  *
27
- * - No `AbortSignal` is honored at the shared layer. Each `Router.push` cycle
28
- * has its own AbortController that supersedes prior navigations via
29
- * `_navigationId`; aborting the shared fetch on behalf of one caller would
30
- * destroy the dedup gain for every other concurrent caller. Cancellation is
31
- * handled by the caller's `assertStillCurrent()` checkpoints after `await`,
32
- * not by abort propagation.
27
+ * - Each caller owns one waiter. Cancelling a waiter leaves the shared request
28
+ * alive while another waiter remains; cancelling the final waiter aborts the
29
+ * underlying fetch and evicts the entry immediately so a replacement caller
30
+ * can retry without joining a doomed request.
33
31
  *
34
32
  * - The map is module-scoped (one per realm). The Pages Router runs in the
35
33
  * browser only, so a single `Map` is sufficient.
36
34
  */
37
35
  /**
38
36
  * Dedupe a `fetch()` against the `_next/data` endpoint. Multiple concurrent
39
- * callers for the same `dataHref` share one underlying network request.
37
+ * callers for the same resolved URL and deployment ID share one underlying
38
+ * network request.
40
39
  *
41
40
  * Each call returns a freshly-cloned `Response` so consumers can read the
42
41
  * body independently. Once the in-flight Promise settles (resolve or reject)
@@ -1,3 +1,4 @@
1
+ import "../../utils/deployment-id.js";
1
2
  //#region src/shims/internal/pages-data-fetch-dedup.ts
2
3
  /**
3
4
  * In-flight request dedup for the Pages Router `/_next/data/<id>/<page>.json`
@@ -24,21 +25,57 @@
24
25
  * directly by anyone, which keeps subsequent clones legal even after one
25
26
  * caller has consumed its copy.
26
27
  *
27
- * - No `AbortSignal` is honored at the shared layer. Each `Router.push` cycle
28
- * has its own AbortController that supersedes prior navigations via
29
- * `_navigationId`; aborting the shared fetch on behalf of one caller would
30
- * destroy the dedup gain for every other concurrent caller. Cancellation is
31
- * handled by the caller's `assertStillCurrent()` checkpoints after `await`,
32
- * not by abort propagation.
28
+ * - Each caller owns one waiter. Cancelling a waiter leaves the shared request
29
+ * alive while another waiter remains; cancelling the final waiter aborts the
30
+ * underlying fetch and evicts the entry immediately so a replacement caller
31
+ * can retry without joining a doomed request.
33
32
  *
34
33
  * - The map is module-scoped (one per realm). The Pages Router runs in the
35
34
  * browser only, so a single `Map` is sufficient.
36
35
  */
37
- /** Inflight fetch promises keyed by the resolved data URL. */
36
+ /** Inflight fetch entries keyed by the resolved data request identity. */
38
37
  const inflight = /* @__PURE__ */ new Map();
38
+ function getInflightKey(dataHref, init) {
39
+ let resolvedHref = dataHref;
40
+ if (typeof window !== "undefined") try {
41
+ resolvedHref = new URL(dataHref, window.location.href).href;
42
+ } catch {}
43
+ const deploymentId = new Headers(init?.headers).get("x-deployment-id") ?? "";
44
+ return `${resolvedHref}\n${deploymentId}`;
45
+ }
46
+ function cloneSharedResponse(key, entry, signal) {
47
+ entry.waiters += 1;
48
+ return new Promise((resolve, reject) => {
49
+ let released = false;
50
+ const release = (cancelled) => {
51
+ if (released) return;
52
+ released = true;
53
+ entry.waiters -= 1;
54
+ if (cancelled && entry.waiters === 0 && !entry.settled) {
55
+ if (inflight.get(key) === entry) inflight.delete(key);
56
+ entry.controller.abort();
57
+ }
58
+ };
59
+ const abort = () => {
60
+ release(true);
61
+ reject(new DOMException("Aborted", "AbortError"));
62
+ };
63
+ signal?.addEventListener("abort", abort, { once: true });
64
+ entry.promise.then((response) => {
65
+ signal?.removeEventListener("abort", abort);
66
+ release(false);
67
+ resolve(response.clone());
68
+ }, (error) => {
69
+ signal?.removeEventListener("abort", abort);
70
+ release(false);
71
+ reject(error);
72
+ });
73
+ });
74
+ }
39
75
  /**
40
76
  * Dedupe a `fetch()` against the `_next/data` endpoint. Multiple concurrent
41
- * callers for the same `dataHref` share one underlying network request.
77
+ * callers for the same resolved URL and deployment ID share one underlying
78
+ * network request.
42
79
  *
43
80
  * Each call returns a freshly-cloned `Response` so consumers can read the
44
81
  * body independently. Once the in-flight Promise settles (resolve or reject)
@@ -48,20 +85,36 @@ const inflight = /* @__PURE__ */ new Map();
48
85
  * dropped on failure so the next navigation can retry.
49
86
  */
50
87
  function dedupedPagesDataFetch(dataHref, init) {
51
- let entry = inflight.get(dataHref);
88
+ const key = getInflightKey(dataHref, init);
89
+ const signal = init?.signal ?? void 0;
90
+ if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
91
+ let entry = inflight.get(key);
52
92
  if (!entry) {
53
- entry = fetch(dataHref, init).finally(() => {
54
- if (inflight.get(dataHref) === entry) inflight.delete(dataHref);
55
- });
56
- inflight.set(dataHref, entry);
93
+ const controller = new AbortController();
94
+ let currentEntry;
95
+ currentEntry = {
96
+ controller,
97
+ promise: fetch(dataHref, {
98
+ ...init,
99
+ signal: controller.signal
100
+ }).finally(() => {
101
+ currentEntry.settled = true;
102
+ if (inflight.get(key) === currentEntry) inflight.delete(key);
103
+ }),
104
+ settled: false,
105
+ waiters: 0
106
+ };
107
+ inflight.set(key, currentEntry);
108
+ entry = currentEntry;
57
109
  }
58
- return entry.then((res) => res.clone());
110
+ return cloneSharedResponse(key, entry, signal);
59
111
  }
60
112
  /**
61
113
  * Drop every cached in-flight entry. Intended for tests; production code
62
114
  * does not need to call this because entries self-evict on settle.
63
115
  */
64
116
  function clearPagesDataInflight() {
117
+ for (const entry of inflight.values()) entry.controller.abort();
65
118
  inflight.clear();
66
119
  }
67
120
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import { stripBasePath } from "../../utils/base-path.js";
2
2
  import { getLocalePathPrefix } from "../../utils/domain-locale.js";
3
3
  import { buildPagesDataHref, matchPagesPattern } from "./pages-data-url.js";
4
- import { dedupedPagesDataFetch } from "./pages-data-fetch-dedup.js";
5
4
  import { NEXT_DEPLOYMENT_ID_HEADER, getDeploymentId } from "../../utils/deployment-id.js";
5
+ import { dedupedPagesDataFetch } from "./pages-data-fetch-dedup.js";
6
6
  //#region src/shims/internal/pages-data-target.ts
7
7
  /**
8
8
  * Shared decision helper for the Pages Router `/_next/data/<id>/<page>.json`
@@ -17,7 +17,7 @@ import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-
17
17
  import { getNavigationRuntime, hasAppNavigationRuntime, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
18
18
  import { createRscRequestHeaders, createRscRequestUrl, stripRscCacheBustingSearchParam, stripRscSuffix } from "../server/app-rsc-cache-busting.js";
19
19
  import { getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
20
- import { navigatePagesRouterLink } from "../client/pages-router-link-navigation.js";
20
+ import { navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref } from "../client/pages-router-link-navigation.js";
21
21
  import { getI18nContext } from "./i18n-context.js";
22
22
  import { canLinkIntentPrefetch, canLinkPrefetch, getLinkPrefetchHref } from "./link-prefetch.js";
23
23
  import { clearLinkForCurrentNavigation, notifyLinkNavigationStart, setLinkForCurrentNavigation } from "./internal/link-status-registry.js";
@@ -56,6 +56,19 @@ function resolveHref(href) {
56
56
  }
57
57
  return url;
58
58
  }
59
+ function resolvePagesQueryOnlyHref(href) {
60
+ if (!href.startsWith("?") || typeof window === "undefined") return href;
61
+ const pagesRouter = window.next?.appDir === true ? void 0 : window.next?.router;
62
+ return resolvePagesRouterQueryOnlyHref(href, {
63
+ asPath: pagesRouter && "reload" in pagesRouter && "asPath" in pagesRouter && typeof pagesRouter.asPath === "string" ? pagesRouter.asPath : void 0,
64
+ basePath: __basePath,
65
+ fallbackHref: window.location.href,
66
+ locales: window.__VINEXT_LOCALES__
67
+ });
68
+ }
69
+ function resolvePagesLinkNavigationHref(href, locale) {
70
+ return normalizePathTrailingSlash(applyLocaleToHref(resolvePagesQueryOnlyHref(href), locale), __trailingSlash);
71
+ }
59
72
  /**
60
73
  * Collapse repeated forward-slashes (and convert backslashes to forward-slashes)
61
74
  * in the path portion of a URL, preserving any query string.
@@ -493,7 +506,9 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
493
506
  navigateHref = localPath;
494
507
  }
495
508
  e.preventDefault();
496
- const absoluteFullHref = toBrowserNavigationHref(navigateHref, window.location.href, __basePath);
509
+ const hasAppNavigationRuntime = Boolean(getNavigationRuntime()?.functions.navigate);
510
+ const pagesNavigateHref = resolvedHref.startsWith("?") ? resolvePagesLinkNavigationHref(resolvedHref, locale) : navigateHref;
511
+ const absoluteFullHref = toBrowserNavigationHref(hasAppNavigationRuntime ? navigateHref : pagesNavigateHref, window.location.href, __basePath);
497
512
  if (onNavigate) try {
498
513
  const navUrl = new URL(absoluteFullHref, window.location.origin);
499
514
  let prevented = false;
@@ -509,12 +524,12 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
509
524
  onNavigate(navEvent);
510
525
  if (navEvent.defaultPrevented) return;
511
526
  } catch {}
512
- if (getNavigationRuntime()?.functions.navigate && ["pages", "document"].includes(resolveHybridClientRouteOwner(navigateHref, __basePath) ?? "")) {
527
+ if (hasAppNavigationRuntime && ["pages", "document"].includes(resolveHybridClientRouteOwner(navigateHref, __basePath) ?? "")) {
513
528
  if (replace) window.location.replace(absoluteFullHref);
514
529
  else window.location.assign(absoluteFullHref);
515
530
  return;
516
531
  }
517
- if (getNavigationRuntime()?.functions.navigate) {
532
+ if (hasAppNavigationRuntime) {
518
533
  const setter = setPendingRef.current;
519
534
  if (setter) setLinkForCurrentNavigation(setter);
520
535
  setPending(true);
@@ -525,19 +540,25 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
525
540
  });
526
541
  });
527
542
  return;
528
- } else try {
529
- const Router = (await import("next/router.js")).default;
530
- await navigatePagesRouterLink(Router, {
531
- href: navigateHref,
532
- replace,
533
- scroll,
534
- shallow,
535
- locale
543
+ } else {
544
+ const Router = window.next?.appDir === true ? void 0 : window.next?.router;
545
+ await navigatePagesRouterLinkWithFallback({
546
+ router: Router && "reload" in Router ? Router : void 0,
547
+ loadRouter: async () => (await import("next/router.js")).default,
548
+ navigation: {
549
+ href: pagesNavigateHref,
550
+ replace,
551
+ scroll,
552
+ shallow,
553
+ locale,
554
+ interpolateDynamicRoute: resolvedHref.startsWith("?")
555
+ },
556
+ fallback: () => {
557
+ if (replace) window.history.replaceState({}, "", absoluteFullHref);
558
+ else window.history.pushState({}, "", absoluteFullHref);
559
+ window.dispatchEvent(new PopStateEvent("popstate"));
560
+ }
536
561
  });
537
- } catch {
538
- if (replace) window.history.replaceState({}, "", absoluteFullHref);
539
- else window.history.pushState({}, "", absoluteFullHref);
540
- window.dispatchEvent(new PopStateEvent("popstate"));
541
562
  }
542
563
  };
543
564
  const anchorProps = restWithoutLocale;
@@ -621,7 +621,7 @@ function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
621
621
  }
622
622
  }
623
623
  }
624
- if (tw.players) {
624
+ if (tw.card === "player" && tw.players) {
625
625
  const players = Array.isArray(tw.players) ? tw.players : [tw.players];
626
626
  for (const player of players) {
627
627
  const playerUrl = player.playerUrl.toString();
@@ -644,7 +644,7 @@ function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
644
644
  }, key++));
645
645
  }
646
646
  }
647
- if (tw.app) {
647
+ if (tw.card === "app" && tw.app) {
648
648
  const { app } = tw;
649
649
  for (const platform of [
650
650
  "iphone",
@@ -655,11 +655,11 @@ function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
655
655
  name: `twitter:app:name:${platform}`,
656
656
  content: app.name
657
657
  }, key++));
658
- if (app.id[platform] !== void 0) elements.push(/* @__PURE__ */ jsx("meta", {
658
+ if (app.id[platform]) elements.push(/* @__PURE__ */ jsx("meta", {
659
659
  name: `twitter:app:id:${platform}`,
660
660
  content: String(app.id[platform])
661
661
  }, key++));
662
- if (app.url?.[platform] !== void 0) {
662
+ if (app.url?.[platform]) {
663
663
  const appUrl = app.url[platform].toString();
664
664
  elements.push(/* @__PURE__ */ jsx("meta", {
665
665
  name: `twitter:app:url:${platform}`,
@@ -1084,6 +1084,7 @@ const _appRouter = {
1084
1084
  push(href, options) {
1085
1085
  assertSafeNavigationUrl(href);
1086
1086
  if (isServer) return;
1087
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
1087
1088
  const releaseNavigation = trackScheduledAppRouterNavigation();
1088
1089
  try {
1089
1090
  React$1.startTransition(() => {
@@ -1098,6 +1099,7 @@ const _appRouter = {
1098
1099
  replace(href, options) {
1099
1100
  assertSafeNavigationUrl(href);
1100
1101
  if (isServer) return;
1102
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
1101
1103
  const releaseNavigation = trackScheduledAppRouterNavigation();
1102
1104
  try {
1103
1105
  React$1.startTransition(() => {
@@ -34,12 +34,15 @@ type NextRouter = {
34
34
  type UrlObject = {
35
35
  pathname?: string;
36
36
  query?: UrlQuery;
37
+ search?: string;
38
+ hash?: string;
37
39
  };
38
40
  type TransitionOptions = {
39
41
  _h?: 1;
40
42
  shallow?: boolean;
41
43
  scroll?: boolean;
42
44
  locale?: string | false;
45
+ _vinextInterpolateDynamicRoute?: boolean;
43
46
  };
44
47
  type RouterEvents = {
45
48
  on(event: string, handler: (...args: unknown[]) => void): void;
@@ -50,7 +53,7 @@ type RouterEvents = {
50
53
  * Apply locale prefix to a URL for client-side navigation.
51
54
  * Same logic as Link's applyLocaleToHref but reads from window globals.
52
55
  */
53
- declare function applyNavigationLocale(url: string, locale?: string): string;
56
+ declare function applyNavigationLocale(url: string, locale?: string, replaceExistingLocale?: boolean): string;
54
57
  /** Check if a URL is external (any URL scheme per RFC 3986, or protocol-relative) */
55
58
  declare function isExternalUrl(url: string): boolean;
56
59
  /** Check if a href is only a hash change relative to the current URL */
@@ -110,6 +113,7 @@ type PagesNavigationContextShape = {
110
113
  declare function getPagesNavigationContext(): PagesNavigationContextShape | null;
111
114
  declare function getPagesNavigationIsReadyFromSerializedState(routePattern: string | undefined, searchString: string, nextData?: VinextNextData): boolean;
112
115
  declare function markPagesRouterReady(): boolean;
116
+ declare function initializePagesRouterReadyFromNextData(nextData: VinextNextData): void;
113
117
  /**
114
118
  * useRouter hook - Pages Router compatible.
115
119
  *
@@ -187,4 +191,4 @@ declare const RouterMethods: {
187
191
  };
188
192
  declare const Router: typeof RouterMethods & Omit<NextRouter, keyof typeof RouterMethods>;
189
193
  //#endregion
190
- export { ExcludeRouterProps, NextRouter, WithRouterProps, markPagesRouterReady as _markPagesRouterReady, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
194
+ export { ExcludeRouterProps, NextRouter, WithRouterProps, initializePagesRouterReadyFromNextData as _initializePagesRouterReadyFromNextData, markPagesRouterReady as _markPagesRouterReady, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
@@ -1,7 +1,7 @@
1
1
  import { splitPathSegments } from "../routing/utils.js";
2
2
  import { removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
3
3
  import { assertSafeNavigationUrl } from "./url-safety.js";
4
- import { matchRoutePattern, routePatternParts } from "../routing/route-pattern.js";
4
+ import { fillRoutePatternSegments, matchRoutePattern, routePatternParts } from "../routing/route-pattern.js";
5
5
  import { isUnknownRecord } from "../utils/record.js";
6
6
  import { AppRouterContext } from "./internal/app-router-context.js";
7
7
  import { RouterContext } from "./internal/router-context.js";
@@ -9,8 +9,8 @@ import { applyVinextLocaleGlobals, extractVinextNextDataJson, parseVinextNextDat
9
9
  import { isValidModulePath } from "../client/validate-module-path.js";
10
10
  import { addLocalePrefix, getDomainLocaleUrl, getLocalePathPrefix } from "../utils/domain-locale.js";
11
11
  import { buildPagesDataHref } from "./internal/pages-data-url.js";
12
- import { dedupedPagesDataFetch } from "./internal/pages-data-fetch-dedup.js";
13
12
  import { NEXT_DEPLOYMENT_ID_HEADER, getDeploymentId } from "../utils/deployment-id.js";
13
+ import { dedupedPagesDataFetch } from "./internal/pages-data-fetch-dedup.js";
14
14
  import { prefetchPagesData, resolvePagesDataNavigationTarget } from "./internal/pages-data-target.js";
15
15
  import { addQueryParam, appendSearchParamsToUrl, mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
16
16
  import { resolveHybridClientRouteOwner } from "./internal/hybrid-client-route-owner.js";
@@ -214,11 +214,20 @@ function getPagesRouterRuntimeComponents() {
214
214
  }
215
215
  function resolveUrl(url) {
216
216
  if (typeof url === "string") return url;
217
- let result = url.pathname ?? "/";
218
- if (url.query) {
217
+ const hasQuery = url.query !== void 0 && Object.keys(url.query).length > 0;
218
+ const hasSearch = typeof url.search === "string" && url.search.length > 0;
219
+ const hasHash = typeof url.hash === "string" && url.hash.length > 0;
220
+ const inheritsVisiblePath = url.pathname === void 0 && (hasQuery || hasSearch || hasHash);
221
+ let result = url.pathname ?? (typeof window !== "undefined" ? inheritsVisiblePath ? stripBasePath(window.location.pathname, __basePath) : window.__NEXT_DATA__?.page ?? stripBasePath(window.location.pathname, __basePath) : "/");
222
+ if (hasSearch) {
223
+ const search = url.search.startsWith("?") ? url.search : `?${url.search}`;
224
+ const hashIndex = search.indexOf("#");
225
+ result += hashIndex === -1 ? search : `${search.slice(0, hashIndex)}%23${search.slice(hashIndex + 1)}`;
226
+ } else if (hasQuery) {
219
227
  const params = urlQueryToSearchParams(url.query);
220
228
  result = appendSearchParamsToUrl(result, params);
221
- }
229
+ } else if (hasHash && typeof window !== "undefined") result += window.location.search;
230
+ if (hasHash) result += url.hash.startsWith("#") ? url.hash : `#${url.hash}`;
222
231
  return result;
223
232
  }
224
233
  /**
@@ -230,8 +239,39 @@ function resolveUrl(url) {
230
239
  * Pages error routes are handled as a narrow exception below because Next.js
231
240
  * treats their href as the component route while preserving `as` in history.
232
241
  */
233
- function resolveNavigationTarget(url, as, locale) {
234
- return applyNavigationLocale(as ?? resolveUrl(url), locale);
242
+ function resolveNavigationTarget(url, as, locale, replaceExistingLocale = false) {
243
+ return applyNavigationLocale(as ?? resolveUrl(url), locale, replaceExistingLocale);
244
+ }
245
+ var HrefInterpolationError = class extends Error {};
246
+ function interpolateCurrentDynamicRoute(resolved) {
247
+ if (typeof window === "undefined") return resolved;
248
+ const routePattern = window.__NEXT_DATA__?.page;
249
+ if (!routePattern || extractRouteParamNames(routePattern).length === 0) return resolved;
250
+ try {
251
+ const target = new URL(resolved, "http://vinext.local");
252
+ const currentOrigin = getWindowOrigin();
253
+ if (currentOrigin && target.origin !== "http://vinext.local" && target.origin !== currentOrigin) return resolved;
254
+ const visiblePath = stripBasePath(window.location.pathname, __basePath);
255
+ const visibleLocale = getLocalePathPrefix(visiblePath, window.__VINEXT_LOCALES__);
256
+ if (extractRouteParamsFromPath(routePattern, visibleLocale ? visiblePath.slice(visibleLocale.length + 1) || "/" : visiblePath) === null) return resolved;
257
+ const query = parseQueryString(target.search);
258
+ const missingParams = routePatternParts(routePattern).filter((part) => part.startsWith(":") && !part.endsWith("*")).map((part) => part.slice(1, part.endsWith("+") ? -1 : void 0)).filter((paramName) => {
259
+ const value = query[paramName];
260
+ return value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
261
+ });
262
+ if (missingParams.length > 0) throw new HrefInterpolationError(`The provided \`href\` (${`${routePattern}${target.search}${target.hash}`}) value is missing query values (${missingParams.join(", ")}) to be interpolated properly. Read more: https://nextjs.org/docs/messages/href-interpolation-failed`);
263
+ const routeParams = getRouteParamsFromQuery(routePattern, query);
264
+ if (!routeParams) return resolved;
265
+ const pathname = fillRoutePatternSegments(routePattern, Object.fromEntries(Object.entries(routeParams).map(([key, value]) => [key, Array.isArray(value) ? value.map(encodeURIComponent) : encodeURIComponent(value)])));
266
+ if (!pathname) return resolved;
267
+ const targetLocale = getLocalePathPrefix(target.pathname, window.__VINEXT_LOCALES__);
268
+ target.pathname = targetLocale ? `/${targetLocale}${pathname}` : pathname;
269
+ for (const paramName of extractRouteParamNames(routePattern)) target.searchParams.delete(paramName);
270
+ return target.href.slice(target.origin.length);
271
+ } catch (error) {
272
+ if (error instanceof HrefInterpolationError) throw error;
273
+ return resolved;
274
+ }
235
275
  }
236
276
  function getCurrentUrlLocale() {
237
277
  return getCurrentBrowserLocale({
@@ -296,13 +336,26 @@ function getDomainLocalePath(url, locale) {
296
336
  * Apply locale prefix to a URL for client-side navigation.
297
337
  * Same logic as Link's applyLocaleToHref but reads from window globals.
298
338
  */
299
- function applyNavigationLocale(url, locale) {
339
+ function applyNavigationLocale(url, locale, replaceExistingLocale = false) {
300
340
  if (!locale || typeof window === "undefined") return url;
301
341
  if (isAbsoluteOrProtocolRelativeUrl(url)) return url;
302
- if (getLocalePathPrefix(url, window.__VINEXT_LOCALES__)) return url;
303
- const domainLocalePath = getDomainLocalePath(url, locale);
342
+ if (!replaceExistingLocale && getLocalePathPrefix(url, window.__VINEXT_LOCALES__)) return url;
343
+ const normalizedUrl = replaceExistingLocale ? removeNavigationLocalePrefix(url) : url;
344
+ const domainLocalePath = getDomainLocalePath(normalizedUrl, locale);
304
345
  if (domainLocalePath) return domainLocalePath;
305
- return addLocalePrefix(url, locale, window.__VINEXT_DEFAULT_LOCALE__ ?? "");
346
+ return addLocalePrefix(normalizedUrl, locale, window.__VINEXT_DEFAULT_LOCALE__ ?? "");
347
+ }
348
+ function removeNavigationLocalePrefix(url) {
349
+ const locales = window.__VINEXT_LOCALES__;
350
+ if (!locales?.length) return url;
351
+ try {
352
+ const parsed = new URL(url, "http://vinext.local");
353
+ const locale = getLocalePathPrefix(parsed.pathname, locales);
354
+ if (!locale) return url;
355
+ return `${parsed.pathname.slice(locale.length + 1) || "/"}${parsed.search}${parsed.hash}`;
356
+ } catch {
357
+ return url;
358
+ }
306
359
  }
307
360
  function isDefaultLocaleRootNavigation(url, locale) {
308
361
  if (typeof window === "undefined") return false;
@@ -640,9 +693,23 @@ function getPathnameAndQuery() {
640
693
  ...searchQuery,
641
694
  ...routeQuery
642
695
  },
643
- asPath: resolvedPath + window.location.search + window.location.hash
696
+ asPath: getCurrentHistoryAsPath() ?? resolvedPath + window.location.search + window.location.hash
644
697
  };
645
698
  }
699
+ function getCurrentHistoryAsPath() {
700
+ const state = window.history?.state;
701
+ if (!isNextRouterState(state) || typeof state.as !== "string") return null;
702
+ try {
703
+ const browserUrl = new URL(window.location.href);
704
+ const stateUrl = new URL(toBrowserNavigationHref(state.as, window.location.href, __basePath), window.location.href);
705
+ if (stateUrl.pathname !== browserUrl.pathname || stateUrl.search !== browserUrl.search) return null;
706
+ const stateAs = stripHash(state.as);
707
+ const visibleAs = `${stripBasePath(window.location.pathname, __basePath)}${window.location.search}`;
708
+ return `${stateAs || visibleAs}${window.location.hash}`;
709
+ } catch {
710
+ return null;
711
+ }
712
+ }
646
713
  function getPagesNavigationIsReadyFromSerializedState(routePattern, searchString, nextData) {
647
714
  if (!routePattern) return true;
648
715
  if (nextData?.gssp === true || nextData?.gip === true || nextData?.isExperimentalCompile === true || nextData?.appGip === true && nextData.gsp !== true) return true;
@@ -672,6 +739,10 @@ function markPagesRouterReady() {
672
739
  routerRuntimeState.pagesRouterReady = true;
673
740
  return true;
674
741
  }
742
+ function initializePagesRouterReadyFromNextData(nextData) {
743
+ if (typeof window === "undefined") return;
744
+ routerRuntimeState.pagesRouterReady = getPagesNavigationIsReadyFromSerializedState(nextData.page, window.location.search, nextData);
745
+ }
675
746
  function markPagesRouterHydrated() {
676
747
  if (typeof window === "undefined" || window.__NEXT_HYDRATED === true) return;
677
748
  const hydratedAt = performance.now();
@@ -792,10 +863,13 @@ async function resolveMiddlewareDataEffect(browserUrl, signal) {
792
863
  if (!dataUrl) return null;
793
864
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
794
865
  try {
795
- const res = await dedupedPagesDataFetch(dataUrl, { headers: {
796
- Accept: "application/json",
797
- "x-nextjs-data": "1"
798
- } });
866
+ const res = await dedupedPagesDataFetch(dataUrl, {
867
+ headers: {
868
+ Accept: "application/json",
869
+ "x-nextjs-data": "1"
870
+ },
871
+ signal
872
+ });
799
873
  return {
800
874
  redirectLocation: res.headers.get("x-nextjs-redirect"),
801
875
  rewriteTarget: res.headers.get("x-nextjs-rewrite"),
@@ -851,7 +925,10 @@ async function navigateClientData(url, initialTarget, controller, navId, assertS
851
925
  };
852
926
  const deploymentId = getDeploymentId();
853
927
  if (deploymentId) headers[NEXT_DEPLOYMENT_ID_HEADER] = deploymentId;
854
- res = await dedupedPagesDataFetch(initialTarget.dataHref, { headers });
928
+ res = await dedupedPagesDataFetch(initialTarget.dataHref, {
929
+ headers,
930
+ signal: controller.signal
931
+ });
855
932
  } catch (err) {
856
933
  if (err instanceof DOMException && err.name === "AbortError") throw new NavigationCancelledError(url);
857
934
  throw err;
@@ -1043,7 +1120,8 @@ async function navigateClientHtml(url, fetchUrl, controller, navId, assertStillC
1043
1120
  */
1044
1121
  async function navigateClient(url, fetchUrl = url, options = {}) {
1045
1122
  if (typeof window === "undefined") return;
1046
- routerRuntimeState.activeAbortController?.abort();
1123
+ const previousAbortController = routerRuntimeState.activeAbortController;
1124
+ if (previousAbortController) queueMicrotask(() => previousAbortController.abort());
1047
1125
  cancelPreviousRenderCommit();
1048
1126
  const controller = new AbortController();
1049
1127
  routerRuntimeState.activeAbortController = controller;
@@ -1222,7 +1300,8 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
1222
1300
  assertSafeNavigationUrl(resolveUrl(url));
1223
1301
  if (as !== void 0) assertSafeNavigationUrl(String(as));
1224
1302
  const navigationLocale = resolveTransitionLocale(options?.locale);
1225
- let resolved = resolveNavigationTarget(url, as, navigationLocale);
1303
+ let resolved = resolveNavigationTarget(url, as, navigationLocale, as === void 0 && options?.locale !== void 0 && typeof url !== "string" && url.pathname === void 0 && (url.query !== void 0 && Object.keys(url.query).length > 0 || typeof url.search === "string" && url.search.length > 0 || typeof url.hash === "string" && url.hash.length > 0));
1304
+ if (as === void 0 && (typeof url === "string" && options?._vinextInterpolateDynamicRoute === true || typeof url !== "string" && url.pathname === void 0 && (url.query !== void 0 && Object.keys(url.query).length > 0 || typeof url.search === "string" && url.search.length > 0))) resolved = interpolateCurrentDynamicRoute(resolved);
1226
1305
  if (isExternalUrl(resolved)) {
1227
1306
  const localPath = toSameOriginAppPath(resolved, __basePath);
1228
1307
  if (localPath == null) {
@@ -1660,4 +1739,4 @@ if (typeof window !== "undefined") {
1660
1739
  const _PAGES_NAVIGATION_ACCESSOR_KEY = Symbol.for("vinext.navigation.pagesNavigationContextAccessor");
1661
1740
  globalThis[_PAGES_NAVIGATION_ACCESSOR_KEY] = getPagesNavigationContext;
1662
1741
  //#endregion
1663
- export { markPagesRouterReady as _markPagesRouterReady, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
1742
+ export { initializePagesRouterReadyFromNextData as _initializePagesRouterReadyFromNextData, markPagesRouterReady as _markPagesRouterReady, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };