vinext 1.0.0-beta.3 → 1.0.0-beta.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 (90) hide show
  1. package/dist/build/precompress.js +1 -1
  2. package/dist/build/prerender.d.ts +2 -1
  3. package/dist/build/prerender.js +12 -2
  4. package/dist/cli-args.d.ts +2 -1
  5. package/dist/cli-args.js +10 -0
  6. package/dist/cli.js +14 -8
  7. package/dist/client/navigation-runtime.d.ts +6 -0
  8. package/dist/client/navigation-runtime.js +1 -1
  9. package/dist/config/dotenv.d.ts +3 -4
  10. package/dist/config/dotenv.js +30 -22
  11. package/dist/config/next-config.d.ts +4 -2
  12. package/dist/config/next-config.js +3 -0
  13. package/dist/entries/app-browser-entry.js +1 -1
  14. package/dist/entries/app-rsc-entry.js +18 -3
  15. package/dist/entries/pages-server-entry.js +4 -1
  16. package/dist/index.js +11 -7
  17. package/dist/plugins/client-reference-dedup.js +7 -5
  18. package/dist/plugins/sass.js +3 -4
  19. package/dist/server/api-handler.js +4 -3
  20. package/dist/server/app-bfcache-identity.js +28 -2
  21. package/dist/server/app-browser-entry.js +73 -45
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-navigation-controller.js +29 -4
  24. package/dist/server/app-browser-prefetch-response.d.ts +9 -0
  25. package/dist/server/app-browser-prefetch-response.js +32 -0
  26. package/dist/server/app-browser-state.d.ts +8 -3
  27. package/dist/server/app-browser-state.js +9 -3
  28. package/dist/server/app-page-dispatch.d.ts +1 -1
  29. package/dist/server/app-page-dispatch.js +6 -2
  30. package/dist/server/app-page-element-builder.d.ts +2 -1
  31. package/dist/server/app-page-element-builder.js +1 -0
  32. package/dist/server/app-page-render.d.ts +1 -1
  33. package/dist/server/app-page-render.js +13 -3
  34. package/dist/server/app-page-request.d.ts +0 -1
  35. package/dist/server/app-page-request.js +3 -12
  36. package/dist/server/app-page-response.d.ts +2 -0
  37. package/dist/server/app-page-response.js +6 -1
  38. package/dist/server/app-page-route-wiring.d.ts +3 -2
  39. package/dist/server/app-page-route-wiring.js +61 -13
  40. package/dist/server/app-pages-bridge.d.ts +2 -1
  41. package/dist/server/app-pages-bridge.js +2 -1
  42. package/dist/server/app-rsc-cache-busting.d.ts +7 -0
  43. package/dist/server/app-rsc-cache-busting.js +6 -0
  44. package/dist/server/app-rsc-handler.d.ts +2 -0
  45. package/dist/server/app-rsc-handler.js +9 -4
  46. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  47. package/dist/server/app-rsc-render-mode.js +4 -1
  48. package/dist/server/app-rsc-request-normalization.js +65 -4
  49. package/dist/server/app-server-action-execution.d.ts +1 -1
  50. package/dist/server/app-visited-response-cache.d.ts +6 -1
  51. package/dist/server/app-visited-response-cache.js +49 -1
  52. package/dist/server/dev-server.d.ts +1 -1
  53. package/dist/server/dev-server.js +61 -38
  54. package/dist/server/edge-api-runtime.d.ts +10 -1
  55. package/dist/server/edge-api-runtime.js +29 -1
  56. package/dist/server/headers.d.ts +3 -1
  57. package/dist/server/headers.js +3 -1
  58. package/dist/server/pages-api-route.d.ts +2 -0
  59. package/dist/server/pages-api-route.js +2 -2
  60. package/dist/server/pages-asset-tags.d.ts +1 -0
  61. package/dist/server/pages-asset-tags.js +6 -4
  62. package/dist/server/pages-document-asset-props.d.ts +26 -0
  63. package/dist/server/pages-document-asset-props.js +69 -0
  64. package/dist/server/pages-node-compat.js +3 -3
  65. package/dist/server/pages-page-handler.d.ts +1 -0
  66. package/dist/server/pages-page-handler.js +4 -1
  67. package/dist/server/pages-page-response.d.ts +2 -0
  68. package/dist/server/pages-page-response.js +19 -8
  69. package/dist/server/pages-router-entry.js +1 -1
  70. package/dist/server/prerender-manifest.d.ts +1 -0
  71. package/dist/server/prod-server.js +2 -1
  72. package/dist/server/seed-cache.js +1 -1
  73. package/dist/server/worker-revalidation-context.js +1 -0
  74. package/dist/shims/app-router-scroll-state.d.ts +2 -1
  75. package/dist/shims/app-router-scroll-state.js +7 -2
  76. package/dist/shims/document.js +15 -2
  77. package/dist/shims/hash-scroll.d.ts +1 -1
  78. package/dist/shims/hash-scroll.js +2 -1
  79. package/dist/shims/link.d.ts +2 -0
  80. package/dist/shims/link.js +59 -18
  81. package/dist/shims/metadata.d.ts +11 -17
  82. package/dist/shims/metadata.js +24 -32
  83. package/dist/shims/navigation.d.ts +18 -2
  84. package/dist/shims/navigation.js +69 -20
  85. package/dist/shims/request-context.d.ts +2 -1
  86. package/dist/shims/slot.d.ts +2 -1
  87. package/dist/shims/slot.js +6 -2
  88. package/dist/shims/streamed-icons.d.ts +9 -0
  89. package/dist/shims/streamed-icons.js +33 -0
  90. package/package.json +1 -1
