vinext 0.1.1 → 0.1.2

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 (147) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/check.js +4 -3
  5. package/dist/client/navigation-runtime.d.ts +3 -2
  6. package/dist/client/window-next.d.ts +6 -4
  7. package/dist/config/config-matchers.d.ts +11 -4
  8. package/dist/config/config-matchers.js +15 -2
  9. package/dist/config/next-config.d.ts +13 -0
  10. package/dist/config/next-config.js +2 -0
  11. package/dist/deploy.js +9 -2
  12. package/dist/entries/app-rsc-entry.js +7 -1
  13. package/dist/entries/pages-client-entry.js +1 -1
  14. package/dist/entries/pages-server-entry.js +7 -6
  15. package/dist/index.d.ts +0 -2
  16. package/dist/index.js +86 -78
  17. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  18. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  19. package/dist/plugins/og-assets.js +2 -2
  20. package/dist/plugins/optimize-imports.d.ts +8 -4
  21. package/dist/plugins/optimize-imports.js +16 -12
  22. package/dist/plugins/sass.d.ts +53 -24
  23. package/dist/plugins/sass.js +249 -1
  24. package/dist/plugins/wasm-module-import.d.ts +15 -0
  25. package/dist/plugins/wasm-module-import.js +50 -0
  26. package/dist/routing/app-route-graph.d.ts +23 -1
  27. package/dist/routing/app-route-graph.js +47 -8
  28. package/dist/routing/file-matcher.js +1 -1
  29. package/dist/server/app-browser-entry.js +108 -213
  30. package/dist/server/app-browser-error.d.ts +4 -1
  31. package/dist/server/app-browser-error.js +7 -1
  32. package/dist/server/app-browser-history-controller.d.ts +104 -0
  33. package/dist/server/app-browser-history-controller.js +210 -0
  34. package/dist/server/app-browser-navigation-controller.d.ts +3 -2
  35. package/dist/server/app-browser-navigation-controller.js +10 -7
  36. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  37. package/dist/server/app-browser-rsc-redirect.js +30 -8
  38. package/dist/server/app-browser-state.js +4 -7
  39. package/dist/server/app-browser-visible-commit.js +1 -1
  40. package/dist/server/app-fallback-renderer.d.ts +2 -1
  41. package/dist/server/app-fallback-renderer.js +3 -1
  42. package/dist/server/app-middleware.js +1 -0
  43. package/dist/server/app-optimistic-routing.js +22 -1
  44. package/dist/server/app-page-boundary-render.d.ts +2 -1
  45. package/dist/server/app-page-boundary-render.js +4 -2
  46. package/dist/server/app-page-cache.js +9 -7
  47. package/dist/server/app-page-dispatch.d.ts +8 -0
  48. package/dist/server/app-page-dispatch.js +18 -5
  49. package/dist/server/app-page-element-builder.d.ts +22 -2
  50. package/dist/server/app-page-element-builder.js +37 -8
  51. package/dist/server/app-page-execution.d.ts +1 -1
  52. package/dist/server/app-page-execution.js +32 -17
  53. package/dist/server/app-page-render.d.ts +1 -1
  54. package/dist/server/app-page-render.js +7 -14
  55. package/dist/server/app-page-request.d.ts +1 -0
  56. package/dist/server/app-page-request.js +3 -2
  57. package/dist/server/app-page-response.js +1 -1
  58. package/dist/server/app-page-route-wiring.d.ts +3 -1
  59. package/dist/server/app-page-route-wiring.js +8 -7
  60. package/dist/server/app-page-stream.d.ts +1 -6
  61. package/dist/server/app-page-stream.js +1 -4
  62. package/dist/server/app-route-handler-response.js +11 -10
  63. package/dist/server/app-route-handler-runtime.js +12 -1
  64. package/dist/server/app-rsc-handler.js +1 -1
  65. package/dist/server/app-rsc-response-finalizer.js +1 -1
  66. package/dist/server/app-server-action-execution.d.ts +11 -0
  67. package/dist/server/app-server-action-execution.js +5 -2
  68. package/dist/server/app-ssr-entry.js +2 -2
  69. package/dist/server/app-ssr-stream.js +9 -1
  70. package/dist/server/dev-lockfile.js +2 -1
  71. package/dist/server/dev-server.js +43 -12
  72. package/dist/server/headers.d.ts +8 -1
  73. package/dist/server/headers.js +8 -1
  74. package/dist/server/instrumentation-runtime.d.ts +6 -0
  75. package/dist/server/instrumentation-runtime.js +8 -0
  76. package/dist/server/isr-decision.d.ts +79 -0
  77. package/dist/server/isr-decision.js +70 -0
  78. package/dist/server/metadata-route-response.js +5 -3
  79. package/dist/server/middleware-runtime.d.ts +13 -0
  80. package/dist/server/middleware-runtime.js +11 -7
  81. package/dist/server/middleware.js +1 -0
  82. package/dist/server/navigation-planner.d.ts +62 -1
  83. package/dist/server/navigation-planner.js +188 -0
  84. package/dist/server/navigation-trace.d.ts +11 -1
  85. package/dist/server/navigation-trace.js +11 -1
  86. package/dist/server/normalize-path.d.ts +0 -8
  87. package/dist/server/normalize-path.js +3 -1
  88. package/dist/server/otel-tracer-extension.d.ts +45 -0
  89. package/dist/server/otel-tracer-extension.js +89 -0
  90. package/dist/server/pages-api-route.d.ts +14 -3
  91. package/dist/server/pages-api-route.js +6 -1
  92. package/dist/server/pages-asset-tags.d.ts +15 -4
  93. package/dist/server/pages-asset-tags.js +18 -12
  94. package/dist/server/pages-data-route.js +5 -1
  95. package/dist/server/pages-node-compat.d.ts +3 -11
  96. package/dist/server/pages-node-compat.js +174 -121
  97. package/dist/server/pages-page-data.d.ts +28 -0
  98. package/dist/server/pages-page-data.js +61 -17
  99. package/dist/server/pages-page-handler.d.ts +1 -0
  100. package/dist/server/pages-page-handler.js +22 -6
  101. package/dist/server/pages-page-response.d.ts +45 -1
  102. package/dist/server/pages-page-response.js +66 -5
  103. package/dist/server/pages-readiness.d.ts +1 -1
  104. package/dist/server/pages-request-pipeline.d.ts +15 -1
  105. package/dist/server/pages-request-pipeline.js +23 -2
  106. package/dist/server/prod-server.d.ts +39 -1
  107. package/dist/server/prod-server.js +98 -34
  108. package/dist/shims/cache-runtime.js +9 -2
  109. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  110. package/dist/shims/dynamic-preload-chunks.js +77 -0
  111. package/dist/shims/dynamic.d.ts +4 -0
  112. package/dist/shims/dynamic.js +4 -2
  113. package/dist/shims/error-boundary.d.ts +4 -4
  114. package/dist/shims/error.js +37 -11
  115. package/dist/shims/fetch-cache.d.ts +9 -1
  116. package/dist/shims/fetch-cache.js +11 -1
  117. package/dist/shims/head.js +6 -1
  118. package/dist/shims/headers.d.ts +16 -2
  119. package/dist/shims/headers.js +37 -1
  120. package/dist/shims/image-config.js +7 -1
  121. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  122. package/dist/shims/internal/app-route-detection.js +10 -6
  123. package/dist/shims/internal/app-router-context.d.ts +5 -0
  124. package/dist/shims/metadata.d.ts +6 -2
  125. package/dist/shims/metadata.js +32 -14
  126. package/dist/shims/navigation.d.ts +7 -16
  127. package/dist/shims/navigation.js +33 -16
  128. package/dist/shims/router.js +28 -1
  129. package/dist/shims/script-nonce-context.d.ts +1 -1
  130. package/dist/shims/script-nonce-context.js +11 -3
  131. package/dist/shims/server.d.ts +17 -1
  132. package/dist/shims/server.js +31 -6
  133. package/dist/shims/slot.js +1 -1
  134. package/dist/shims/unified-request-context.js +1 -0
  135. package/dist/typegen.js +1 -0
  136. package/dist/utils/client-build-manifest.js +15 -5
  137. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  138. package/dist/utils/client-runtime-metadata.js +63 -0
  139. package/dist/utils/hash.d.ts +17 -1
  140. package/dist/utils/hash.js +36 -1
  141. package/dist/utils/lazy-chunks.d.ts +27 -1
  142. package/dist/utils/lazy-chunks.js +65 -1
  143. package/dist/utils/manifest-paths.d.ts +20 -2
  144. package/dist/utils/manifest-paths.js +38 -3
  145. package/dist/utils/path.d.ts +2 -1
  146. package/dist/utils/path.js +5 -1
  147. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
