vinext 1.0.0-beta.11 → 1.0.0-beta.13

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 (170) hide show
  1. package/README.md +39 -12
  2. package/dist/build/action-owner-manifest.js +23 -5
  3. package/dist/build/lifecycle.d.ts +42 -0
  4. package/dist/build/lifecycle.js +246 -0
  5. package/dist/build/nitro-route-rules.js +1 -1
  6. package/dist/build/pages-client-assets-module.d.ts +0 -3
  7. package/dist/build/pages-client-assets-module.js +1 -13
  8. package/dist/build/precompress.js +1 -1
  9. package/dist/build/prerender-paths.d.ts +1 -1
  10. package/dist/build/prerender-paths.js +6 -6
  11. package/dist/build/prerender-server-pool.js +1 -1
  12. package/dist/build/prerender.d.ts +1 -1
  13. package/dist/build/prerender.js +5 -5
  14. package/dist/build/preview-credentials.d.ts +1 -1
  15. package/dist/build/preview-credentials.js +2 -2
  16. package/dist/build/report.js +1 -1
  17. package/dist/build/run-prerender.d.ts +2 -2
  18. package/dist/build/run-prerender.js +5 -5
  19. package/dist/build/standalone.js +2 -2
  20. package/dist/cli-args.d.ts +5 -13
  21. package/dist/cli-args.js +2 -45
  22. package/dist/cli-dev-config.d.ts +6 -2
  23. package/dist/cli-dev-config.js +160 -3
  24. package/dist/cli.js +104 -439
  25. package/dist/config/config-matchers.js +1 -1
  26. package/dist/config/next-config.d.ts +2 -2
  27. package/dist/config/next-config.js +5 -5
  28. package/dist/config/prerender.d.ts +3 -0
  29. package/dist/config/prerender.js +8 -2
  30. package/dist/entries/app-browser-entry.js +1 -1
  31. package/dist/entries/app-rsc-entry.d.ts +2 -0
  32. package/dist/entries/app-rsc-entry.js +25 -5
  33. package/dist/entries/pages-client-entry.d.ts +1 -0
  34. package/dist/entries/pages-client-entry.js +17 -2
  35. package/dist/index.d.ts +6 -8
  36. package/dist/index.js +282 -105
  37. package/dist/init-cloudflare.d.ts +3 -2
  38. package/dist/init-cloudflare.js +117 -5
  39. package/dist/init-platform.d.ts +2 -0
  40. package/dist/init-platform.js +9 -3
  41. package/dist/init.d.ts +7 -5
  42. package/dist/init.js +41 -22
  43. package/dist/plugins/dynamic-preload-metadata.js +1 -1
  44. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  45. package/dist/plugins/fonts.js +1 -1
  46. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  47. package/dist/plugins/import-meta-url.js +1 -1
  48. package/dist/plugins/instrumentation-client.js +1 -1
  49. package/dist/plugins/og-assets.js +32 -13
  50. package/dist/plugins/og-harfbuzz.d.ts +24 -0
  51. package/dist/plugins/og-harfbuzz.js +260 -0
  52. package/dist/plugins/optimize-imports.js +1 -1
  53. package/dist/plugins/require-condition-resolution.js +2 -2
  54. package/dist/plugins/require-context.js +2 -2
  55. package/dist/plugins/typeof-window.js +1 -1
  56. package/dist/plugins/use-cache-callable.d.ts +0 -2
  57. package/dist/plugins/use-cache-callable.js +40 -22
  58. package/dist/plugins/worker-image-imports.js +1 -1
  59. package/dist/routing/app-route-graph.js +1 -1
  60. package/dist/routing/app-router.js +1 -1
  61. package/dist/routing/pages-router.js +1 -1
  62. package/dist/server/app-action-forwarding.js +1 -1
  63. package/dist/server/app-browser-entry.js +1 -0
  64. package/dist/server/app-browser-error.js +1 -1
  65. package/dist/server/app-elements-wire.js +2 -2
  66. package/dist/server/app-middleware.js +1 -1
  67. package/dist/server/app-optimistic-routing.js +1 -1
  68. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  69. package/dist/server/app-page-cache-finalizer.js +14 -2
  70. package/dist/server/app-page-dispatch.js +1 -1
  71. package/dist/server/app-page-element-builder.js +3 -2
  72. package/dist/server/app-page-probe.js +3 -3
  73. package/dist/server/app-page-render.js +1 -0
  74. package/dist/server/app-page-request.d.ts +2 -0
  75. package/dist/server/app-page-request.js +16 -12
  76. package/dist/server/app-request-stage-dispatch.js +1 -1
  77. package/dist/server/app-request-stage-independent-entry.js +2 -2
  78. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  79. package/dist/server/app-route-handler-dispatch.js +3 -2
  80. package/dist/server/app-route-handler-execution.d.ts +1 -0
  81. package/dist/server/app-route-handler-execution.js +2 -2
  82. package/dist/server/app-route-handler-runtime.js +1 -1
  83. package/dist/server/app-router-entry.js +2 -2
  84. package/dist/server/app-rsc-handler.d.ts +9 -2
  85. package/dist/server/app-rsc-handler.js +30 -10
  86. package/dist/server/app-rsc-response-stage.js +6 -3
  87. package/dist/server/app-rsc-route-matching.js +1 -1
  88. package/dist/server/app-server-action-execution.js +35 -6
  89. package/dist/server/app-ssr-entry.js +7 -2
  90. package/dist/server/app-ssr-stream.js +25 -6
  91. package/dist/server/app-worker-stages.d.ts +4 -1
  92. package/dist/server/app-worker-stages.js +4 -4
  93. package/dist/server/artifact-compatibility.js +1 -1
  94. package/dist/server/client-reuse-manifest.js +1 -1
  95. package/dist/server/dev-lockfile.d.ts +1 -1
  96. package/dist/server/dev-lockfile.js +4 -1
  97. package/dist/server/dev-server.d.ts +1 -1
  98. package/dist/server/dev-server.js +28 -6
  99. package/dist/server/dev-stack-sourcemap.js +1 -1
  100. package/dist/server/html.d.ts +0 -20
  101. package/dist/server/html.js +16 -1
  102. package/dist/server/instrumentation.d.ts +4 -9
  103. package/dist/server/instrumentation.js +10 -15
  104. package/dist/server/isr-cache.js +3 -3
  105. package/dist/server/metadata-route-response.d.ts +1 -0
  106. package/dist/server/metadata-route-response.js +65 -0
  107. package/dist/server/middleware-runtime.js +3 -3
  108. package/dist/server/pages-asset-tags.d.ts +1 -0
  109. package/dist/server/pages-asset-tags.js +37 -2
  110. package/dist/server/pages-client-assets.d.ts +7 -0
  111. package/dist/server/pages-data-route.js +1 -1
  112. package/dist/server/pages-dev-hydration.d.ts +6 -0
  113. package/dist/server/pages-dev-hydration.js +19 -7
  114. package/dist/server/pages-document-initial-props.d.ts +1 -0
  115. package/dist/server/pages-document-initial-props.js +1 -1
  116. package/dist/server/pages-page-data.js +2 -2
  117. package/dist/server/pages-page-handler.js +5 -2
  118. package/dist/server/pages-page-response.d.ts +1 -0
  119. package/dist/server/pages-page-response.js +3 -2
  120. package/dist/server/pages-request-pipeline.js +3 -3
  121. package/dist/server/pages-request-stage-entry.js +3 -3
  122. package/dist/server/pages-revalidate.js +1 -1
  123. package/dist/server/prerender-route-params.js +1 -1
  124. package/dist/server/prod-server.js +19 -13
  125. package/dist/server/request-pipeline.js +2 -2
  126. package/dist/server/rsc-stream-hints.d.ts +12 -0
  127. package/dist/server/rsc-stream-hints.js +70 -35
  128. package/dist/server/seed-cache.js +3 -4
  129. package/dist/server/socket-error-backstop.js +1 -1
  130. package/dist/shims/cache-callable-runtime.d.ts +38 -0
  131. package/dist/shims/cache-callable-runtime.js +162 -11
  132. package/dist/shims/cache-runtime.d.ts +6 -8
  133. package/dist/shims/cache-runtime.js +89 -24
  134. package/dist/shims/constants.js +1 -1
  135. package/dist/shims/dynamic-preload-chunks.js +6 -2
  136. package/dist/shims/font-local.js +1 -1
  137. package/dist/shims/form.js +1 -1
  138. package/dist/shims/head.js +1 -1
  139. package/dist/shims/headers.js +2 -2
  140. package/dist/shims/image-config.js +2 -0
  141. package/dist/shims/internal/app-page-props-cache-key.d.ts +29 -0
  142. package/dist/shims/internal/app-page-props-cache-key.js +53 -1
  143. package/dist/shims/internal/format-url-object.d.ts +7 -0
  144. package/dist/shims/internal/format-url-object.js +53 -0
  145. package/dist/shims/internal/normalize-router-href.d.ts +15 -0
  146. package/dist/shims/internal/normalize-router-href.js +41 -0
  147. package/dist/shims/internal/pages-data-target.d.ts +4 -1
  148. package/dist/shims/internal/pages-data-target.js +28 -35
  149. package/dist/shims/internal/route-pattern-for-warning.d.ts +2 -2
  150. package/dist/shims/internal/route-pattern-for-warning.js +3 -3
  151. package/dist/shims/link.js +31 -92
  152. package/dist/shims/metadata.js +5 -4
  153. package/dist/shims/navigation.js +2 -2
  154. package/dist/shims/router-state.js +1 -1
  155. package/dist/shims/router.d.ts +8 -7
  156. package/dist/shims/router.js +312 -148
  157. package/dist/shims/script-nonce-context.d.ts +4 -1
  158. package/dist/shims/script-nonce-context.js +15 -6
  159. package/dist/shims/server.js +3 -3
  160. package/dist/utils/built-asset-url.js +1 -1
  161. package/dist/utils/client-runtime-metadata.d.ts +6 -0
  162. package/dist/utils/client-runtime-metadata.js +16 -0
  163. package/dist/utils/lazy-chunks.js +1 -1
  164. package/dist/utils/project.d.ts +1 -11
  165. package/dist/utils/project.js +20 -29
  166. package/dist/utils/react-version.d.ts +3 -1
  167. package/dist/utils/react-version.js +19 -1
  168. package/dist/utils/vite-cli-invocation.d.ts +21 -0
  169. package/dist/utils/vite-cli-invocation.js +144 -0
  170. package/package.json +10 -2
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
3
- import { useScriptNonce } from "./script-nonce-context.js";
3
+ import { useInitialStylesheetHrefs, useScriptNonce } from "./script-nonce-context.js";
4
4
  import React from "react";