@@ -8,6 +8,7 @@ import { encodeCacheTag } from "../utils/encode-cache-tag.js";
8
8
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
9
9
  import { withScriptNonce } from "../shims/script-nonce-context.js";
10
10
  import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
11
+ import { applyDocumentAssetProps, extractDocumentAssetProps } from "./pages-document-asset-props.js";
11
12
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
12
13
  import { setCacheStateHeaders } from "./cache-headers.js";
13
14
  import { readStreamAsText } from "../utils/text-stream.js";
@@ -54,7 +55,7 @@ function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles, scriptNonce
54
55
  let html = "";
55
56
  const nonceAttr = createNonceAttribute(scriptNonce);
56
57
  for (const link of fontLinks) html += `<link rel="stylesheet"${nonceAttr} href="${escapeHtmlAttr(appendAssetDeploymentIdQuery(link))}" />\n `;
57
- for (const preload of fontPreloads) html += `<link rel="preload"${nonceAttr} href="${escapeHtmlAttr(preload.href)}" as="font" type="${escapeHtmlAttr(preload.type)}" crossorigin />\n `;
58
+ for (const preload of fontPreloads) html += `<link rel="preload"${nonceAttr} href="${escapeHtmlAttr(preload.href)}" as="font" type="${escapeHtmlAttr(preload.type)}" crossorigin="anonymous" />\n `;
58
59
  if (fontStyles.length > 0) html += `<style data-vinext-fonts${nonceAttr}>${fontStyles.join("\n")}</style>\n `;
59
60
  return html;
60
61
  }
@@ -85,19 +86,27 @@ async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, opt
85
86
  if (options.DocumentComponent) {
86
87
  const docProps = options.resolvedDocProps ?? await loadUserDocumentInitialProps(options.DocumentComponent);
87
88
  const docElement = docProps ? React.createElement(options.DocumentComponent, docProps) : React.createElement(options.DocumentComponent);
88
- let html = await options.renderDocumentToString(docElement);
89
+ const renderedDocument = extractDocumentAssetProps(await options.renderDocumentToString(docElement));
90
+ let html = renderedDocument.html;
91
+ const generatedAssetTags = applyDocumentAssetProps(options.assetTags, renderedDocument.props, {
92
+ configuredCrossOrigin: options.crossOrigin,
93
+ scriptOwner: options.disableOptimizedLoading ? "next-script" : "head"
94
+ });
95
+ const generatedNextDataScript = applyDocumentAssetProps(nextDataScript, renderedDocument.props, { configuredCrossOrigin: options.crossOrigin });
89
96
  html = html.replace("__NEXT_MAIN__", bodyMarker);
90
- if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) html = html.replace("</head>", ` ${fontHeadHTML}${options.ssrHeadHTML}\n ${options.assetTags}\n</head>`);
91
- html = html.replace("<!-- __NEXT_SCRIPTS__ -->", nextDataScript);
92
- if (!html.includes("__NEXT_DATA__")) html = html.replace("</body>", ` ${nextDataScript}\n</body>`);
97
+ if (options.ssrHeadHTML || generatedAssetTags || fontHeadHTML) html = html.replace("</head>", ` ${fontHeadHTML}${options.ssrHeadHTML}\n ${generatedAssetTags}\n</head>`);
98
+ html = html.replace("<!-- __NEXT_SCRIPTS__ -->", generatedNextDataScript);
99
+ if (!html.includes("__NEXT_DATA__")) html = html.replace("</body>", ` ${generatedNextDataScript}\n</body>`);
93
100
  return html;
94
101
  }
102
+ const generatedAssetTags = applyDocumentAssetProps(options.assetTags, {}, { configuredCrossOrigin: options.crossOrigin });
103
+ const generatedNextDataScript = applyDocumentAssetProps(nextDataScript, {}, { configuredCrossOrigin: options.crossOrigin });
95
104
  return `<!DOCTYPE html>
96
105
  <html>
97
106
  <head>
98
- ${fontHeadHTML}${options.ssrHeadHTML}\n ${options.assetTags}\n</head>
107
+ ${fontHeadHTML}${options.ssrHeadHTML}\n ${generatedAssetTags}\n</head>
99
108
  <body>
100
- <div id="__next">${bodyMarker}</div>\n ${nextDataScript}\n</body>
109
+ <div id="__next">${bodyMarker}</div>\n ${generatedNextDataScript}\n</body>
101
110
  </html>`;
102
111
  }