- import { NavigateOptions, PrefetchOptions as PrefetchOptions$1 } from "./internal/app-router-context.js";
1
+ import { NavigationRuntimeVisibleCommitMode } from "../client/navigation-runtime.js";
2
+ import { AppRouterInstance, NavigateOptions, PrefetchOptions as PrefetchOptions$1 } from "./internal/app-router-context.js";
2
3
  import { ReadonlyURLSearchParams } from "./readonly-url-search-params.js";
3
4
  import { UnrecognizedActionError, unstable_isUnrecognizedActionError } from "./unrecognized-action-error.js";
4
5
  import * as React$1 from "react";
@@ -205,6 +206,7 @@ type ClientNavigationRenderSnapshot = {
205
206
  };
206
207
  declare function getClientNavigationRenderContext(): React$1.Context<ClientNavigationRenderSnapshot | null> | null;
207
208
  declare function createClientNavigationRenderSnapshot(href: string, params: Record<string, string | string[]>): ClientNavigationRenderSnapshot;
209
+ declare function createSnapshotPathAndSearch(snapshot: ClientNavigationRenderSnapshot): string;
208
210
  declare function setClientParams(params: Record<string, string | string[]>): void;
209
211
  declare function replaceClientParamsWithoutNotify(params: Record<string, string | string[]>): void;
210
212
  /** Get the current client params (for testing referential stability). */
@@ -256,7 +258,7 @@ declare function saveScrollPosition(): void;
256
258
  /**
257
259
  * Navigate to a URL, handling external URLs, hash-only changes, and RSC navigation.
258
260
  */
259
- declare function navigateClientSide(href: string, mode: "push" | "replace", scroll: boolean, programmaticTransition?: boolean): Promise<void>;
261
+ declare function navigateClientSide(href: string, mode: "push" | "replace", scroll: boolean, programmaticTransition?: boolean, visibleCommitMode?: NavigationRuntimeVisibleCommitMode): Promise<void>;
260
262
  /**
261
263
  * Public App Router instance, exposed for the browser entry so it can wire
262
264
  * `window.next.router` to the same singleton returned from `useRouter()`.
@@ -264,19 +266,7 @@ declare function navigateClientSide(href: string, mode: "push" | "replace", scro
264
266
  * Mirrors `publicAppRouterInstance` from Next.js's
265
267
  * `packages/next/src/client/components/app-router-instance.ts` (line 392).
266
268
  */
267
- declare const appRouterInstance: {
268
- bfcacheId: string;
269
- push(href: string, options?: {
270
- scroll?: boolean;
271
- }): void;
272
- replace(href: string, options?: {
273
- scroll?: boolean;
274
- }): void;
275
- back(): void;
276
- forward(): void;
277
- refresh(): void;
278
- prefetch(href: string, options?: PrefetchOptions): void;
279
- };
269
+ declare const appRouterInstance: AppRouterInstance;
280
270
  /**
281
271
  * App Router's useRouter — returns push/replace/back/forward/refresh.
282
272
  * Different from Pages Router's useRouter (next/router).
@@ -292,6 +282,7 @@ declare function useRouter(): {
292
282
  push(href: string, options?: NavigateOptions): void;
293
283
  replace(href: string, options?: NavigateOptions): void;
294
284
  prefetch(href: string, options?: PrefetchOptions$1): void;
285
+ experimental_gesturePush?(href: string, options?: NavigateOptions): void;
295
286
  };
296
287
  /**
297
288
  * Returns the active child segment one level below the layout where it's called.
@@ -574,4 +565,4 @@ declare function isDynamicServerError(error: unknown): error is DynamicServerErr
574
565
  */
575
566
  declare function unstable_rethrow(error: unknown): void;
576
567
  //#endregion