5
5
  import { getPagesClientAssets } from "vinext/server/pages-client-assets";
6
6
  //#region src/shims/dynamic-preload-chunks.tsx
@@ -47,18 +47,22 @@ function resolveDynamicPreloadFiles(moduleIds) {
47
47
  }
48
48
  function DynamicPreloadChunks(props) {
49
49
  const nonce = useScriptNonce();
50
+ const initialStylesheetHrefs = useInitialStylesheetHrefs();
50
51
  if (typeof window !== "undefined") return null;
51
52
  const files = resolveDynamicPreloadFiles(props.moduleIds);
52
53
  if (files.length === 0) return null;
54
+ const crossOrigin = getPagesClientAssets().crossOrigin ?? "";
53
55
  const preloadLinks = [];
54
56
  for (const file of files) {
55
57
  const assetHref = dynamicPreloadHref(file);
56
58
  if (assetHref.endsWith(".css")) {
57
59
  const href = appendAssetDeploymentIdQuery(assetHref);
60
+ if (initialStylesheetHrefs?.has(href)) continue;
58
61
  preloadLinks.push(React.createElement("link", {
59
62
  key: href,
60
63
  rel: "stylesheet",
61
64
  href,
65
+ crossOrigin,
62
66
  nonce,
63
67
  precedence: "dynamic"
64
68
  }));
@@ -69,7 +73,7 @@ function DynamicPreloadChunks(props) {
69
73
  rel: "modulepreload",
70
74
  href: assetHref,
71
75
  as: "script",
72
- crossOrigin: "",
76
+ crossOrigin,
73
77
  fetchPriority: "low",
74
78
  nonce
75
79
  }));
@@ -1,5 +1,5 @@
1
- import { fnv1a64 } from "../utils/hash.js";
2
1
  import { escapeCSSString, formatFontClassRule, getFontMimeType, resolveSingleFaceStyle, sanitizeCSSVarName, sanitizeFallback, sanitizeFontDescriptorValue } from "./font-utils.js";
2
+ import { fnv1a64 } from "../utils/hash.js";
3
3
  //#region src/shims/font-local.ts
4
4
  /**
5
5
  * next/font/local shim
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { isBotUserAgent } from "../utils/html-limited-bots.js";
2
3
  import { VINEXT_MOUNTED_SLOTS_HEADER } from "../server/app-rsc-vary.js";
3
4
  import "../server/headers.js";
4
5
  import { assertSafeNavigationUrl } from "./url-safety.js";
@@ -6,7 +7,6 @@ import { APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL } from "../server/app-rsc-re
6
7
  import { AppElementsWire } from "../server/app-elements-wire.js";
7
8
  import "../server/app-elements.js";
8
9
  import { withBasePath } from "./url-utils.js";
9
- import { isBotUserAgent } from "../utils/html-limited-bots.js";
10
10
  import { createRscRequestHeaders, createRscRequestUrl } from "../server/app-rsc-cache-busting.js";
11
11
  import { hasAppNavigationRuntime } from "../client/navigation-runtime.js";
12
12
  import { getMountedSlotsHeader, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
@@ -75,7 +75,7 @@ function getSSRHeadHTML() {
75
75
  ...defaultHead(),
76
76
  ..._getSSRHeadChildren(),
77
77
  ..._getDocumentInitialHead()
78
- ]).map((child) => headChildToHTML(child.type, child.props)).filter(Boolean).join("\n ");
78
+ ]).map((child) => headChildToHTML(child.type, child.props)).filter(Boolean).join("");
79
79
  }
80
80
  /**
81
81
  * Tags allowed inside <head>. Anything else is silently dropped.
@@ -1,9 +1,9 @@
1
1
  import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
- import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
4
3
  import { MIDDLEWARE_SET_COOKIE_HEADER } from "../utils/protocol-headers.js";
5
- import { FLIGHT_HEADERS, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER } from "../server/headers.js";
6
4
  import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
5
+ import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
6
+ import { FLIGHT_HEADERS, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER } from "../server/headers.js";
7
7
  import { serializeSetCookie, validateCookieName } from "./internal/cookie-serialize.js";
8
8
  import { createPprFallbackShellSuspensePromise } from "./ppr-fallback-shell.js";
9
9
  //#region src/shims/headers.ts
@@ -59,6 +59,7 @@ function matchRemotePattern(pattern, url) {
59
59
  function hasRemoteMatch(domains, remotePatterns, url) {
60
60
  return domains.some((domain) => url.hostname === domain) || remotePatterns.some((p) => matchRemotePattern(p, url));
61
61
  }
62
+ const IPV4_LITERAL_CHARS_RE = /^[0-9a-fA-FxX.]+$/;
62
63
  /**
63
64
  * Determine whether a string is a private (non-routable) IP address.
64
65
  * Works for IPv4 and IPv6, including bracketed and IPv4-mapped forms.
@@ -73,6 +74,7 @@ function hasRemoteMatch(domains, remotePatterns, url) {
73
74
  */
74
75
  function isPrivateIp(ip) {
75
76
  if (ip.startsWith("[") && ip.endsWith("]")) ip = ip.slice(1, -1);
77
+ if (!ip.includes(":") && !IPV4_LITERAL_CHARS_RE.test(ip)) return false;
76
78
  try {
77
79
  const parsed = ipaddr.parse(ip);
78
80
  if (parsed instanceof ipaddr.IPv6 && parsed.isIPv4MappedAddress()) return parsed.toIPv4Address().range() !== "unicast";
@@ -1,4 +1,33 @@
1
1
  //#region src/shims/internal/app-page-props-cache-key.d.ts
2
+ /**
3
+ * Enumerable page marker, matching Next.js's `$$isPage` prop. It must be an
4
+ * ordinary string key: React's createElement drops symbol and non-enumerable
5
+ * props before a server component is invoked.
6
+ */
7
+ export declare const APP_PAGE_USE_CACHE_MARKER = "$$isPage";
2
8
  export declare function markAppPagePropsForUseCache<T extends object>(props: T): T;
3
9
  export declare function isMarkedAppPagePropsObject(value: object): boolean;
10
+ /**
11
+ * Whether `fn` is a transformed `"use cache"` function, including a bound
12
+ * one. Like Next.js's `isUseCacheFunction`, this survives `.bind()`: React's
13
+ * server-reference `bind` copies `$$typeof`/`$$id` to the bound function but
14
+ * not vinext's own marker symbol.
15
+ */
16
+ export declare function isUseCacheFunctionReference(fn: unknown): boolean;
17
+ /**
18
+ * Add Next.js's `$$isPage` marker when `fn` is a `"use cache"` function
19
+ * invoked as a page component or as a page's generateMetadata/generateViewport
20
+ * (create-component-tree.tsx, resolve-metadata.ts `createSegmentProps`). The
21
+ * cache wrapper reads and removes it, so page semantics follow the invocation
22
+ * even when the cached function is defined in (or re-exported from) another
23
+ * module, or bound.
24
+ */
25
+ export declare function withUseCachePageMarker<T extends Record<string, unknown>>(fn: unknown, props: T): T;
26
+ /**
27
+ * Whether `value` is page props carrying the `$$isPage` marker. Only plain
28
+ * objects qualify: the framework always passes a plain props object, and
29
+ * reading the marker through a thenable params/searchParams proxy would be
30
+ * observed as a param access.
31
+ */
32
+ export declare function hasUseCachePageMarker(value: unknown): value is Record<string, unknown>;
4
33
  //#endregion
@@ -1,5 +1,20 @@
1
1
  //#region src/shims/internal/app-page-props-cache-key.ts
2
2
  const APP_PAGE_PROPS_CACHE_KEY_MARKER = Symbol.for("vinext.appPagePropsCacheKeyMarker");
3
+ const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
4
+ const SERVER_REFERENCE_TAG = Symbol.for("react.server.reference");
5
+ /**
6
+ * Server reference ids of transformed cache functions end in the export name
7
+ * the use-cache transform assigns (`secureExportName` in
8
+ * plugins/use-cache-callable.ts): `$$vinext_cache_` plus an HMAC-SHA256 hex
9
+ * digest. `"use server"` actions keep their own export names.
10
+ */
11
+ const USE_CACHE_SERVER_REFERENCE_ID_RE = /#\$\$vinext_cache_[0-9a-f]{64}$/;
12
+ /**
13
+ * Enumerable page marker, matching Next.js's `$$isPage` prop. It must be an
14
+ * ordinary string key: React's createElement drops symbol and non-enumerable
15
+ * props before a server component is invoked.
16
+ */
17
+ const APP_PAGE_USE_CACHE_MARKER = "$$isPage";
3
18
  function markAppPagePropsForUseCache(props) {
4
19
  Object.defineProperty(props, APP_PAGE_PROPS_CACHE_KEY_MARKER, {
5
20
  configurable: false,
@@ -12,5 +27,42 @@ function markAppPagePropsForUseCache(props) {
12
27
  function isMarkedAppPagePropsObject(value) {
13
28
  return Reflect.get(value, APP_PAGE_PROPS_CACHE_KEY_MARKER) === true;
14
29
  }
30
+ /**
31
+ * Whether `fn` is a transformed `"use cache"` function, including a bound
32
+ * one. Like Next.js's `isUseCacheFunction`, this survives `.bind()`: React's
33
+ * server-reference `bind` copies `$$typeof`/`$$id` to the bound function but
34
+ * not vinext's own marker symbol.
35
+ */
36
+ function isUseCacheFunctionReference(fn) {
37
+ if (typeof fn !== "function") return false;
38
+ if (Reflect.get(fn, USE_CACHE_FUNCTION_SYMBOL) === true) return true;
39
+ const reference = fn;
40
+ return reference.$$typeof === SERVER_REFERENCE_TAG && typeof reference.$$id === "string" && USE_CACHE_SERVER_REFERENCE_ID_RE.test(reference.$$id);
41
+ }
42
+ /**
43
+ * Add Next.js's `$$isPage` marker when `fn` is a `"use cache"` function
44
+ * invoked as a page component or as a page's generateMetadata/generateViewport
45
+ * (create-component-tree.tsx, resolve-metadata.ts `createSegmentProps`). The
46
+ * cache wrapper reads and removes it, so page semantics follow the invocation
47
+ * even when the cached function is defined in (or re-exported from) another
48
+ * module, or bound.
49
+ */
50
+ function withUseCachePageMarker(fn, props) {
51
+ return isUseCacheFunctionReference(fn) ? {
52
+ ...props,
53
+ [APP_PAGE_USE_CACHE_MARKER]: true
54
+ } : props;
55
+ }
56
+ /**
57
+ * Whether `value` is page props carrying the `$$isPage` marker. Only plain
58
+ * objects qualify: the framework always passes a plain props object, and
59
+ * reading the marker through a thenable params/searchParams proxy would be
60
+ * observed as a param access.
61
+ */
62
+ function hasUseCachePageMarker(value) {
63
+ if (value === null || typeof value !== "object") return false;
64
+ const prototype = Object.getPrototypeOf(value);
65
+ return (prototype === Object.prototype || prototype === null) && value["$$isPage"] === true;
66
+ }
15
67
  //#endregion
16
- export { isMarkedAppPagePropsObject, markAppPagePropsForUseCache };
68
+ export { APP_PAGE_USE_CACHE_MARKER, hasUseCachePageMarker, isMarkedAppPagePropsObject, isUseCacheFunctionReference, markAppPagePropsForUseCache, withUseCachePageMarker };
@@ -0,0 +1,7 @@
1
+ import { UrlObject } from "node:url";
2
+ //#region src/shims/internal/format-url-object.d.ts
3
+ /** Format Node's public UrlObject shape with the semantics used by Next.js. */
4
+ export declare function formatUrlObject(url: UrlObject): string;
5
+ /** Format a Pages Router UrlObject and surface Next.js's development diagnostics. */
6
+ export declare function formatUrlObjectWithValidation(url: UrlObject): string;
7
+ //#endregion
@@ -0,0 +1,53 @@
1
+ import { urlQueryToSearchParams } from "../../utils/query.js";
2
+ //#region src/shims/internal/format-url-object.ts
3
+ const slashedProtocols = /https?|ftp|gopher|file/;
4
+ const urlObjectKeys = /* @__PURE__ */ new Set([
5
+ "auth",
6
+ "hash",
7
+ "host",
8
+ "hostname",
9
+ "href",
10
+ "path",
11
+ "pathname",
12
+ "port",
13
+ "protocol",
14
+ "query",
15
+ "search",
16
+ "slashes"
17
+ ]);
18
+ /** Format Node's public UrlObject shape with the semantics used by Next.js. */
19
+ function formatUrlObject(url) {
20
+ let { auth, hostname } = url;
21
+ let protocol = url.protocol || "";
22
+ let pathname = url.pathname || "";
23
+ let hash = url.hash || "";
24
+ let query = url.query || "";
25
+ let host = false;
26
+ auth = auth ? `${encodeURIComponent(auth).replace(/%3A/i, ":")}@` : "";
27
+ if (url.host) host = auth + url.host;
28
+ else if (hostname) {
29
+ host = auth + (hostname.includes(":") ? `[${hostname}]` : hostname);
30
+ if (url.port) host += `:${url.port}`;
31
+ }
32
+ if (query && typeof query === "object") query = String(urlQueryToSearchParams(query));
33
+ let search = url.search || query && `?${query}` || "";
34
+ if (protocol && !protocol.endsWith(":")) protocol += ":";
35
+ if (url.slashes || (!protocol || slashedProtocols.test(protocol)) && host !== false) {
36
+ host = `//${host || ""}`;
37
+ if (pathname && !pathname.startsWith("/")) pathname = `/${pathname}`;
38
+ } else if (!host) host = "";
39
+ if (hash && !hash.startsWith("#")) hash = `#${hash}`;
40
+ if (search && !search.startsWith("?")) search = `?${search}`;
41
+ pathname = pathname.replace(/[?#]/g, encodeURIComponent);
42
+ search = search.replace("#", "%23");
43
+ return `${protocol}${host}${pathname}${search}${hash}`;
44
+ }
45
+ /** Format a Pages Router UrlObject and surface Next.js's development diagnostics. */
46
+ function formatUrlObjectWithValidation(url) {
47
+ if (process.env.NODE_ENV === "development") {
48
+ for (const key of Object.keys(url)) if (!urlObjectKeys.has(key)) console.warn(`Unknown key passed via urlObject into url.format: ${key}`);
49
+ }
50
+ return formatUrlObject(url);
51
+ }
52
+ //#endregion
53
+ export { formatUrlObject, formatUrlObjectWithValidation };
@@ -0,0 +1,15 @@
1
+ //#region src/shims/internal/normalize-router-href.d.ts
2
+ /**
3
+ * Warn about and normalize repeated path separators like Next.js `resolveHref`.
4
+ * The protocol separator is preserved and query strings are left untouched.
5
+ *
6
+ * Ported from Next.js: packages/next/src/client/resolve-href.ts
7
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/resolve-href.ts
8
+ */
9
+ export declare function normalizeRouterHref(href: string, routePathname: string): string;
10
+ /** Resolve a Pages Router href against the route state used by Next.js Link. */
11
+ export declare function resolvePagesRouterHref(href: string, router: {
12
+ pathname: string;
13
+ asPath: string;
14
+ }, basePath: string): string;
15
+ //#endregion
@@ -0,0 +1,41 @@
1
+ import { hasBasePath } from "../../utils/base-path.js";
2
+ import { getWindowOrigin, isAbsoluteUrl } from "../url-utils.js";
3
+ //#region src/shims/internal/normalize-router-href.ts
4
+ /**
5
+ * Warn about and normalize repeated path separators like Next.js `resolveHref`.
6
+ * The protocol separator is preserved and query strings are left untouched.
7
+ *
8
+ * Ported from Next.js: packages/next/src/client/resolve-href.ts
9
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/client/resolve-href.ts
10
+ */
11
+ function normalizeRouterHref(href, routePathname) {
12
+ const protocol = href.match(/^[a-z][a-z0-9+.-]*:\/\//i)?.[0] ?? "";
13
+ const withoutProtocol = protocol ? href.slice(protocol.length) : href;
14
+ if (!/(\/\/|\\)/.test(withoutProtocol.split("?", 1)[0] ?? "")) return href;
15
+ console.error(`Invalid href '${href}' passed to next/router in page: '${routePathname}'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.`);
16
+ const [pathname, ...query] = withoutProtocol.split("?");
17
+ return protocol + pathname.replace(/\\/g, "/").replace(/\/\/+/g, "/") + (query[0] ? `?${query.join("?")}` : "");
18
+ }
19
+ /** Resolve a Pages Router href against the route state used by Next.js Link. */
20
+ function resolvePagesRouterHref(href, router, basePath) {
21
+ const normalized = normalizeRouterHref(href, router.pathname);
22
+ if (isAbsoluteUrl(normalized)) {
23
+ const origin = getWindowOrigin();
24
+ if (!origin) return normalized;
25
+ try {
26
+ const target = new URL(normalized, origin);
27
+ if (target.origin !== origin || basePath !== "" && !hasBasePath(target.pathname, basePath)) return normalized;
28
+ } catch {
29
+ return normalized;
30
+ }
31
+ }
32
+ try {
33
+ const base = new URL(normalized.startsWith("#") || normalized.startsWith("?") ? router.asPath : router.pathname, "http://n");
34
+ const resolved = new URL(normalized, base);
35
+ return resolved.origin === base.origin ? resolved.href.slice(resolved.origin.length) : resolved.href;
36
+ } catch {
37
+ return normalized;
38
+ }
39
+ }
40
+ //#endregion
41
+ export { normalizeRouterHref, resolvePagesRouterHref };
@@ -35,8 +35,11 @@ export type PagesDataTarget = {
35
35
  };
36
36
  type PagesDataNavigationTargetOptions = {
37
37
  locale?: string | false;
38
+ /** Force page identity while deriving params and data URLs from browserUrl. */
39
+ routePattern?: string;
38
40
  };
39
- export declare function getPagesMiddlewareDataHref(browserUrl: string, basePath: string, options?: PagesDataNavigationTargetOptions): string | null;
41
+ export declare function matchesPagesMiddlewarePath(pathname: string): boolean;
42
+ export declare function getPagesMiddlewareDataHref(browserUrl: string, basePath: string, options?: PagesDataNavigationTargetOptions, routeUrl?: string): string | null;
40
43
  /**
41
44
  * Decide whether the JSON data-endpoint navigation path is usable for this
42
45
  * browser URL. We require:
@@ -1,6 +1,6 @@
1
- import { removeTrailingSlash, stripBasePath } from "../../utils/base-path.js";
2
- import { getLocalePathPrefix } from "../../utils/domain-locale.js";
3
1
  import { isUnknownRecord } from "../../utils/record.js";
2
+ import { stripBasePath } from "../../utils/base-path.js";
3
+ import { getLocalePathPrefix } from "../../utils/domain-locale.js";
4
4
  import { buildPagesDataHref, matchPagesPattern } from "./pages-data-url.js";
5
5
  import { NEXT_DEPLOYMENT_ID_HEADER, getDeploymentId } from "../../utils/deployment-id.js";
6
6
  import { fetchStaticPagesData, fetchUncachedPagesData } from "./pages-data-fetch-dedup.js";
@@ -22,10 +22,9 @@ function hasVinextMiddleware(nextData) {
22
22
  }
23
23
  function isClientMiddlewareMatcherObject(value) {
24
24
  if (!isUnknownRecord(value)) return false;
25
- if (typeof value.source !== "string") return false;
25
+ if (typeof value.regexp !== "string") return false;
26
+ if (value.flags !== void 0 && typeof value.flags !== "string") return false;
26
27
  if (value.locale !== void 0 && value.locale !== false) return false;
27
- if (value.has !== void 0 && !Array.isArray(value.has)) return false;
28
- if (value.missing !== void 0 && !Array.isArray(value.missing)) return false;
29
28
  return true;
30
29
  }
31
30
  function stripLocaleForMiddlewareMatcher(pathname) {
@@ -35,40 +34,28 @@ function stripLocaleForMiddlewareMatcher(pathname) {
35
34
  if (!firstSegment || !locales.includes(firstSegment)) return pathname;
36
35
  return "/" + pathname.split("/").slice(2).join("/");
37
36
  }
38
- function clientMiddlewareSourceMatches(pathname, source) {
39
- if (!/[\\():*+?]/.test(source)) return removeTrailingSlash(pathname) === removeTrailingSlash(source);
40
- if (source.includes("(") || source.includes("\\")) return true;
41
- const sourceParts = source.split("/").filter(Boolean);
42
- const pathParts = pathname.split("/").filter(Boolean);
43
- let pathIndex = 0;
44
- for (const sourcePart of sourceParts) {
45
- if (sourcePart.startsWith(":")) {
46
- if (sourcePart.endsWith("*")) return true;
47
- if (sourcePart.endsWith("+")) return pathIndex < pathParts.length;
48
- if (pathIndex >= pathParts.length) return false;
49
- pathIndex++;
50
- continue;
51
- }
52
- if (pathParts[pathIndex] !== sourcePart) return false;
53
- pathIndex++;
54
- }
55
- return pathIndex === pathParts.length;
37
+ function localeAwareMiddlewarePathname(pathname) {
38
+ const locales = window.__VINEXT_LOCALES__;
39
+ const defaultLocale = window.__VINEXT_DEFAULT_LOCALE__;
40
+ if (!locales?.includes(defaultLocale ?? "") || getLocalePathPrefix(pathname, locales)) return pathname;
41
+ return `/${defaultLocale}${pathname === "/" ? "" : pathname}`;
56
42
  }
57
- function clientMiddlewareMatcherMatches(pathname, matcher) {
43
+ function matchesPagesMiddlewarePath(pathname) {
44
+ const matcher = window.__VINEXT_MIDDLEWARE_MATCHER__;
58
45
  if (matcher === void 0) return true;
59
- if (typeof matcher === "string") return clientMiddlewareSourceMatches(stripLocaleForMiddlewareMatcher(pathname), matcher);
60
46
  if (!Array.isArray(matcher)) return true;
61
47
  for (const item of matcher) {
62
- if (typeof item === "string") {
63
- if (clientMiddlewareSourceMatches(stripLocaleForMiddlewareMatcher(pathname), item)) return true;
64
- continue;
65
- }
66
48
  if (!isClientMiddlewareMatcherObject(item)) return true;
67
- if (clientMiddlewareSourceMatches(item.locale === false ? pathname : stripLocaleForMiddlewareMatcher(pathname), item.source)) return true;
49
+ const candidate = item.locale === false ? localeAwareMiddlewarePathname(pathname) : stripLocaleForMiddlewareMatcher(pathname);
50
+ try {
51
+ if (new RegExp(item.regexp, item.flags).test(candidate)) return true;
52
+ } catch {
53
+ return true;
54
+ }
68
55
  }
69
56
  return false;
70
57
  }
71
- function getPagesMiddlewareDataHref(browserUrl, basePath, options = {}) {
58
+ function getPagesMiddlewareDataHref(browserUrl, basePath, options = {}, routeUrl = browserUrl) {
72
59
  const nextData = window.__NEXT_DATA__;
73
60
  if (!nextData || !hasVinextMiddleware(nextData)) return null;
74
61
  const buildId = nextData.buildId;
@@ -80,13 +67,19 @@ function getPagesMiddlewareDataHref(browserUrl, basePath, options = {}) {
80
67
  return null;
81
68
  }
82
69
  if (parsed.origin !== window.location.origin) return null;
70
+ let routeSearch = parsed.search;
71
+ if (routeUrl !== browserUrl) try {
72
+ routeSearch = new URL(routeUrl, window.location.href).search;
73
+ } catch {
74
+ return null;
75
+ }
83
76
  const pathname = stripBasePath(parsed.pathname, basePath);
84
- if (!clientMiddlewareMatcherMatches(pathname, window.__VINEXT_MIDDLEWARE_MATCHER__)) return null;
77
+ if (!matchesPagesMiddlewarePath(pathname)) return null;
85
78
  const pathnameLocale = getLocalePathPrefix(pathname, window.__VINEXT_LOCALES__);
86
79
  const explicitLocale = options.locale === false ? window.__VINEXT_DEFAULT_LOCALE__ : options.locale;
87
80
  const currentLocale = pathnameLocale ?? explicitLocale ?? window.__VINEXT_LOCALE__;
88
81
  const dataPathname = pathnameLocale || !currentLocale || !window.__VINEXT_LOCALES__?.includes(currentLocale) ? pathname : `/${currentLocale}${pathname === "/" ? "" : pathname}`;
89
- return buildPagesDataHref(basePath, buildId, dataPathname, parsed.search);
82
+ return buildPagesDataHref(basePath, buildId, dataPathname, routeSearch);
90
83
  }
91
84
  /**
92
85
  * Decide whether the JSON data-endpoint navigation path is usable for this
@@ -128,7 +121,7 @@ function resolvePagesDataNavigationTarget(browserUrl, basePath, options = {}) {
128
121
  const pagePath = stripBasePath(parsed.pathname, basePath);
129
122
  const locale = getLocalePathPrefix(pagePath, window.__VINEXT_LOCALES__);
130
123
  const pathForMatch = locale ? pagePath.slice(locale.length + 1) || "/" : pagePath;
131
- const match = matchPagesPattern(pathForMatch, patterns);
124
+ const match = matchPagesPattern(pathForMatch, options.routePattern ? [options.routePattern] : patterns);
132
125
  if (!match) return null;
133
126
  const loader = loaders[match.pattern];
134
127
  if (!loader) return null;
@@ -195,4 +188,4 @@ function prefetchMiddlewareRewriteLoader(response) {
195
188
  if (target) target.loader().catch(() => {});
196
189
  }
197
190
  //#endregion
198
- export { getPagesMiddlewareDataHref, prefetchPagesData, resolvePagesDataNavigationTarget };
191
+ export { getPagesMiddlewareDataHref, matchesPagesMiddlewarePath, prefetchPagesData, resolvePagesDataNavigationTarget };
@@ -13,8 +13,8 @@
13
13
  * `installWindowNext()` side effect into every consumer of the Link shim
14
14
  * (including the App Router client bundle), clobbering `window.next.router`.
15
15
  *
16
- * On the client (or when no accessor is registered, e.g. App Router) we fall
17
- * back to `window.location.pathname`, then to `"/"`.
16
+ * On the client, Pages Router exposes the pattern through `__NEXT_DATA__.page`.
17
+ * App Router falls back to `window.location.pathname`, then to `"/"`.
18
18
  */
19
19
  /**
20
20
  * Register the server-side route-pattern accessor. Called once by the
@@ -13,8 +13,8 @@
13
13
  * `installWindowNext()` side effect into every consumer of the Link shim
14
14
  * (including the App Router client bundle), clobbering `window.next.router`.
15
15
  *
16
- * On the client (or when no accessor is registered, e.g. App Router) we fall
17
- * back to `window.location.pathname`, then to `"/"`.
16
+ * On the client, Pages Router exposes the pattern through `__NEXT_DATA__.page`.
17
+ * App Router falls back to `window.location.pathname`, then to `"/"`.
18
18
  */
19
19
  const ROUTE_PATTERN_FOR_WARNING_ACCESSOR_KEY = Symbol.for("vinext.router.routePatternForWarningAccessor");
20
20
  /**
@@ -34,7 +34,7 @@ function getCurrentRoutePathnameForWarning() {
34
34
  } catch {}
35
35
  return "/";
36
36
  }
37
- return window.location?.pathname ?? "/";
37
+ return window.__NEXT_DATA__?.page ?? window.location?.pathname ?? "/";
38
38
  }
39
39
  //#endregion
40
40
  export { getCurrentRoutePathnameForWarning, registerRoutePatternForWarningAccessor };