103
112
  async function buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix) {
@@ -217,10 +226,12 @@ async function renderPagesPageResponse(options) {
217
226
  if (documentRenderPage.status === "rendered" && documentRenderPage.stylesHTML) ssrHeadHTML += `\n ${documentRenderPage.stylesHTML}`;
218
227
  const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {
219
228
  assetTags: options.assetTags,
229
+ disableOptimizedLoading: options.disableOptimizedLoading,
220
230
  DocumentComponent: options.DocumentComponent,
221
231
  renderDocumentToString: options.renderDocumentToString,
222
232
  ssrHeadHTML,
223
- resolvedDocProps: documentRenderPage.status === "skipped" ? null : documentRenderPage.docProps
233
+ resolvedDocProps: documentRenderPage.status === "skipped" ? null : documentRenderPage.docProps,
234
+ crossOrigin: options.crossOrigin
224
235
  });
225
236
  options.clearSsrContext();
226
237
  const markerIndex = shellHtml.indexOf(bodyMarker);
@@ -102,7 +102,7 @@ async function handleRequest(request, env, platformCtx) {
102
102
  matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
103
103
  runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
104
104
  renderPage: typeof renderPage === "function" ? (req, resolvedUrl, options, stagedHeaders) => renderPage(req, resolvedUrl, null, ctx, stagedHeaders, options) : null,
105
- handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx, new URL(req.url).origin) : null,
105
+ handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx, new URL(req.url).origin, "worker") : null,
106
106
  serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase) => {
107
107
  if (!env?.ASSETS) return false;
108
108
  return fetchWorkerFilesystemRoute(request, requestPathname, phase, (assetRequest) => Promise.resolve(env.ASSETS.fetch(assetRequest)));
@@ -8,6 +8,7 @@ type PrerenderManifestRoute = {
8
8
  router?: string;
9
9
  fallback?: boolean;
10
10
  headers?: Record<string, string>;
11
+ tags?: string[];
11
12
  };
12
13
  type PrerenderManifest = {
13
14
  buildId?: string;
@@ -666,6 +666,7 @@ async function startProdServer(options = {}) {
666
666
  }
667
667
  function createNodeExecutionContext(trustedRevalidateOrigin) {
668
668
  return {
669
+ hostRuntime: "node",
669
670
  waitUntil(promise) {
670
671
  Promise.resolve(promise).catch(() => {});
671
672
  },
@@ -1148,7 +1149,7 @@ async function startPagesRouterServer(options) {
1148
1149
  ...options,
1149
1150
  originalUrl: originalRenderUrl
1150
1151
  }) : null,
1151
- handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port)) : null,
1152
+ handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node") : null,
1152
1153
  serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1153
1154
  if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1154
1155
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
@@ -66,7 +66,7 @@ async function seedMemoryCacheFromPrerender(serverDir, options) {
66
66
  const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? baseKey + ":rsc";
67
67
  const revalidateSeconds = typeof route.revalidate === "number" ? route.revalidate : void 0;
68
68
  const expireSeconds = typeof route.expire === "number" ? route.expire : void 0;
69
- const tags = buildAppPageCacheTags(cachePathname, []);
69
+ const tags = buildAppPageCacheTags(cachePathname, route.tags ?? []);
70
70
  if (await seedHtml(writeAppPageEntry, prerenderDir, htmlKey, artifactPathname, trailingSlash, route.headers, revalidateSeconds, expireSeconds, tags)) {
71
71
  await seedRsc(writeAppPageEntry, prerenderDir, rscKey, artifactPathname, revalidateSeconds, expireSeconds, tags);
72
72
  seeded++;
@@ -8,6 +8,7 @@ function deriveExecutionContext(base, dispatchPagesRevalidate, isInternalPagesRe
8
8
  ...typeof base?.passThroughOnException === "function" ? { passThroughOnException() {
9
9
  base.passThroughOnException?.();
10
10
  } } : {},
11
+ hostRuntime: "worker",
11
12
  ...base?.cache === void 0 ? {} : { cache: base.cache },
12
13
  ...base?.trustedRevalidateOrigin === void 0 ? {} : { trustedRevalidateOrigin: base.trustedRevalidateOrigin },
13
14
  dispatchPagesRevalidate,
@@ -9,8 +9,9 @@ type AppRouterScrollIntent = Readonly<{
9
9
  declare function beginAppRouterScrollIntent(hash: string | null): AppRouterScrollIntent;
10
10
  declare function clearAppRouterScrollIntent(): void;
11
11
  declare function getPendingAppRouterScrollIntent(): AppRouterScrollIntent | null;
12
+ declare function isLatestAppRouterScrollIntent(expected: AppRouterScrollIntent | null | undefined): boolean;
12
13
  declare function claimAppRouterScrollIntentForCommit(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
13
14
  declare function markAppRouterScrollIntentHeadHoisted(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
14
15
  declare function consumeAppRouterScrollIntent(expected: AppRouterScrollIntent | null | undefined, commitId?: number): AppRouterScrollIntent | null;
15
16
  //#endregion
16
- export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
17
+ export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, isLatestAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -22,11 +22,16 @@ function beginAppRouterScrollIntent(hash) {
22
22
  return intent;
23
23
  }
24
24
  function clearAppRouterScrollIntent() {
25
- getScrollIntentStore().pending = null;
25
+ const store = getScrollIntentStore();
26
+ store.nextId += 1;
27
+ store.pending = null;
26
28
  }
27
29
  function getPendingAppRouterScrollIntent() {
28
30
  return getScrollIntentStore().pending;
29
31
  }
32
+ function isLatestAppRouterScrollIntent(expected) {
33
+ return expected !== null && expected !== void 0 && getScrollIntentStore().nextId === expected.id;
34
+ }
30
35
  function claimAppRouterScrollIntentForCommit(expected, commitId) {
31
36
  const store = getScrollIntentStore();
32
37
  const intent = store.pending;
@@ -59,4 +64,4 @@ function consumeAppRouterScrollIntent(expected, commitId) {
59
64
  return intent;
60
65
  }
61
66
  //#endregion
62
- export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
67
+ export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, isLatestAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -8,6 +8,12 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  * custom Pages Router documents. Vinext's renderer replaces the Main and
9
9
  * NextScript placeholders with the rendered page and hydration scripts.
10
10
  */
11
+ const documentAssetMarkerAttributes = {
12
+ headNonce: "data-vinext-head-nonce",
13
+ headCrossOrigin: "data-vinext-head-cross-origin",
14
+ scriptNonce: "data-vinext-script-nonce",
15
+ scriptCrossOrigin: "data-vinext-script-cross-origin"
16
+ };
11
17
  const HtmlContext = React.createContext(void 0);
12
18
  function Html(props) {
13
19
  return /* @__PURE__ */ jsx("html", { ...props });
@@ -39,9 +45,11 @@ var Head = class extends React.Component {
39
45
  return [];
40
46
  }
41
47
  render() {
42
- const { children, ...props } = this.props;
48
+ const { children, nonce, crossOrigin, ...props } = this.props;
43
49
  return /* @__PURE__ */ jsx("head", {
44
50
  ...props,
51
+ ...nonce ? { [documentAssetMarkerAttributes.headNonce]: nonce } : {},
52
+ ...crossOrigin ? { [documentAssetMarkerAttributes.headCrossOrigin]: crossOrigin } : {},
45
53
  children
46
54
  });
47
55
  }
@@ -70,7 +78,12 @@ var NextScript = class extends React.Component {
70
78
  return JSON.stringify(context.__NEXT_DATA__);
71
79
  }
72
80
  render() {
73
- return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: "<!-- __NEXT_SCRIPTS__ -->" } });
81
+ const { nonce, crossOrigin } = this.props;
82
+ return /* @__PURE__ */ jsx("span", {
83
+ ...nonce ? { [documentAssetMarkerAttributes.scriptNonce]: nonce } : {},
84
+ ...crossOrigin ? { [documentAssetMarkerAttributes.scriptCrossOrigin]: crossOrigin } : {},
85
+ dangerouslySetInnerHTML: { __html: "<!-- __NEXT_SCRIPTS__ -->" }
86
+ });
74
87
  }
75
88
  };
76
89
  var Document = class extends React.Component {
@@ -1,7 +1,7 @@
1
1
  //#region src/shims/hash-scroll.d.ts
2
2
  declare function decodeHashFragment(fragment: string): string;
3
3
  declare function scrollToHashTarget(hash: string): void;
4
- declare function scrollToHashTargetOnNextFrame(hash: string): void;
4
+ declare function scrollToHashTargetOnNextFrame(hash: string, shouldScroll?: () => boolean): void;
5
5
  declare function retryScrollTo(x: number, y: number, opts?: {
6
6
  minFrames?: number;
7
7
  shouldContinue?: () => boolean;
@@ -24,8 +24,9 @@ function scrollToHashTarget(hash) {
24
24
  }
25
25
  window.scrollTo(0, 0);
26
26
  }
27
- function scrollToHashTargetOnNextFrame(hash) {
27
+ function scrollToHashTargetOnNextFrame(hash, shouldScroll) {
28
28
  requestAnimationFrame(() => {
29
+ if (shouldScroll && !shouldScroll()) return;
29
30
  scrollToHashTarget(hash);
30
31
  });
31
32
  }
@@ -56,6 +56,8 @@ declare function resolveLinkPrefetchMode(prefetchProp: LinkProps["prefetch"], is
56
56
  declare function canAutoPrefetchFullAppRoute(href: string): boolean;
57
57
  declare function resolveAutoAppRoutePrefetch(href: string): {
58
58
  cacheForNavigation: boolean;
59
+ fallbackTtl: "dynamic" | "static";
60
+ minimumTtlMs: number | undefined;
59
61
  prefetchShellFirst: boolean;
60
62
  shouldPrefetch: boolean;
61
63
  };
@@ -30,6 +30,22 @@ import { jsx } from "react/jsx-runtime";
30
30
  */
31
31
  const HAS_PAGES_ROUTER = process.env.__VINEXT_HAS_PAGES_ROUTER !== "false";
32
32
  const HAS_CLIENT_REWRITES = process.env.__VINEXT_HAS_CLIENT_REWRITES !== "false";
33
+ let loadedNavigationModule = null;
34
+ let navigationModulePromise = null;
35
+ let loadedHybridClientRouteOwnerModule = null;
36
+ let hybridClientRouteOwnerModulePromise = null;
37
+ function loadNavigationModule() {
38
+ return navigationModulePromise ??= import("./navigation.js").then((module) => {
39
+ loadedNavigationModule = module;
40
+ return module;
41
+ });
42
+ }
43
+ function loadHybridClientRouteOwnerModule() {
44
+ return hybridClientRouteOwnerModulePromise ??= import("./internal/hybrid-client-route-owner.js").then((module) => {
45
+ loadedHybridClientRouteOwnerModule = module;
46
+ return module;
47
+ });
48
+ }
33
49
  const LinkStatusContext = createContext({ pending: false });
34
50
  /**
35
51
  * useLinkStatus returns the pending state of the enclosing <Link>.
@@ -148,6 +164,8 @@ function getLinkPrefetchRouterMode() {
148
164
  function resolveMatchedAutoAppRoutePrefetch(route) {
149
165
  return {
150
166
  cacheForNavigation: !route.canPrefetchLoadingShell && route.requiresDynamicNavigationRequest !== true,
167
+ fallbackTtl: "static",
168
+ minimumTtlMs: route.isDynamic ? 0 : void 0,
151
169
  prefetchShellFirst: !route.isDynamic,
152
170
  shouldPrefetch: true
153
171
  };
@@ -164,24 +182,32 @@ function canAutoPrefetchFullAppRoute(href) {
164
182
  function resolveAutoAppRoutePrefetch(href) {
165
183
  if (typeof window === "undefined") return {
166
184
  cacheForNavigation: false,
185
+ fallbackTtl: "static",
186
+ minimumTtlMs: void 0,
167
187
  prefetchShellFirst: false,
168
188
  shouldPrefetch: false
169
189
  };
170
190
  const routes = window.__VINEXT_LINK_PREFETCH_ROUTES__;
171
191
  if (!routes) return {
172
192
  cacheForNavigation: false,
193
+ fallbackTtl: "static",
194
+ minimumTtlMs: void 0,
173
195
  prefetchShellFirst: false,
174
196
  shouldPrefetch: false
175
197
  };
176
198
  const routeHref = toSameOriginRouteHref(href);
177
199
  if (routeHref === null) return {
178
200
  cacheForNavigation: false,
201
+ fallbackTtl: "static",
202
+ minimumTtlMs: void 0,
179
203
  prefetchShellFirst: false,
180
204
  shouldPrefetch: false
181
205
  };
182
206
  const match = matchRouteWithTrie(routeHref, routes, linkPrefetchRouteTrieCache);
183
207
  if (!match) return {
184
208
  cacheForNavigation: false,
209
+ fallbackTtl: "static",
210
+ minimumTtlMs: void 0,
185
211
  prefetchShellFirst: false,
186
212
  shouldPrefetch: false
187
213
  };
@@ -196,6 +222,8 @@ function resolveAutoAppRoutePrefetch(href) {
196
222
  function resolveFullAppRoutePrefetch() {
197
223
  return {
198
224
  cacheForNavigation: true,
225
+ fallbackTtl: "static",
226
+ minimumTtlMs: void 0,
199
227
  prefetchShellFirst: true,
200
228
  shouldPrefetch: true
201
229
  };
@@ -229,27 +257,30 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
229
257
  }) ?? prefetchHref, window.location.href, __basePath);
230
258
  const target = new URL(fullHref, window.location.href);
231
259
  if (target.origin === window.location.origin && target.pathname === window.location.pathname && target.search === window.location.search) return;
260
+ if (mode === "auto" && priority === "low" && target.origin === window.location.origin && target.pathname === window.location.pathname && target.search !== window.location.search && target.hash !== "") return;
232
261
  const runPrefetch = () => {
233
262
  (async () => {
234
263
  if (hasAppNavigationRuntime()) {
235
264
  if (isBotUserAgent(window.navigator?.userAgent ?? "")) return;
236
265
  const [navigation, { AppElementsWire }, rscCacheBusting, { APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL }, headersModule, hybridRouteOwner] = await Promise.all([
237
- import("./navigation.js"),
266
+ loadNavigationModule(),
238
267
  import("../server/app-elements.js"),
239
268
  import("../server/app-rsc-cache-busting.js"),
240
269
  import("../server/app-rsc-render-mode.js"),
241
270
  import("../server/headers.js"),
242
- HAS_PAGES_ROUTER || HAS_CLIENT_REWRITES ? import("./internal/hybrid-client-route-owner.js") : null
271
+ HAS_PAGES_ROUTER || HAS_CLIENT_REWRITES ? loadHybridClientRouteOwnerModule() : null
243
272
  ]);
244
273
  if (navigationEpoch !== linkPrefetchNavigationEpoch) return;
245
- const { getPrefetchInterceptionContext, getPrefetchCache, getPrefetchedUrls, getMountedSlotsHeader, hasSearchAgnosticPrefetchShellForRoute, hasPrefetchCacheEntryForNavigation, peekPrefetchResponseForNavigation, prefetchRscResponse, restoreRscResponse, PREFETCH_CACHE_TTL } = navigation;
246
- const { createRscRequestHeaders, createRscRequestUrl } = rscCacheBusting;
274
+ const { getPrefetchInterceptionContext, getPrefetchCache, getPrefetchedUrls, getMountedSlotsHeader, createAppPrefetchRequestHeaders, discardLearningOnlyPrefetchCacheEntry, hasSearchAgnosticPrefetchShellForRoute, hasPrefetchCacheEntryForNavigation, peekPrefetchResponseForNavigation, prefetchRscResponse, DYNAMIC_NAVIGATION_CACHE_TTL, restoreRscResponse, PREFETCH_CACHE_TTL } = navigation;
275
+ const { createRscRequestUrl } = rscCacheBusting;
247
276
  const { NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, VINEXT_MOUNTED_SLOTS_HEADER } = headersModule;
248
277
  const hybridOwner = HAS_PAGES_ROUTER ? hybridRouteOwner.resolveHybridClientRouteOwner(prefetchHref, __basePath) : null;
249
278
  if (hybridOwner === "pages" || hybridOwner === "document") return;
250
279
  const rewrittenPrefetchHref = HAS_CLIENT_REWRITES ? hybridRouteOwner.resolveHybridClientRewriteHref(fullHref, __basePath) : null;
251
280
  const autoPrefetch = mode === "auto" ? resolveAutoAppRoutePrefetch(rewrittenPrefetchHref ?? prefetchHref) : mode === "full-after-shell" ? {
252
281
  cacheForNavigation: true,
282
+ fallbackTtl: "static",
283
+ minimumTtlMs: void 0,
253
284
  prefetchShellFirst: true,
254
285
  shouldPrefetch: true
255
286
  } : resolveFullAppRoutePrefetch();
@@ -260,13 +291,15 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
260
291
  const hasSearchParams = new URL(fullHref, window.location.href).search !== "";
261
292
  const isAutomaticSearchParamShell = mode === "auto" && isOptimisticRouteShellPrefetch && hasSearchParams;
262
293
  const hasSearchAgnosticShell = isAutomaticSearchParamShell && hasSearchAgnosticPrefetchShellForRoute(await createRscRequestUrl(fullHref, new Headers()), interceptionContext, mountedSlotsHeader);
263
- const headers = createRscRequestHeaders({
294
+ const headers = createAppPrefetchRequestHeaders({
264
295
  interceptionContext,
296
+ fetchPriority: priority,
297
+ prefetchKind: mode === "full" ? "full" : "auto",
265
298
  renderMode: isOptimisticRouteShellPrefetch ? hasSearchAgnosticShell ? APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL : isAutomaticSearchParamShell ? APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL : APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL : void 0
266
299
  });
267
300
  if (mountedSlotsHeader) headers.set(VINEXT_MOUNTED_SLOTS_HEADER, mountedSlotsHeader);
268
301
  const shouldSendSegmentPrefetchHeaders = isOptimisticRouteShellPrefetch || mode === "auto";
269
- if (__prefetchInlining && autoPrefetch.cacheForNavigation) {
302
+ if (__prefetchInlining && mode === "auto" && autoPrefetch.cacheForNavigation) {
270
303
  headers.set(NEXT_ROUTER_PREFETCH_HEADER, "1");
271
304
  headers.set(NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, "/__PAGE__");
272
305
  } else if (shouldSendSegmentPrefetchHeaders) {
@@ -277,10 +310,9 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
277
310
  const additionalRscUrls = rewrittenPrefetchHref && rewrittenPrefetchHref !== fullHref ? [await createRscRequestUrl(rewrittenPrefetchHref, headers)] : [];
278
311
  const cacheKey = AppElementsWire.encodeCacheKey(rscUrl, interceptionContext);
279
312
  const prefetched = getPrefetchedUrls();
313
+ if (autoPrefetch.cacheForNavigation) discardLearningOnlyPrefetchCacheEntry(rscUrl, interceptionContext);
280
314
  if (prefetched.has(cacheKey)) {
281
315
  if (!autoPrefetch.cacheForNavigation) return;
282
- const existing = getPrefetchCache().get(cacheKey);
283
- if (existing?.cacheForNavigation === false) existing.cacheForNavigation = true;
284
316
  }
285
317
  const fetchFullRscPayload = () => scheduleAppPrefetchFetch(() => fetch(rscUrl, {
286
318
  headers,
@@ -289,8 +321,9 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
289
321
  purpose: "prefetch"
290
322
  }), priority);
291
323
  const fetchLoadingShellForReuse = async () => {
292
- const shellHeaders = createRscRequestHeaders({
324
+ const shellHeaders = createAppPrefetchRequestHeaders({
293
325
  interceptionContext,
326
+ fetchPriority: priority,
294
327
  renderMode: APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL
295
328
  });
296
329
  shellHeaders.set(NEXT_ROUTER_PREFETCH_HEADER, "1");
@@ -317,8 +350,9 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
317
350
  await shellEntry?.pending?.catch(() => {});
318
351
  };
319
352
  const fetchAliasCacheHitProbe = async () => {
320
- const probeHeaders = createRscRequestHeaders({
353
+ const probeHeaders = createAppPrefetchRequestHeaders({
321
354
  interceptionContext,
355
+ fetchPriority: priority,
322
356
  renderMode: APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL
323
357
  });
324
358
  probeHeaders.set(NEXT_ROUTER_PREFETCH_HEADER, "1");
@@ -350,8 +384,9 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
350
384
  await fetchLoadingShellForReuse();
351
385
  return fetchFullRscPayload();
352
386
  }
353
- const shellHeaders = createRscRequestHeaders({
387
+ const shellHeaders = createAppPrefetchRequestHeaders({
354
388
  interceptionContext,
389
+ fetchPriority: priority,
355
390
  renderMode: void 0
356
391
  });
357
392
  shellHeaders.set(NEXT_ROUTER_PREFETCH_HEADER, "1");
@@ -389,12 +424,18 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
389
424
  purpose: "prefetch"
390
425
  }), priority);
391
426
  })() : fetchFullRscPayload();
392
- if (mode === "full" && autoPrefetch.cacheForNavigation && autoPrefetch.prefetchShellFirst && mountedSlotsHeader === null && !gateViaExplicitSearchShell) fetchLoadingShellForReuse();
427
+ if (!__prefetchInlining && mode === "full" && autoPrefetch.cacheForNavigation && autoPrefetch.prefetchShellFirst && mountedSlotsHeader === null && !gateViaExplicitSearchShell) fetchLoadingShellForReuse();
393
428
  prefetchRscResponse(rscUrl, fetchPromise, interceptionContext, mountedSlotsHeader, void 0, {
394
429
  cacheForNavigation: autoPrefetch.cacheForNavigation,
395
- fallbackTtlMs: PREFETCH_CACHE_TTL,
430
+ fallbackTtlMs: autoPrefetch.fallbackTtl === "dynamic" ? DYNAMIC_NAVIGATION_CACHE_TTL : PREFETCH_CACHE_TTL,
431
+ minimumTtlMs: autoPrefetch.minimumTtlMs,
396
432
  optimisticRouteShell: isOptimisticRouteShellPrefetch,
397
433
  prefetchKind: isOptimisticRouteShellPrefetch ? "loading-shell" : "navigation",
434
+ prepareSnapshot: autoPrefetch.cacheForNavigation ? async (snapshot) => {
435
+ const preparePrefetchResponse = getNavigationRuntime()?.functions.preparePrefetchResponse;
436
+ if (!preparePrefetchResponse) throw new Error("App Router prefetch preparation is unavailable");
437
+ return await preparePrefetchResponse(restoreRscResponse(snapshot));
438
+ } : void 0,
398
439
  searchAgnosticShell: isAutomaticSearchParamShell && !hasSearchAgnosticShell
399
440
  });
400
441
  } else if (HAS_PAGES_ROUTER && window.__NEXT_DATA__) {
@@ -427,7 +468,7 @@ function prefetchUrl(href, mode, priority = "low", pagesRouteHref, locale) {
427
468
  async function promotePrefetchEntriesForNavigation(href) {
428
469
  if (typeof window === "undefined") return;
429
470
  if (!hasAppNavigationRuntime()) return;
430
- const [{ getPrefetchCache }, { stripRscCacheBustingSearchParam, stripRscSuffix }] = await Promise.all([import("./navigation.js"), import("../server/app-rsc-cache-busting.js")]);
471
+ const [{ getPrefetchCache }, { stripRscCacheBustingSearchParam, stripRscSuffix }] = await Promise.all([loadNavigationModule(), import("../server/app-rsc-cache-busting.js")]);
431
472
  let target;
432
473
  try {
433
474
  target = new URL(toBrowserNavigationHref(href, window.location.href, __basePath), window.location.href);
@@ -595,7 +636,7 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
595
636
  } : null;
596
637
  const pagesAsHref = asHref ?? dynamicRouteHref?.as;
597
638
  const unresolvedHref = pagesAsHref ?? hrefStr;
598
- const rawResolvedHref = typeof unresolvedHref === "string" && unresolvedHref.startsWith("#") ? resolvePagesQueryOnlyHref(unresolvedHref) : unresolvedHref;
639
+ const rawResolvedHref = typeof unresolvedHref === "string" && unresolvedHref.startsWith("#") && !getNavigationRuntime()?.functions.navigate ? resolvePagesQueryOnlyHref(unresolvedHref) : unresolvedHref;
599
640
  const concreteRouteHref = HAS_PAGES_ROUTER ? resolveConcreteRouteHref(dynamicRouteHref ? hrefForImplicitInterpolation ?? href : href, pagesAsHref) : null;
600
641
  const routeHrefRaw = dynamicRouteHref?.href ?? concreteRouteHref ?? hrefStr;
601
642
  const prefetchRouteHrefRaw = concreteRouteHref ?? routeHrefRaw;
@@ -743,19 +784,19 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
743
784
  onNavigate(navEvent);
744
785
  if (navEvent.defaultPrevented) return;
745
786
  } catch {}
746
- const hybridOwner = HAS_PAGES_ROUTER && hasAppNavigationRuntime ? (await import("./internal/hybrid-client-route-owner.js")).resolveHybridClientRouteOwner(navigateHref, __basePath) : null;
787
+ const hybridOwner = (HAS_PAGES_ROUTER && hasAppNavigationRuntime ? loadedHybridClientRouteOwnerModule ?? await loadHybridClientRouteOwnerModule() : null)?.resolveHybridClientRouteOwner(navigateHref, __basePath);
747
788
  if (HAS_PAGES_ROUTER && hasAppNavigationRuntime && ["pages", "document"].includes(hybridOwner ?? "")) {
748
789
  if (replace) window.location.replace(absoluteFullHref);
749
790
  else window.location.assign(absoluteFullHref);
750
791
  return;
751
792
  }
752
793
  if (hasAppNavigationRuntime) {
753
- const { navigateClientSide } = await import("./navigation.js");
794
+ const { navigateClientSide } = loadedNavigationModule ?? await loadNavigationModule();
754
795
  const setter = setPendingRef.current;
755
796
  if (setter) setLinkForCurrentNavigation(setter);
756
797
  setPending(true);
757
798
  React.startTransition(() => {
758
- navigateClientSide(navigateHref, replace ? "replace" : "push", scroll, true).finally(() => {
799
+ navigateClientSide(navigateHref, replace ? "replace" : "push", scroll, false).finally(() => {
759
800
  if (mountedRef.current) setPending(false);
760
801
  if (setter) clearLinkForCurrentNavigation(setter);
761
802
  });
@@ -181,28 +181,19 @@ type SocialImageDescriptor = {
181
181
  };
182
182
  type IconDescriptor = {
183
183
  url: string | URL;
184
- sizes?: string;
185
184
  type?: string;
186
- media?: string;
187
- };
188
- type AppleIconDescriptor = {
189
- url: string | URL;
190
185
  sizes?: string;
191
- type?: string;
186
+ color?: string;
187
+ rel?: string;
188
+ media?: string;
189
+ fetchPriority?: "high" | "low" | "auto";
192
190
  };
193
191
  type IconInput = string | URL | IconDescriptor;
194
- type AppleIconInput = string | URL | AppleIconDescriptor;
195
- type OtherIconDescriptor = {
196
- rel: string;
197
- url: string | URL;
198
- sizes?: string;
199
- type?: string;
200
- };
201
192
  type IconsMap = {
202
193
  icon?: IconInput | IconInput[];
203
- shortcut?: string | URL | Array<string | URL>;
204
- apple?: AppleIconInput | AppleIconInput[];
205
- other?: OtherIconDescriptor | OtherIconDescriptor[];
194
+ shortcut?: IconInput | IconInput[];
195
+ apple?: IconInput | IconInput[];
196
+ other?: IconDescriptor | IconDescriptor[];
206
197
  };
207
198
  type IconsMetadata = IconInput | IconInput[] | IconsMap;
208
199
  type MetadataMergeEntry = {
@@ -263,14 +254,17 @@ type MetadataHeadProps = {
263
254
  metadata: Metadata;
264
255
  pathname?: string;
265
256
  trailingSlash?: boolean;
257
+ streamedIconKey?: string;
266
258
  };
267
259
  declare function renderMetadataToHtml(metadata: Metadata, pathname?: string, options?: {
268
260
  trailingSlash?: boolean;
261
+ streamedIconKey?: string;
269
262
  }): string;
270
263
  declare function MetadataHead({
271
264
  metadata,
272
265
  pathname,
273
- trailingSlash
266
+ trailingSlash,
267
+ streamedIconKey
274
268
  }: MetadataHeadProps): React.JSX.Element;
275
269
  //#endregion
276
270
  export { DEFAULT_VIEWPORT, Metadata, MetadataHead, MetadataMergeEntry, ResolvedViewport, Viewport, ViewportHead, mergeMetadata, mergeMetadataEntries, mergeViewport, postProcessMetadata, renderMetadataToHtml, resolveModuleMetadata, resolveModuleViewport };
@@ -420,12 +420,15 @@ function renderMetadataElementToHtml(node) {
420
420
  "content"
421
421
  ])}>`;
422
422
  case "link": return `<link${renderMetadataAttributes(props, [
423
+ "data-vinext-streamed-icon",
423
424
  "rel",
424
425
  "href",
425
426
  "hrefLang",
426
- "media",
427
427
  "type",
428
- "sizes"
428
+ "sizes",
429
+ "color",
430
+ "media",
431
+ "fetchPriority"
429
432
  ])}>`;
430
433
  default: return "";
431
434
  }
@@ -434,10 +437,11 @@ function renderMetadataToHtml(metadata, pathname = "/", options) {
434
437
  return renderMetadataElementToHtml(MetadataHead({
435
438
  metadata,
436
439
  pathname,
437
- trailingSlash: options?.trailingSlash
440
+ trailingSlash: options?.trailingSlash,
441
+ streamedIconKey: options?.streamedIconKey
438
442
  }));
439
443
  }
440
- function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
444
+ function MetadataHead({ metadata, pathname = "/", trailingSlash, streamedIconKey }) {
441
445
  const elements = [];
442
446
  let key = 0;
443
447
  const base = metadata.metadataBase;
@@ -725,35 +729,23 @@ function MetadataHead({ metadata, pathname = "/", trailingSlash }) {
725
729
  }
726
730
  if (metadata.icons) {
727
731
  const iconEntries = isIconsMap(metadata.icons) ? normalizeUrlDescriptorEntries(metadata.icons.icon, (url) => ({ url })) : normalizeUrlDescriptorEntries(metadata.icons, (url) => ({ url }));
728
- if (isIconsMap(metadata.icons) && metadata.icons.shortcut) {
729
- const shortcuts = Array.isArray(metadata.icons.shortcut) ? metadata.icons.shortcut : [metadata.icons.shortcut];
730
- for (const s of shortcuts) elements.push(/* @__PURE__ */ jsx("link", {
731
- rel: "shortcut icon",
732
- href: stringifyUrl(s)
733
- }, key++));
734
- }
735
- if (iconEntries.length > 0) for (const i of iconEntries) elements.push(/* @__PURE__ */ jsx("link", {
736
- rel: "icon",
737
- href: stringifyUrl(i.url),
738
- ...i.sizes ? { sizes: i.sizes } : {},
739
- ...i.type ? { type: i.type } : {},
740
- ...i.media ? { media: i.media } : {}
741
- }, key++));
742
- if (isIconsMap(metadata.icons) && metadata.icons.apple) for (const a of normalizeUrlDescriptorEntries(metadata.icons.apple, (url) => ({ url }))) elements.push(/* @__PURE__ */ jsx("link", {
743
- rel: "apple-touch-icon",
744
- href: stringifyUrl(a.url),
745
- ...a.sizes ? { sizes: a.sizes } : {},
746
- ...a.type ? { type: a.type } : {}
747
- }, key++));
748
- if (isIconsMap(metadata.icons) && metadata.icons.other) {
749
- const others = Array.isArray(metadata.icons.other) ? metadata.icons.other : [metadata.icons.other];
750
- for (const o of others) elements.push(/* @__PURE__ */ jsx("link", {
751
- rel: o.rel,
752
- href: stringifyUrl(o.url),
753
- ...o.sizes ? { sizes: o.sizes } : {},
754
- ...o.type ? { type: o.type } : {}
732
+ let streamedIconOrder = 0;
733
+ const appendIcons = (entries, defaultRel) => {
734
+ for (const { url, rel, type, sizes, color, media, fetchPriority } of entries) elements.push(/* @__PURE__ */ jsx("link", {
735
+ "data-vinext-streamed-icon": streamedIconKey ? `${streamedIconKey}:${streamedIconOrder++}` : void 0,
736
+ rel: rel || defaultRel,
737
+ href: stringifyUrl(url),
738
+ type,
739
+ sizes,
740
+ color,
741
+ media,
742
+ fetchPriority
755
743
  }, key++));
756
- }
744
+ };
745
+ if (isIconsMap(metadata.icons) && metadata.icons.shortcut) appendIcons(normalizeUrlDescriptorEntries(metadata.icons.shortcut, (url) => ({ url })), "shortcut icon");
746
+ if (iconEntries.length > 0) appendIcons(iconEntries, "icon");
747
+ if (isIconsMap(metadata.icons) && metadata.icons.apple) appendIcons(normalizeUrlDescriptorEntries(metadata.icons.apple, (url) => ({ url })), "apple-touch-icon");
748
+ if (isIconsMap(metadata.icons) && metadata.icons.other) appendIcons(normalizeUrlDescriptorEntries(metadata.icons.other, (url) => ({ url })), "icon");
757
749
  }
758
750
  if (metadata.manifest) elements.push(/* @__PURE__ */ jsx("link", {
759
751
  rel: "manifest",