577
- export { BailoutToCSRError, CachedRscResponse, ClientNavigationRenderSnapshot, DynamicServerError, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, NavigationContext, PREFETCH_CACHE_TTL, PrefetchCacheEntry, PrefetchOptions, ReadonlyURLSearchParams, RedirectType, SegmentMap, ServerInsertedHTMLContext, UnrecognizedActionError, __basePath, _registerStateAccessors, activateNavigationSnapshot, appRouterInstance, clearPendingPathname, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, decodeRedirectError, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getBfcacheIdMapContext, getBfcacheSegmentIdContext, getClientNavigationRenderContext, getClientNavigationState, getClientParams, getCurrentInterceptionContext, getCurrentNextUrl, getLayoutSegmentContext, getMountedSlotsHeader, getNavigationContext, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, isBailoutToCSRError, isDynamicServerError, isHTTPAccessFallbackError, isNextRouterError, isRedirectError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, renderServerInsertedHTML, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveCachedRscResponseExpiresAt, resolveCachedRscResponseTtlMs, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname, snapshotRscResponse, storePrefetchResponse, unauthorized, unstable_isUnrecognizedActionError, unstable_rethrow, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
568
+ export { BailoutToCSRError, CachedRscResponse, ClientNavigationRenderSnapshot, DynamicServerError, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, NavigationContext, PREFETCH_CACHE_TTL, PrefetchCacheEntry, PrefetchOptions, ReadonlyURLSearchParams, RedirectType, SegmentMap, ServerInsertedHTMLContext, UnrecognizedActionError, __basePath, _registerStateAccessors, activateNavigationSnapshot, appRouterInstance, clearPendingPathname, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, decodeRedirectError, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getBfcacheIdMapContext, getBfcacheSegmentIdContext, getClientNavigationRenderContext, getClientNavigationState, getClientParams, getCurrentInterceptionContext, getCurrentNextUrl, getLayoutSegmentContext, getMountedSlotsHeader, getNavigationContext, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, isBailoutToCSRError, isDynamicServerError, isHTTPAccessFallbackError, isNextRouterError, isRedirectError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, renderServerInsertedHTML, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveCachedRscResponseExpiresAt, resolveCachedRscResponseTtlMs, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname, snapshotRscResponse, storePrefetchResponse, unauthorized, unstable_isUnrecognizedActionError, unstable_rethrow, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
@@ -4,7 +4,7 @@ import { assertSafeNavigationUrl } from "./url-safety.js";
4
4
  import { AppElementsWire } from "../server/app-elements-wire.js";
5
5
  import "../server/app-elements.js";
6
6
  import { AppRouterContext } from "./internal/app-router-context.js";
7
- import { isAbsoluteOrProtocolRelativeUrl, isHashOnlyBrowserUrlChange, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
7
+ import { isAbsoluteOrProtocolRelativeUrl, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
8
8
  import { retryScrollTo, scrollToHashTarget } from "./hash-scroll.js";
9
9
  import { getNavigationRuntime, hasAppNavigationRuntime } from "../client/navigation-runtime.js";
10
10
  import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
@@ -13,6 +13,7 @@ import { resolveManifestNavigationInterceptionContext } from "../server/app-brow
13
13
  import { createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata } from "../server/app-history-state.js";
14
14
  import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, createRscRequestHeaders, createRscRequestUrl, stripRscCacheBustingSearchParam } from "../server/app-rsc-cache-busting.js";
15
15
  import { hasPendingAppRouterPageRedirect } from "../server/app-browser-mpa-navigation.js";
16
+ import { navigationPlanner } from "../server/navigation-planner.js";
16
17
  import { ReadonlyURLSearchParams } from "./readonly-url-search-params.js";
17
18
  import { beginAppRouterScrollIntent, clearAppRouterScrollIntent, consumeAppRouterScrollIntent } from "./app-router-scroll-state.js";
18
19
  import { UnrecognizedActionError, unstable_isUnrecognizedActionError } from "./unrecognized-action-error.js";
@@ -715,6 +716,10 @@ function createClientNavigationRenderSnapshot(href, params) {
715
716
  params
716
717
  };
717
718
  }
719
+ function createSnapshotPathAndSearch(snapshot) {
720
+ const query = snapshot.searchParams.toString();
721
+ return query === "" ? snapshot.pathname : `${snapshot.pathname}?${query}`;
722
+ }
718
723
  let _fallbackClientParams = _EMPTY_PARAMS;
719
724
  let _fallbackClientParamsJson = "{}";
720
725
  function setClientParams(params) {
@@ -844,14 +849,6 @@ function useParams() {
844
849
  function isExternalUrl(href) {
845
850
  return isAbsoluteOrProtocolRelativeUrl(href);
846
851
  }
847
- /**
848
- * Check if a href is only a hash change relative to the current URL.
849
- */
850
- function isHashOnlyChange(href) {
851
- if (typeof window === "undefined") return false;
852
- if (href.startsWith("#")) return true;
853
- return isHashOnlyBrowserUrlChange(href, window.location.href, __basePath);
854
- }
855
852
  function withSuppressedUrlNotifications(fn) {
856
853
  const state = getClientNavigationState();
857
854
  if (!state) return fn();
@@ -963,7 +960,7 @@ function restoreScrollPosition(state) {
963
960
  /**
964
961
  * Navigate to a URL, handling external URLs, hash-only changes, and RSC navigation.
965
962
  */
966
- async function navigateClientSide(href, mode, scroll, programmaticTransition = false) {
963
+ async function navigateClientSide(href, mode, scroll, programmaticTransition = false, visibleCommitMode = "transition") {
967
964
  getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
968
965
  let normalizedHref = href;
969
966
  if (isExternalUrl(href)) {
@@ -985,11 +982,17 @@ async function navigateClientSide(href, mode, scroll, programmaticTransition = f
985
982
  const fullHref = toBrowserNavigationHref(normalizedHref, window.location.href, __basePath);
986
983
  notifyAppRouterTransitionStart(fullHref, mode);
987
984
  if (mode === "push") saveScrollPosition();
988
- if (isHashOnlyChange(fullHref)) {
989
- const hash = fullHref.includes("#") ? fullHref.slice(fullHref.indexOf("#")) : "";
990
- commitHashOnlyHistoryState(fullHref, mode, scroll);
985
+ const earlyIntent = navigationPlanner.classifyEarlyNavigationIntent({
986
+ basePath: __basePath,
987
+ currentHref: window.location.href,
988
+ mode,
989
+ scroll,
990
+ targetHref: fullHref
991
+ });
992
+ if (earlyIntent.kind === "sameDocumentScroll") {
993
+ commitHashOnlyHistoryState(fullHref, earlyIntent.mode, earlyIntent.scroll);
991
994
  commitClientNavigationState();
992
- if (scroll) scrollToHashTarget(hash);
995
+ if (earlyIntent.scroll) scrollToHashTarget(earlyIntent.hash);
993
996
  return;
994
997
  }
995
998
  if (hasPendingAppRouterPageRedirect(typeof document === "undefined" ? void 0 : document)) {
@@ -1009,7 +1012,7 @@ async function navigateClientSide(href, mode, scroll, programmaticTransition = f
1009
1012
  if (!scroll) clearAppRouterScrollIntent();
1010
1013
  const appNavigate = getNavigationRuntime()?.functions.navigate;
1011
1014
  try {
1012
- if (appNavigate) await appNavigate(fullHref, 0, "navigate", mode, void 0, programmaticTransition, void 0, scrollIntent);
1015
+ if (appNavigate) await appNavigate(fullHref, 0, "navigate", mode, void 0, programmaticTransition, void 0, scrollIntent, visibleCommitMode);
1013
1016
  else {
1014
1017
  if (mode === "replace") replaceHistoryStateWithoutNotify(null, "", fullHref);
1015
1018
  else pushHistoryStateWithoutNotify(null, "", fullHref);
@@ -1137,6 +1140,20 @@ const _appRouter = {
1137
1140
  });
1138
1141
  }
1139
1142
  };
1143
+ if (process.env.__NEXT_GESTURE_TRANSITION) _appRouter.experimental_gesturePush = (href, options) => {
1144
+ assertSafeNavigationUrl(href);
1145
+ if (isServer) return;
1146
+ if (!getNavigationRuntime()?.functions.navigate) return;
1147
+ let appHref = href;
1148
+ if (isAbsoluteOrProtocolRelativeUrl(href)) {
1149
+ const localPath = toSameOriginAppPath(href, __basePath);
1150
+ if (localPath === null) return;
1151
+ appHref = localPath;
1152
+ }
1153
+ const releaseNavigation = trackScheduledAppRouterNavigation();
1154
+ navigateClientSide(appHref, "push", options?.scroll !== false, false, "synchronous");
1155
+ releaseScheduledAppRouterNavigationAfterCurrentTask(releaseNavigation);
1156
+ };
1140
1157
  function formatPublicBfcacheId(value) {
1141
1158
  if (!value || value === "0") return PUBLIC_INITIAL_BFCACHE_ID;
1142
1159
  return value;
@@ -1585,4 +1602,4 @@ if (!isServer) {
1585
1602
  }
1586
1603
  }
1587
1604
  //#endregion
1588
- export { BailoutToCSRError, DynamicServerError, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, PREFETCH_CACHE_TTL, ReadonlyURLSearchParams, RedirectType, ServerInsertedHTMLContext, UnrecognizedActionError, __basePath, _registerStateAccessors, activateNavigationSnapshot, appRouterInstance, clearPendingPathname, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, decodeRedirectError, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getBfcacheIdMapContext, getBfcacheSegmentIdContext, getClientNavigationRenderContext, getClientNavigationState, getClientParams, getCurrentInterceptionContext, getCurrentNextUrl, getLayoutSegmentContext, getMountedSlotsHeader, getNavigationContext, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, isBailoutToCSRError, isDynamicServerError, isHTTPAccessFallbackError, isNextRouterError, isRedirectError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, renderServerInsertedHTML, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveCachedRscResponseExpiresAt, resolveCachedRscResponseTtlMs, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname, snapshotRscResponse, storePrefetchResponse, unauthorized, unstable_isUnrecognizedActionError, unstable_rethrow, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
1605
+ export { BailoutToCSRError, DynamicServerError, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, PREFETCH_CACHE_TTL, ReadonlyURLSearchParams, RedirectType, ServerInsertedHTMLContext, UnrecognizedActionError, __basePath, _registerStateAccessors, activateNavigationSnapshot, appRouterInstance, clearPendingPathname, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, decodeRedirectError, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getBfcacheIdMapContext, getBfcacheSegmentIdContext, getClientNavigationRenderContext, getClientNavigationState, getClientParams, getCurrentInterceptionContext, getCurrentNextUrl, getLayoutSegmentContext, getMountedSlotsHeader, getNavigationContext, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, isBailoutToCSRError, isDynamicServerError, isHTTPAccessFallbackError, isNextRouterError, isRedirectError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, renderServerInsertedHTML, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveCachedRscResponseExpiresAt, resolveCachedRscResponseTtlMs, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname, snapshotRscResponse, storePrefetchResponse, unauthorized, unstable_isUnrecognizedActionError, unstable_rethrow, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
@@ -1,5 +1,5 @@
1
1
  import { splitPathSegments } from "../routing/utils.js";
2
- import { stripBasePath } from "../utils/base-path.js";
2
+ import { removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
3
3
  import { assertSafeNavigationUrl } from "./url-safety.js";
4
4
  import { matchRoutePattern, routePatternParts } from "../routing/route-pattern.js";
5
5
  import { isUnknownRecord } from "../utils/record.js";
@@ -1017,6 +1017,14 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
1017
1017
  dispatchNavigateEvent();
1018
1018
  return true;
1019
1019
  }
1020
+ const appPath = getLocalPathname(resolved);
1021
+ const appPathNorm = appPath !== null ? removeTrailingSlash(appPath) : null;
1022
+ const appPathEntry = appPathNorm !== null ? getPagesRouterComponentsMap()[appPathNorm] : void 0;
1023
+ if (appPathEntry !== void 0 && "__appRouter" in appPathEntry && appPathEntry.__appRouter) {
1024
+ if (mode === "push") window.location.assign(full);
1025
+ else window.location.replace(full);
1026
+ return new Promise(() => {});
1027
+ }
1020
1028
  if (mode === "push") saveScrollPosition();
1021
1029
  routerEvents.emit("routeChangeStart", resolved, { shallow });
1022
1030
  routerEvents.emit("beforeHistoryChange", resolved, { shallow });
@@ -1338,6 +1346,25 @@ const Router = Object.defineProperties(RouterMethods, {
1338
1346
  }
1339
1347
  }
1340
1348
  });
1349
+ for (const event of [
1350
+ "routeChangeStart",
1351
+ "beforeHistoryChange",
1352
+ "routeChangeComplete",
1353
+ "routeChangeError",
1354
+ "hashChangeStart",
1355
+ "hashChangeComplete"
1356
+ ]) {
1357
+ const eventField = `on${event.charAt(0).toUpperCase()}${event.substring(1)}`;
1358
+ routerEvents.on(event, (...args) => {
1359
+ const handler = Router[eventField];
1360
+ if (typeof handler === "function") try {
1361
+ handler(...args);
1362
+ } catch (err) {
1363
+ console.error(`Error when running the Router event: ${eventField}`);
1364
+ console.error(err instanceof Error ? `${err.message}\n${err.stack}` : String(err));
1365
+ }
1366
+ });
1367
+ }
1341
1368
  if (typeof window !== "undefined") installWindowNext({ router: Router });
1342
1369
  const _PAGES_NAVIGATION_ACCESSOR_KEY = Symbol.for("vinext.navigation.pagesNavigationContextAccessor");
1343
1370
  globalThis[_PAGES_NAVIGATION_ACCESSOR_KEY] = getPagesNavigationContext;
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  //#region src/shims/script-nonce-context.d.ts
4
- declare const ScriptNonceContext: React.Context<string | undefined>;
4
+ declare const ScriptNonceContext: React.Context<string | undefined> | null;
5
5
  declare function ScriptNonceProvider(props: React.PropsWithChildren<{
6
6
  nonce?: string;
7
7
  }>): React.ReactElement;
@@ -1,15 +1,23 @@
1
1
  import React from "react";
2
2
  //#region src/shims/script-nonce-context.tsx
3
- const ScriptNonceContext = React.createContext(void 0);
3
+ const ScriptNonceContext = typeof React.createContext === "function" ? React.createContext(void 0) : null;
4
4
  function ScriptNonceProvider(props) {
5
+ if (!ScriptNonceContext) return React.createElement(React.Fragment, null, props.children);
5
6
  return React.createElement(ScriptNonceContext.Provider, { value: props.nonce }, props.children);
6
7
  }
7
8
  function withScriptNonce(element, nonce) {
8
- if (!nonce) return element;
9
+ if (!nonce || !ScriptNonceContext) return element;
9
10
  return React.createElement(ScriptNonceProvider, { nonce }, element);
10
11
  }
12
+ function createScriptNonceHook(context) {
13
+ if (!context || typeof React.useContext !== "function") return function useScriptNonceFromContext() {};
14
+ return function useScriptNonceFromContext() {
15
+ return React.useContext(context);
16
+ };
17
+ }
18
+ const useScriptNonceFromContext = createScriptNonceHook(ScriptNonceContext);
11
19
  function useScriptNonce() {
12
- return React.useContext(ScriptNonceContext);
20
+ return useScriptNonceFromContext();
13
21
  }
14
22
  //#endregion
15
23
  export { ScriptNonceContext, ScriptNonceProvider, useScriptNonce, withScriptNonce };
@@ -91,13 +91,29 @@ type NextURLConfig = {
91
91
  declare class NextURL {
92
92
  /** Internal URL stores the pathname WITHOUT basePath or locale prefix. */
93
93
  private _url;
94
+ /**
95
+ * The configured basePath (from nextConfig). May differ from the active
96
+ * `_basePath`: parsing only activates basePath when the URL's pathname
97
+ * actually carries the configured prefix.
98
+ */
99
+ private _configBasePath;
94
100
  private _basePath;
95
101
  private _trailingSlash;
96
102
  private _locale;
97
103
  private _defaultLocale;
98
104
  private _locales;
99
105
  constructor(input: string | URL, base?: string | URL, config?: NextURLConfig);
100
- /** Strip basePath prefix from the internal pathname. */
106
+ /** Strip basePath prefix from the internal pathname.
107
+ * Mirrors Next.js's getNextPathnameInfo (re-run by NextURL.analyze() on
108
+ * every parse, including `href` reassignment): basePath is only considered
109
+ * active when the URL's pathname actually starts with the configured
110
+ * basePath prefix. If the pathname is outside the basePath, the active
111
+ * basePath is cleared to "" so that request.nextUrl.basePath reflects the
112
+ * actual URL rather than the config value; if a later `href` assignment
113
+ * moves the URL back inside the basePath, it is re-activated from the
114
+ * configured value. This matches the Next.js behavior tested by
115
+ * middleware-base-path's "should execute from absolute paths" case.
116
+ */
101
117
  private _stripBasePath;
102
118
  /** Extract locale from pathname, stripping it from the internal URL. */
103
119
  private _analyzeLocale;
@@ -1,4 +1,4 @@
1
- import { stripBasePath } from "../utils/base-path.js";
1
+ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
2
2
  import { getRequestExecutionContext } from "./request-context.js";
3
3
  import { MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER } from "../server/headers.js";
4
4
  import { encodeMiddlewareRequestHeaders } from "../server/middleware-request-headers.js";
@@ -183,6 +183,12 @@ var NextResponse = class NextResponse extends Response {
183
183
  var NextURL = class NextURL {
184
184
  /** Internal URL stores the pathname WITHOUT basePath or locale prefix. */
185
185
  _url;
186
+ /**
187
+ * The configured basePath (from nextConfig). May differ from the active
188
+ * `_basePath`: parsing only activates basePath when the URL's pathname
189
+ * actually carries the configured prefix.
190
+ */
191
+ _configBasePath;
186
192
  _basePath;
187
193
  _trailingSlash;
188
194
  _locale;
@@ -190,7 +196,8 @@ var NextURL = class NextURL {
190
196
  _locales;
191
197
  constructor(input, base, config) {
192
198
  this._url = new URL(input.toString(), base);
193
- this._basePath = config?.basePath ?? "";
199
+ this._configBasePath = config?.basePath ?? "";
200
+ this._basePath = this._configBasePath;
194
201
  this._trailingSlash = config?.nextConfig?.trailingSlash ?? false;
195
202
  this._stripBasePath();
196
203
  const i18n = config?.nextConfig?.i18n;
@@ -200,10 +207,25 @@ var NextURL = class NextURL {
200
207
  this._analyzeLocale(this._locales);
201
208
  }
202
209
  }
203
- /** Strip basePath prefix from the internal pathname. */
210
+ /** Strip basePath prefix from the internal pathname.
211
+ * Mirrors Next.js's getNextPathnameInfo (re-run by NextURL.analyze() on
212
+ * every parse, including `href` reassignment): basePath is only considered
213
+ * active when the URL's pathname actually starts with the configured
214
+ * basePath prefix. If the pathname is outside the basePath, the active
215
+ * basePath is cleared to "" so that request.nextUrl.basePath reflects the
216
+ * actual URL rather than the config value; if a later `href` assignment
217
+ * moves the URL back inside the basePath, it is re-activated from the
218
+ * configured value. This matches the Next.js behavior tested by
219
+ * middleware-base-path's "should execute from absolute paths" case.
220
+ */
204
221
  _stripBasePath() {
205
- if (!this._basePath) return;
206
- this._url.pathname = stripBasePath(this._url.pathname, this._basePath);
222
+ if (!this._configBasePath) return;
223
+ if (!hasBasePath(this._url.pathname, this._configBasePath)) {
224
+ this._basePath = "";
225
+ return;
226
+ }
227
+ this._basePath = this._configBasePath;
228
+ this._url.pathname = stripBasePath(this._url.pathname, this._configBasePath);
207
229
  }
208
230
  /** Extract locale from pathname, stripping it from the internal URL. */
209
231
  _analyzeLocale(locales) {
@@ -655,10 +677,13 @@ function after(task) {
655
677
  * and sets Cache-Control: no-store on the response.
656
678
  */
657
679
  async function connection() {
658
- const { markDynamicUsage, markRenderRequestApiUsage, throwIfInsideCacheScope } = await import("./headers.js");
680
+ const { getHeadersContext, markDynamicUsage, markRenderRequestApiUsage, suspendConnectionProbe, throwIfInsideCacheScope } = await import("./headers.js");
681
+ if (getHeadersContext()?.forceStatic) return;
659
682
  markRenderRequestApiUsage("connection");
660
683
  throwIfInsideCacheScope("connection()");
661
684
  markDynamicUsage();
685
+ const pendingProbe = suspendConnectionProbe();
686
+ if (pendingProbe) await pendingProbe;
662
687
  }
663
688
  /**
664
689
  * URLPattern re-export — used in middleware for route matching.
@@ -23,7 +23,7 @@ const MAX_BFCACHE_SLOT_ENTRIES_WITH_CACHE_COMPONENTS = 3;
23
23
  const MAX_BFCACHE_SLOT_ENTRIES_WITHOUT_CACHE_COMPONENTS = 1;
24
24
  const BfcacheStateKeyMapContext = React$1.createContext(EMPTY_BFCACHE_STATE_KEYS);
25
25
  function isCacheComponentsEnabled() {
26
- return process.env.__NEXT_CACHE_COMPONENTS === "true";
26
+ return String(process.env.__NEXT_CACHE_COMPONENTS) === "true";
27
27
  }
28
28
  function getBfcacheSlotEntryLimit() {
29
29
  return isCacheComponentsEnabled() ? MAX_BFCACHE_SLOT_ENTRIES_WITH_CACHE_COMPONENTS : MAX_BFCACHE_SLOT_ENTRIES_WITHOUT_CACHE_COMPONENTS;
@@ -18,6 +18,7 @@ function createRequestContext(opts) {
18
18
  actionRevalidationKind: 0,
19
19
  dynamicUsageDetected: false,
20
20
  renderRequestApiUsage: /* @__PURE__ */ new Set(),
21
+ connectionProbe: null,
21
22
  invalidDynamicUsageError: null,
22
23
  pendingSetCookies: [],
23
24
  draftModeCookieHeader: null,
package/dist/typegen.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { decodeRouteSegment, isInvisibleSegment } from "./routing/utils.js";
2
2
  import { patternToNextFormat } from "./routing/route-validation.js";
3
3
  import { compareStrings } from "./utils/compare.js";
4
+ import "./routing/app-route-graph.js";
4
5
  import { appRouteGraph } from "./routing/app-router.js";
5
6
  import path from "node:path";
6
7
  import fs from "node:fs/promises";
@@ -47,16 +47,26 @@ function findEntryFileFromManifest(buildManifest, assetBase, markers, fallbackTo
47
47
  const chosen = fallbackToFirstEntry ? entries[0] : void 0;
48
48
  return chosen ? manifestFileWithBase(chosen.file, assetBase) : void 0;
49
49
  }
50
+ function listFilesRecursive(dir) {
51
+ const out = [];
52
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
53
+ const full = path.join(dir, entry.name);
54
+ if (entry.isDirectory()) out.push(...listFilesRecursive(full));
55
+ else if (entry.isFile()) out.push(full);
56
+ }
57
+ return out;
58
+ }
50
59
  function findClientEntryFileInAssetsDir(options) {
51
60
  const assetsDir = path.join(options.clientDir, options.assetsSubdir);
52
61
  if (!fs.existsSync(assetsDir)) return void 0;
53
- const files = fs.readdirSync(assetsDir);
54
- let entry;
62
+ const files = listFilesRecursive(assetsDir);
63
+ let entryFull;
55
64
  for (const marker of options.markers) {
56
- entry = files.find((file) => file.includes(marker) && file.endsWith(".js"));
57
- if (entry) break;
65
+ entryFull = files.find((file) => path.basename(file).includes(marker) && file.endsWith(".js"));
66
+ if (entryFull) break;
58
67
  }
59
- return entry ? manifestFileWithBase(`${options.assetsSubdir}/${entry}`, options.assetBase) : void 0;
68
+ if (!entryFull) return void 0;
69
+ return manifestFileWithBase(path.relative(options.clientDir, entryFull).split(path.sep).join("/"), options.assetBase);
60
70
  }
61
71
  function findClientEntryFile(options) {
62
72
  return (options.buildManifest ? findClientEntryFileFromManifest(options.buildManifest, options.assetBase) : void 0) ?? findClientEntryFileInAssetsDir({
@@ -0,0 +1,45 @@
1
+ //#region src/utils/client-runtime-metadata.d.ts
2
+ type ClientRuntimeMetadata = {
3
+ clientEntryFile?: string;
4
+ lazyChunks?: string[];
5
+ dynamicPreloads?: Record<string, string[]>;
6
+ };
7
+ /**
8
+ * Read the client build manifest and compute runtime metadata used by
9
+ * Cloudflare worker entry injection and Node production server startup.
10
+ *
11
+ * - `lazyChunks` — chunks only reachable through dynamic `import()`, excluded
12
+ * from modulepreload hints.
13
+ * - `dynamicPreloads` — per-module JS/CSS files for rendered `next/dynamic()`
14
+ * boundaries, injected as preload links during SSR.
15
+ * - `clientEntryFile` — the client entry chunk filename (optional, only
16
+ * needed for Pages Router).
17
+ *
18
+ * All file paths are normalised with the configured `assetBase` (basePath)
19
+ * and `assetPrefix`.
20
+ */
21
+ declare function computeClientRuntimeMetadata(opts: {
22
+ clientDir: string;
23
+ assetBase: string;
24
+ assetPrefix: string;
25
+ includeClientEntry?: boolean | "pages-client-entry";
26
+ }): ClientRuntimeMetadata;
27
+ /**
28
+ * Serialize runtime metadata into the `globalThis.__VINEXT_*` assignment script
29
+ * that the Cloudflare `closeBundle` hook prepends to the worker entry. Returns
30
+ * `""` when there is nothing to inject.
31
+ *
32
+ * Both the App Router and Pages Router closeBundle paths call this (and the
33
+ * deploy tests mirror it), so the injection shape stays in one place. The caller
34
+ * decides which fields to pass — e.g. App Router only forwards `clientEntryFile`
35
+ * for mixed app+pages builds (where `computeClientRuntimeMetadata` was asked for
36
+ * the Pages client entry); pure App Router leaves it undefined.
37
+ */
38
+ declare function buildRuntimeGlobalsScript(input: {
39
+ clientEntryFile?: string | null;
40
+ ssrManifest?: Record<string, string[]> | null;
41
+ lazyChunks?: string[] | null;
42
+ dynamicPreloads?: Record<string, string[]> | null;
43
+ }): string;
44
+ //#endregion
45
+ export { buildRuntimeGlobalsScript, computeClientRuntimeMetadata };
@@ -0,0 +1,63 @@
1
+ import { resolveAssetsDir } from "./asset-prefix.js";
2
+ import { manifestFileWithAssetPrefix, manifestFileWithBase } from "./manifest-paths.js";
3
+ import { findClientEntryFile, findPagesClientEntryFile, readClientBuildManifest } from "./client-build-manifest.js";
4
+ import { findClientEntryFileFromVinextManifest, findPagesClientEntryFileFromVinextManifest, readClientEntryManifest } from "./client-entry-manifest.js";
5
+ import { computeDynamicImportPreloads, computeLazyChunks, dynamicImportPreloadsWithBase } from "./lazy-chunks.js";
6
+ import path from "node:path";
7
+ //#region src/utils/client-runtime-metadata.ts
8
+ /**
9
+ * Read the client build manifest and compute runtime metadata used by
10
+ * Cloudflare worker entry injection and Node production server startup.
11
+ *
12
+ * - `lazyChunks` — chunks only reachable through dynamic `import()`, excluded
13
+ * from modulepreload hints.
14
+ * - `dynamicPreloads` — per-module JS/CSS files for rendered `next/dynamic()`
15
+ * boundaries, injected as preload links during SSR.
16
+ * - `clientEntryFile` — the client entry chunk filename (optional, only
17
+ * needed for Pages Router).
18
+ *
19
+ * All file paths are normalised with the configured `assetBase` (basePath)
20
+ * and `assetPrefix`.
21
+ */
22
+ function computeClientRuntimeMetadata(opts) {
23
+ const buildManifest = readClientBuildManifest(path.join(opts.clientDir, ".vite", "manifest.json"));
24
+ const metadata = {};
25
+ if (opts.includeClientEntry) {
26
+ const clientEntryManifest = readClientEntryManifest(opts.clientDir);
27
+ const entryOptions = {
28
+ buildManifest,
29
+ clientDir: opts.clientDir,
30
+ assetsSubdir: resolveAssetsDir(opts.assetPrefix),
31
+ assetBase: opts.assetBase
32
+ };
33
+ const entry = opts.includeClientEntry === "pages-client-entry" ? findPagesClientEntryFileFromVinextManifest(clientEntryManifest, opts.assetBase) ?? findPagesClientEntryFile(entryOptions) : findClientEntryFileFromVinextManifest(clientEntryManifest, opts.assetBase) ?? findClientEntryFile(entryOptions);
34
+ if (entry) metadata.clientEntryFile = entry;
35
+ }
36
+ if (!buildManifest) return metadata;
37
+ const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, opts.assetBase));
38
+ if (lazyChunks.length > 0) metadata.lazyChunks = lazyChunks;
39
+ const dynamicPreloads = dynamicImportPreloadsWithBase(computeDynamicImportPreloads(buildManifest), (file) => manifestFileWithAssetPrefix(file, opts.assetBase, opts.assetPrefix));
40
+ if (Object.keys(dynamicPreloads).length > 0) metadata.dynamicPreloads = dynamicPreloads;
41
+ return metadata;
42
+ }
43
+ /**
44
+ * Serialize runtime metadata into the `globalThis.__VINEXT_*` assignment script
45
+ * that the Cloudflare `closeBundle` hook prepends to the worker entry. Returns
46
+ * `""` when there is nothing to inject.
47
+ *
48
+ * Both the App Router and Pages Router closeBundle paths call this (and the
49
+ * deploy tests mirror it), so the injection shape stays in one place. The caller
50
+ * decides which fields to pass — e.g. App Router only forwards `clientEntryFile`
51
+ * for mixed app+pages builds (where `computeClientRuntimeMetadata` was asked for
52
+ * the Pages client entry); pure App Router leaves it undefined.
53
+ */
54
+ function buildRuntimeGlobalsScript(input) {
55
+ const globals = [];
56
+ if (input.clientEntryFile) globals.push(`globalThis.__VINEXT_CLIENT_ENTRY__ = ${JSON.stringify(input.clientEntryFile)};`);
57
+ if (input.ssrManifest && Object.keys(input.ssrManifest).length > 0) globals.push(`globalThis.__VINEXT_SSR_MANIFEST__ = ${JSON.stringify(input.ssrManifest)};`);
58
+ if (input.lazyChunks && input.lazyChunks.length > 0) globals.push(`globalThis.__VINEXT_LAZY_CHUNKS__ = ${JSON.stringify(input.lazyChunks)};`);
59
+ if (input.dynamicPreloads && Object.keys(input.dynamicPreloads).length > 0) globals.push(`globalThis.__VINEXT_DYNAMIC_PRELOADS__ = ${JSON.stringify(input.dynamicPreloads)};`);
60
+ return globals.join("\n");
61
+ }
62
+ //#endregion
63
+ export { buildRuntimeGlobalsScript, computeClientRuntimeMetadata };
@@ -2,7 +2,23 @@
2
2
  /**
3
3
  * FNV-1a hash producing a 64-bit result (two 32-bit rounds with different seeds).
4
4
  * Used for deterministic key generation where collisions must be rare.
5
+ *
6
+ * This is a vinext-internal format: nothing outside vinext ever compares
7
+ * these values, so the algorithm only needs to be deterministic. For values
8
+ * that must be byte-for-byte identical to what Next.js emits (ETags), use
9
+ * `fnv1a52` below instead — the two are NOT interchangeable.
5
10
  */
6
11
  declare function fnv1a64(input: string): string;
12
+ /**
13
+ * FNV-1a hash producing a 52-bit result, a byte-for-byte port of Next.js's
14
+ * `fnv1a52` in packages/next/src/server/lib/etag.ts (itself derived from
15
+ * fnv-plus). Used for ETag generation, where matching Next.js's exact output
16
+ * matters: clients and CDNs holding `If-None-Match` values from a Next.js
17
+ * deployment keep revalidating (304) against vinext for unchanged payloads.
18
+ *
19
+ * Deliberately separate from `fnv1a64` above — that one is a vinext-internal
20
+ * key format and produces different values. Do not swap one for the other.
21
+ */
22
+ declare function fnv1a52(str: string): number;
7
23
  //#endregion
8
- export { fnv1a64 };
24
+ export { fnv1a52, fnv1a64 };
@@ -2,6 +2,11 @@
2
2
  /**
3
3
  * FNV-1a hash producing a 64-bit result (two 32-bit rounds with different seeds).
4
4
  * Used for deterministic key generation where collisions must be rare.
5
+ *
6
+ * This is a vinext-internal format: nothing outside vinext ever compares
7
+ * these values, so the algorithm only needs to be deterministic. For values
8
+ * that must be byte-for-byte identical to what Next.js emits (ETags), use
9
+ * `fnv1a52` below instead — the two are NOT interchangeable.
5
10
  */
6
11
  function fnv1a64(input) {
7
12
  let h1 = 2166136261;
@@ -16,5 +21,35 @@ function fnv1a64(input) {
16
21
  }
17
22
  return h1.toString(16).padStart(8, "0") + h2.toString(16).padStart(8, "0");
18
23
  }
24
+ /**
25
+ * FNV-1a hash producing a 52-bit result, a byte-for-byte port of Next.js's
26
+ * `fnv1a52` in packages/next/src/server/lib/etag.ts (itself derived from
27
+ * fnv-plus). Used for ETag generation, where matching Next.js's exact output
28
+ * matters: clients and CDNs holding `If-None-Match` values from a Next.js
29
+ * deployment keep revalidating (304) against vinext for unchanged payloads.
30
+ *
31
+ * Deliberately separate from `fnv1a64` above — that one is a vinext-internal
32
+ * key format and produces different values. Do not swap one for the other.
33
+ */
34
+ function fnv1a52(str) {
35
+ const len = str.length;
36
+ let i = 0, t0 = 0, v0 = 8997, t1 = 0, v1 = 33826, t2 = 0, v2 = 40164, t3 = 0, v3 = 52210;
37
+ while (i < len) {
38
+ v0 ^= str.charCodeAt(i++);
39
+ t0 = v0 * 435;
40
+ t1 = v1 * 435;
41
+ t2 = v2 * 435;
42
+ t3 = v3 * 435;
43
+ t2 += v0 << 8;
44
+ t3 += v1 << 8;
45
+ t1 += t0 >>> 16;
46
+ v0 = t0 & 65535;
47
+ t2 += t1 >>> 16;
48
+ v1 = t1 & 65535;
49
+ v3 = t3 + (t2 >>> 16) & 65535;
50
+ v2 = t2 & 65535;
51
+ }
52
+ return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ v3 >> 4);
53
+ }
19
54
  //#endregion
20
- export { fnv1a64 };
55
+ export { fnv1a52, fnv1a64 };
@@ -29,5 +29,31 @@ type BuildManifestChunk = {
29
29
  * should be excluded from modulepreload hints.
30
30
  */
31
31
  declare function computeLazyChunks(buildManifest: Record<string, BuildManifestChunk>): string[];
32
+ /**
33
+ * Compute the production preload files for each module referenced by a
34
+ * `next/dynamic()` boundary.
35
+ *
36
+ * Next.js records module IDs during compilation, then resolves those IDs
37
+ * against its react-loadable manifest at render time. Vinext's equivalent
38
+ * source of truth is Vite's build manifest: each chunk lists the modules it
39
+ * reaches through `dynamicImports`, and each dynamic entry lists the JS/CSS
40
+ * files required to evaluate it.
41
+ *
42
+ * Note on shared chunks: a boundary's static-import tree (`collectStaticChunkFiles`)
43
+ * can include chunks that the page entry ALSO loads eagerly (a shared vendor
44
+ * chunk imported by both). Those files are intentionally NOT subtracted here, so
45
+ * a rendered boundary may emit a `<link rel="preload">` / `<link rel="stylesheet">`
46
+ * for a chunk the page already `<link rel="modulepreload">`s. This is harmless —
47
+ * the browser dedupes preloads by URL, `ReactDOM.preload()` dedupes script hints,
48
+ * and React's stylesheet resource model dedupes by href + precedence — and it
49
+ * mirrors Next.js listing a module's full file set in its react-loadable
50
+ * manifest. Subtracting the eager set would couple this to the entry's import
51
+ * closure for no correctness gain.
52
+ *
53
+ * @returns A map keyed by root-relative module ID, with JS/CSS files that
54
+ * should be preloaded when that dynamic boundary is rendered.
55
+ */
56
+ declare function computeDynamicImportPreloads(buildManifest: Record<string, BuildManifestChunk>): Record<string, string[]>;
57
+ declare function dynamicImportPreloadsWithBase(preloads: Record<string, string[]>, applyBase: (file: string) => string): Record<string, string[]>;
32
58
  //#endregion
33
- export { BuildManifestChunk, computeLazyChunks };
59
+ export { BuildManifestChunk, computeDynamicImportPreloads, computeLazyChunks, dynamicImportPreloadsWithBase };