vinext 0.0.41 → 0.0.43

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 (166) hide show
  1. package/README.md +0 -1
  2. package/dist/build/client-build-config.d.ts +119 -0
  3. package/dist/build/client-build-config.js +149 -0
  4. package/dist/build/client-build-config.js.map +1 -0
  5. package/dist/build/layout-classification-types.d.ts +62 -0
  6. package/dist/build/layout-classification-types.js +1 -0
  7. package/dist/build/layout-classification.d.ts +60 -0
  8. package/dist/build/layout-classification.js +98 -0
  9. package/dist/build/layout-classification.js.map +1 -0
  10. package/dist/build/report.d.ts +15 -1
  11. package/dist/build/report.js +50 -1
  12. package/dist/build/report.js.map +1 -1
  13. package/dist/build/route-classification-manifest.d.ts +53 -0
  14. package/dist/build/route-classification-manifest.js +145 -0
  15. package/dist/build/route-classification-manifest.js.map +1 -0
  16. package/dist/build/run-prerender.js +1 -1
  17. package/dist/build/ssr-manifest.d.ts +19 -0
  18. package/dist/build/ssr-manifest.js +71 -0
  19. package/dist/build/ssr-manifest.js.map +1 -0
  20. package/dist/check.js +2 -2
  21. package/dist/check.js.map +1 -1
  22. package/dist/cli.js +1 -1
  23. package/dist/client/entry.js +1 -1
  24. package/dist/config/config-matchers.js +1 -0
  25. package/dist/config/config-matchers.js.map +1 -1
  26. package/dist/entries/app-rsc-entry.js +315 -101
  27. package/dist/entries/app-rsc-entry.js.map +1 -1
  28. package/dist/index.d.ts +1 -169
  29. package/dist/index.js +112 -432
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/fonts.d.ts +49 -1
  32. package/dist/plugins/fonts.js +96 -3
  33. package/dist/plugins/fonts.js.map +1 -1
  34. package/dist/plugins/postcss.d.ts +27 -0
  35. package/dist/plugins/postcss.js +94 -0
  36. package/dist/plugins/postcss.js.map +1 -0
  37. package/dist/plugins/strip-server-exports.d.ts +14 -0
  38. package/dist/plugins/strip-server-exports.js +73 -0
  39. package/dist/plugins/strip-server-exports.js.map +1 -0
  40. package/dist/routing/app-router.d.ts +6 -4
  41. package/dist/routing/app-router.js +44 -25
  42. package/dist/routing/app-router.js.map +1 -1
  43. package/dist/server/app-browser-entry.js +307 -100
  44. package/dist/server/app-browser-entry.js.map +1 -1
  45. package/dist/server/app-browser-error.d.ts +8 -0
  46. package/dist/server/app-browser-error.js +9 -0
  47. package/dist/server/app-browser-error.js.map +1 -0
  48. package/dist/server/app-browser-state.d.ts +93 -0
  49. package/dist/server/app-browser-state.js +132 -0
  50. package/dist/server/app-browser-state.js.map +1 -0
  51. package/dist/server/app-elements.d.ts +92 -0
  52. package/dist/server/app-elements.js +122 -0
  53. package/dist/server/app-elements.js.map +1 -0
  54. package/dist/server/app-page-boundary-render.d.ts +2 -1
  55. package/dist/server/app-page-boundary-render.js +40 -1
  56. package/dist/server/app-page-boundary-render.js.map +1 -1
  57. package/dist/server/app-page-cache.d.ts +6 -3
  58. package/dist/server/app-page-cache.js +14 -8
  59. package/dist/server/app-page-cache.js.map +1 -1
  60. package/dist/server/app-page-execution.d.ts +36 -3
  61. package/dist/server/app-page-execution.js +50 -10
  62. package/dist/server/app-page-execution.js.map +1 -1
  63. package/dist/server/app-page-probe.d.ts +10 -4
  64. package/dist/server/app-page-probe.js +24 -15
  65. package/dist/server/app-page-probe.js.map +1 -1
  66. package/dist/server/app-page-render.d.ts +7 -4
  67. package/dist/server/app-page-render.js +13 -4
  68. package/dist/server/app-page-render.js.map +1 -1
  69. package/dist/server/app-page-request.d.ts +52 -4
  70. package/dist/server/app-page-request.js +86 -16
  71. package/dist/server/app-page-request.js.map +1 -1
  72. package/dist/server/app-page-response.d.ts +1 -0
  73. package/dist/server/app-page-response.js +1 -0
  74. package/dist/server/app-page-response.js.map +1 -1
  75. package/dist/server/app-page-route-wiring.d.ts +22 -8
  76. package/dist/server/app-page-route-wiring.js +219 -83
  77. package/dist/server/app-page-route-wiring.js.map +1 -1
  78. package/dist/server/app-render-dependency.d.ts +13 -0
  79. package/dist/server/app-render-dependency.js +35 -0
  80. package/dist/server/app-render-dependency.js.map +1 -0
  81. package/dist/server/app-route-handler-execution.d.ts +1 -0
  82. package/dist/server/app-route-handler-execution.js +1 -0
  83. package/dist/server/app-route-handler-execution.js.map +1 -1
  84. package/dist/server/app-route-handler-policy.js +5 -3
  85. package/dist/server/app-route-handler-policy.js.map +1 -1
  86. package/dist/server/app-route-handler-response.js +2 -0
  87. package/dist/server/app-route-handler-response.js.map +1 -1
  88. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  89. package/dist/server/app-route-handler-runtime.js +26 -1
  90. package/dist/server/app-route-handler-runtime.js.map +1 -1
  91. package/dist/server/app-ssr-entry.js +6 -2
  92. package/dist/server/app-ssr-entry.js.map +1 -1
  93. package/dist/server/dev-server.js +2 -4
  94. package/dist/server/dev-server.js.map +1 -1
  95. package/dist/server/middleware.js +1 -5
  96. package/dist/server/middleware.js.map +1 -1
  97. package/dist/server/prod-server.d.ts +3 -3
  98. package/dist/server/prod-server.js +1 -1
  99. package/dist/server/prod-server.js.map +1 -1
  100. package/dist/server/request-pipeline.d.ts +2 -1
  101. package/dist/server/request-pipeline.js +34 -5
  102. package/dist/server/request-pipeline.js.map +1 -1
  103. package/dist/shims/cache-runtime.d.ts +1 -0
  104. package/dist/shims/cache-runtime.js +0 -5
  105. package/dist/shims/cache-runtime.js.map +1 -1
  106. package/dist/shims/cache.d.ts +1 -0
  107. package/dist/shims/cache.js +1 -8
  108. package/dist/shims/cache.js.map +1 -1
  109. package/dist/shims/client-hook-error.d.ts +14 -0
  110. package/dist/shims/client-hook-error.js +19 -0
  111. package/dist/shims/client-hook-error.js.map +1 -0
  112. package/dist/shims/constants.d.ts +3 -3
  113. package/dist/shims/constants.js +3 -3
  114. package/dist/shims/constants.js.map +1 -1
  115. package/dist/shims/document.d.ts +6 -6
  116. package/dist/shims/error-boundary.d.ts +4 -4
  117. package/dist/shims/error-boundary.js +1 -1
  118. package/dist/shims/error-boundary.js.map +1 -1
  119. package/dist/shims/form.d.ts +3 -3
  120. package/dist/shims/head-state.d.ts +1 -0
  121. package/dist/shims/head-state.js +0 -5
  122. package/dist/shims/head-state.js.map +1 -1
  123. package/dist/shims/headers.d.ts +11 -0
  124. package/dist/shims/headers.js +13 -10
  125. package/dist/shims/headers.js.map +1 -1
  126. package/dist/shims/i18n-state.d.ts +1 -0
  127. package/dist/shims/i18n-state.js +0 -4
  128. package/dist/shims/i18n-state.js.map +1 -1
  129. package/dist/shims/internal/app-router-context.d.ts +6 -6
  130. package/dist/shims/internal/router-context.d.ts +2 -2
  131. package/dist/shims/layout-segment-context.d.ts +2 -2
  132. package/dist/shims/link.js +19 -11
  133. package/dist/shims/link.js.map +1 -1
  134. package/dist/shims/metadata.d.ts +3 -3
  135. package/dist/shims/navigation-state.d.ts +2 -0
  136. package/dist/shims/navigation-state.js +0 -13
  137. package/dist/shims/navigation-state.js.map +1 -1
  138. package/dist/shims/navigation.d.ts +56 -9
  139. package/dist/shims/navigation.js +112 -28
  140. package/dist/shims/navigation.js.map +1 -1
  141. package/dist/shims/navigation.react-server.d.ts +14 -0
  142. package/dist/shims/navigation.react-server.js +29 -0
  143. package/dist/shims/navigation.react-server.js.map +1 -0
  144. package/dist/shims/request-context.d.ts +1 -0
  145. package/dist/shims/request-context.js +0 -9
  146. package/dist/shims/request-context.js.map +1 -1
  147. package/dist/shims/request-state-types.d.ts +1 -1
  148. package/dist/shims/router-state.d.ts +1 -0
  149. package/dist/shims/router-state.js +0 -5
  150. package/dist/shims/router-state.js.map +1 -1
  151. package/dist/shims/slot.d.ts +11 -7
  152. package/dist/shims/slot.js +28 -19
  153. package/dist/shims/slot.js.map +1 -1
  154. package/dist/shims/unified-request-context.d.ts +2 -0
  155. package/dist/shims/unified-request-context.js +0 -14
  156. package/dist/shims/unified-request-context.js.map +1 -1
  157. package/dist/utils/mdx-scan.d.ts +10 -0
  158. package/dist/utils/mdx-scan.js +36 -0
  159. package/dist/utils/mdx-scan.js.map +1 -0
  160. package/dist/utils/public-routes.d.ts +5 -0
  161. package/dist/utils/public-routes.js +50 -0
  162. package/dist/utils/public-routes.js.map +1 -0
  163. package/package.json +3 -3
  164. package/dist/plugins/fix-use-server-closure-collision.d.ts +0 -29
  165. package/dist/plugins/fix-use-server-closure-collision.js +0 -204
  166. package/dist/plugins/fix-use-server-closure-collision.js.map +0 -1
@@ -2,7 +2,8 @@
2
2
  import { resolveRelativeHref, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
3
3
  import { addLocalePrefix, getDomainLocaleUrl } from "../utils/domain-locale.js";
4
4
  import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.js";
5
- import { getPrefetchedUrls, navigateClientSide, prefetchRscResponse, toRscUrl } from "./navigation.js";
5
+ import { createAppPayloadCacheKey } from "../server/app-elements.js";
6
+ import { getCurrentInterceptionContext, getMountedSlotsHeader, getPrefetchedUrls, navigateClientSide, prefetchRscResponse, toRscUrl } from "./navigation.js";
6
7
  import { isDangerousScheme } from "./url-safety.js";
7
8
  import { getI18nContext } from "./i18n-context.js";
8
9
  import React, { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
@@ -55,17 +56,24 @@ function prefetchUrl(href) {
55
56
  }
56
57
  const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);
57
58
  const rscUrl = toRscUrl(fullHref);
59
+ const interceptionContext = getCurrentInterceptionContext();
60
+ const cacheKey = createAppPayloadCacheKey(rscUrl, interceptionContext);
58
61
  const prefetched = getPrefetchedUrls();
59
- if (prefetched.has(rscUrl)) return;
60
- prefetched.add(rscUrl);
62
+ if (prefetched.has(cacheKey)) return;
63
+ prefetched.add(cacheKey);
61
64
  (window.requestIdleCallback ?? ((fn) => setTimeout(fn, 100)))(() => {
62
- if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") prefetchRscResponse(rscUrl, fetch(rscUrl, {
63
- headers: { Accept: "text/x-component" },
64
- credentials: "include",
65
- priority: "low",
66
- purpose: "prefetch"
67
- }));
68
- else if (window.__NEXT_DATA__?.__vinext?.pageModuleUrl) {
65
+ if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") {
66
+ const mountedSlotsHeader = getMountedSlotsHeader();
67
+ const headers = new Headers({ Accept: "text/x-component" });
68
+ if (mountedSlotsHeader) headers.set("X-Vinext-Mounted-Slots", mountedSlotsHeader);
69
+ if (interceptionContext !== null) headers.set("X-Vinext-Interception-Context", interceptionContext);
70
+ prefetchRscResponse(rscUrl, fetch(rscUrl, {
71
+ headers,
72
+ credentials: "include",
73
+ priority: "low",
74
+ purpose: "prefetch"
75
+ }), interceptionContext, mountedSlotsHeader);
76
+ } else if (window.__NEXT_DATA__?.__vinext?.pageModuleUrl) {
69
77
  const link = document.createElement("link");
70
78
  link.rel = "prefetch";
71
79
  link.href = fullHref;
@@ -205,7 +213,7 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
205
213
  if (mountedRef.current) setPending(false);
206
214
  }
207
215
  } else try {
208
- const Router = (await import("next/router")).default;
216
+ const Router = (await import("next/router.js")).default;
209
217
  if (replace) await Router.replace(absoluteHref, void 0, { scroll });
210
218
  else await Router.push(absoluteHref, void 0, { scroll });
211
219
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","names":[],"sources":["../../src/shims/link.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * next/link shim\n *\n * Renders an <a> tag with client-side navigation support.\n * On click, prevents full page reload and triggers client-side\n * page swap via the router's navigation system.\n */\nimport React, {\n forwardRef,\n useRef,\n useEffect,\n useCallback,\n useContext,\n createContext,\n useState,\n type AnchorHTMLAttributes,\n type MouseEvent,\n} from \"react\";\n// Import shared RSC prefetch utilities from navigation shim (relative path\n// so this resolves both via the Vite plugin and in direct vitest imports)\nimport {\n toRscUrl,\n getPrefetchedUrls,\n navigateClientSide,\n prefetchRscResponse,\n} from \"./navigation.js\";\nimport { isDangerousScheme } from \"./url-safety.js\";\nimport {\n resolveRelativeHref,\n toBrowserNavigationHref,\n toSameOriginAppPath,\n withBasePath,\n} from \"./url-utils.js\";\nimport { appendSearchParamsToUrl, type UrlQuery, urlQueryToSearchParams } from \"../utils/query.js\";\nimport { addLocalePrefix, getDomainLocaleUrl, type DomainLocale } from \"../utils/domain-locale.js\";\nimport { getI18nContext } from \"./i18n-context.js\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\n\ntype NavigateEvent = {\n url: URL;\n /** Call to prevent the Link's default navigation (e.g. for View Transitions). */\n preventDefault(): void;\n /** Whether preventDefault() has been called. */\n defaultPrevented: boolean;\n};\n\ntype LinkProps = {\n href: string | { pathname?: string; query?: UrlQuery };\n /** URL displayed in the browser (when href is a route pattern like /user/[id]) */\n as?: string;\n /** Replace the current history entry instead of pushing */\n replace?: boolean;\n /** Prefetch the page in the background (default: true, uses IntersectionObserver) */\n prefetch?: boolean;\n /** Whether to pass the href to the child element */\n passHref?: boolean;\n /** Scroll to top on navigation (default: true) */\n scroll?: boolean;\n /** Locale for i18n (used for locale-prefixed URLs) */\n locale?: string | false;\n /** Called before navigation happens (Next.js 16). Return value is ignored. */\n onNavigate?: (event: NavigateEvent) => void;\n children?: React.ReactNode;\n} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\">;\n\n// ---------------------------------------------------------------------------\n// useLinkStatus — reports the pending state of a parent <Link> navigation\n// ---------------------------------------------------------------------------\n\ntype LinkStatusContextValue = {\n pending: boolean;\n};\n\nconst LinkStatusContext = createContext<LinkStatusContextValue>({ pending: false });\n\n/**\n * useLinkStatus returns the pending state of the enclosing <Link>.\n * In Next.js, this is used to show loading indicators while a\n * prefetch-triggered navigation is in progress.\n */\nexport function useLinkStatus(): LinkStatusContextValue {\n return useContext(LinkStatusContext);\n}\n\n/** basePath from next.config.js, injected by the plugin at build time */\nconst __basePath: string = process.env.__NEXT_ROUTER_BASEPATH ?? \"\";\n\nfunction resolveHref(href: LinkProps[\"href\"]): string {\n if (typeof href === \"string\") return href;\n let url = href.pathname ?? \"/\";\n if (href.query) {\n const params = urlQueryToSearchParams(href.query);\n url = appendSearchParamsToUrl(url, params);\n }\n return url;\n}\n\n// ---------------------------------------------------------------------------\n// Prefetching infrastructure\n// ---------------------------------------------------------------------------\n\n/**\n * Prefetch a URL for faster navigation.\n *\n * For App Router (RSC): fetches the .rsc payload in the background and\n * stores it in an in-memory cache for instant use during navigation.\n * For Pages Router: injects a <link rel=\"prefetch\"> for the page module.\n *\n * Uses `requestIdleCallback` (or `setTimeout` fallback) to avoid blocking\n * the main thread during initial page load.\n */\nfunction prefetchUrl(href: string): void {\n if (typeof window === \"undefined\") return;\n\n // Normalize same-origin absolute URLs to local paths before prefetching\n let prefetchHref = href;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n const localPath = toSameOriginAppPath(href, __basePath);\n if (localPath == null) return; // truly external — don't prefetch\n prefetchHref = localPath;\n }\n\n const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);\n\n // Don't prefetch the same URL twice (keyed by rscUrl so the browser\n // entry can clear the key when a cache entry is consumed)\n const rscUrl = toRscUrl(fullHref);\n const prefetched = getPrefetchedUrls();\n if (prefetched.has(rscUrl)) return;\n prefetched.add(rscUrl);\n\n const schedule = window.requestIdleCallback ?? ((fn: () => void) => setTimeout(fn, 100));\n\n schedule(() => {\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n prefetchRscResponse(\n rscUrl,\n fetch(rscUrl, {\n headers: { Accept: \"text/x-component\" },\n credentials: \"include\",\n priority: \"low\" as const,\n // @ts-expect-error — purpose is a valid fetch option in some browsers\n purpose: \"prefetch\",\n }),\n );\n } else if ((window.__NEXT_DATA__ as VinextNextData | undefined)?.__vinext?.pageModuleUrl) {\n // Pages Router: inject a prefetch link for the target page module\n // We can't easily resolve the target page's module URL from the Link,\n // so we create a <link rel=\"prefetch\"> for the HTML page which helps\n // the browser's preload scanner.\n const link = document.createElement(\"link\");\n link.rel = \"prefetch\";\n link.href = fullHref;\n link.as = \"document\";\n document.head.appendChild(link);\n }\n });\n}\n\n/**\n * Shared IntersectionObserver for viewport-based prefetching.\n * All Link elements use the same observer to minimize resource usage.\n */\nlet sharedObserver: IntersectionObserver | null = null;\nconst observerCallbacks = new WeakMap<Element, () => void>();\n\nfunction getSharedObserver(): IntersectionObserver | null {\n if (typeof window === \"undefined\" || typeof IntersectionObserver === \"undefined\") return null;\n if (sharedObserver) return sharedObserver;\n\n sharedObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const callback = observerCallbacks.get(entry.target);\n if (callback) {\n callback();\n // Unobserve after prefetching — only prefetch once\n sharedObserver?.unobserve(entry.target);\n observerCallbacks.delete(entry.target);\n }\n }\n }\n },\n {\n // Start prefetching when the link is within 250px of the viewport.\n // This gives the browser a head start before the user scrolls to it.\n rootMargin: \"250px\",\n },\n );\n\n return sharedObserver;\n}\n\nfunction getDefaultLocale(): string | undefined {\n if (typeof window !== \"undefined\") {\n return window.__VINEXT_DEFAULT_LOCALE__;\n }\n return getI18nContext()?.defaultLocale;\n}\n\nfunction getDomainLocales(): readonly DomainLocale[] | undefined {\n if (typeof window !== \"undefined\") {\n return (window.__NEXT_DATA__ as VinextNextData | undefined)?.domainLocales;\n }\n return getI18nContext()?.domainLocales;\n}\n\nfunction getCurrentHostname(): string | undefined {\n if (typeof window !== \"undefined\") return window.location.hostname;\n return getI18nContext()?.hostname;\n}\n\nfunction getDomainLocaleHref(href: string, locale: string): string | undefined {\n // Only cross-domain locale switches need a special absolute URL here.\n // Same-domain cases fall back to the standard locale-prefix logic below.\n return getDomainLocaleUrl(href, locale, {\n basePath: __basePath,\n currentHostname: getCurrentHostname(),\n domainItems: getDomainLocales(),\n });\n}\n\n/**\n * Apply locale prefix to a URL path based on the locale prop.\n * - locale=\"fr\" → prepend /fr (unless it already has a locale prefix)\n * - locale={false} → use the href as-is (no locale prefix, link to default)\n * - locale=undefined → use current locale (href as-is in most cases)\n */\nfunction applyLocaleToHref(href: string, locale: string | false | undefined): string {\n if (locale === false) {\n // Explicit false: no locale prefix\n return href;\n }\n\n if (locale === undefined) {\n // No locale prop: keep current behavior (href as-is)\n return href;\n }\n\n // Absolute and protocol-relative URLs must not be prefixed — locale\n // only applies to local paths.\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n return href;\n }\n\n const domainLocaleHref = getDomainLocaleHref(href, locale);\n if (domainLocaleHref) {\n return domainLocaleHref;\n }\n\n return addLocalePrefix(href, locale, getDefaultLocale() ?? \"\");\n}\n\nconst Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n {\n href,\n as,\n replace = false,\n prefetch: prefetchProp,\n scroll = true,\n children,\n onClick,\n onNavigate,\n ...rest\n },\n forwardedRef,\n) {\n // Extract locale from rest props\n const { locale, ...restWithoutLocale } = rest;\n\n // If `as` is provided, use it as the actual URL (legacy Next.js pattern\n // where href is a route pattern like \"/user/[id]\" and as is \"/user/1\")\n const resolvedHref = as ?? resolveHref(href);\n\n const isDangerous = typeof resolvedHref === \"string\" && isDangerousScheme(resolvedHref);\n\n // Apply locale prefix if specified (safe even for dangerous hrefs since we\n // won't use the result when isDangerous is true)\n const localizedHref = applyLocaleToHref(isDangerous ? \"/\" : resolvedHref, locale);\n // Full href with basePath for browser URLs and fetches\n const fullHref = withBasePath(localizedHref, __basePath);\n\n // Track pending state for useLinkStatus()\n const [pending, setPending] = useState(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Prefetching: observe the element when it enters the viewport.\n // prefetch={false} disables, prefetch={true} or undefined/null (default) enables.\n const internalRef = useRef<HTMLAnchorElement | null>(null);\n const shouldPrefetch = prefetchProp !== false && !isDangerous;\n\n const setRefs = useCallback(\n (node: HTMLAnchorElement | null) => {\n internalRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef)\n (forwardedRef as React.MutableRefObject<HTMLAnchorElement | null>).current = node;\n },\n [forwardedRef],\n );\n\n useEffect(() => {\n if (!shouldPrefetch || typeof window === \"undefined\") return;\n const node = internalRef.current;\n if (!node) return;\n\n // Normalize same-origin absolute URLs; skip truly external ones\n let hrefToPrefetch = localizedHref;\n if (\n localizedHref.startsWith(\"http://\") ||\n localizedHref.startsWith(\"https://\") ||\n localizedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(localizedHref, __basePath);\n if (localPath == null) return; // truly external\n hrefToPrefetch = localPath;\n }\n\n const observer = getSharedObserver();\n if (!observer) return;\n\n observerCallbacks.set(node, () => prefetchUrl(hrefToPrefetch));\n observer.observe(node);\n\n return () => {\n observer.unobserve(node);\n observerCallbacks.delete(node);\n };\n }, [shouldPrefetch, localizedHref]);\n\n const handleClick = async (e: MouseEvent<HTMLAnchorElement>) => {\n if (onClick) onClick(e);\n if (e.defaultPrevented) return;\n\n // Only intercept left clicks without modifiers (standard link behavior)\n if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n // Don't intercept links with target (e.g. target=\"_blank\")\n if (e.currentTarget.target && e.currentTarget.target !== \"_self\") {\n return;\n }\n\n // External links: let the browser handle it.\n // Same-origin absolute URLs (e.g. http://localhost:3000/about) are\n // normalized to local paths so they get client-side navigation.\n let navigateHref = localizedHref;\n if (\n resolvedHref.startsWith(\"http://\") ||\n resolvedHref.startsWith(\"https://\") ||\n resolvedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(resolvedHref, __basePath);\n if (localPath == null) return; // truly external\n navigateHref = localPath;\n }\n\n e.preventDefault();\n\n // Resolve relative hrefs (#hash, ?query) against the current URL once so\n // onNavigate and the actual navigation target stay in sync.\n const absoluteHref = resolveRelativeHref(navigateHref, window.location.href, __basePath);\n const absoluteFullHref = toBrowserNavigationHref(\n navigateHref,\n window.location.href,\n __basePath,\n );\n\n // Call onNavigate callback if provided (Next.js 16 View Transitions support)\n if (onNavigate) {\n try {\n const navUrl = new URL(absoluteFullHref, window.location.origin);\n let prevented = false;\n const navEvent: NavigateEvent = {\n url: navUrl,\n preventDefault() {\n prevented = true;\n },\n get defaultPrevented() {\n return prevented;\n },\n };\n onNavigate(navEvent);\n // If the callback called preventDefault(), skip Link's default navigation.\n // The callback is responsible for its own navigation (e.g. via View Transitions API).\n if (navEvent.defaultPrevented) {\n return;\n }\n } catch {\n // Ignore URL parsing errors for relative/hash hrefs\n }\n }\n\n // App Router: delegate to navigateClientSide which handles scroll save,\n // hash-only changes, RSC fetch, and two-phase URL commit.\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n setPending(true);\n try {\n await navigateClientSide(navigateHref, replace ? \"replace\" : \"push\", scroll);\n } finally {\n if (mountedRef.current) setPending(false);\n }\n } else {\n // Next.js only consumes onRouterTransitionStart in the App Router.\n // Pages Router still executes instrumentation-client side effects\n // during startup, but it does not invoke the named export on navigation.\n // Pages Router: use the Router singleton\n try {\n const routerModule = await import(\"next/router\");\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- vinext's Router shim accepts (url, as, options)\n const Router = routerModule.default as any;\n if (replace) {\n await Router.replace(absoluteHref, undefined, { scroll });\n } else {\n await Router.push(absoluteHref, undefined, { scroll });\n }\n } catch {\n // Fallback to hard navigation if router fails\n if (replace) {\n window.history.replaceState({}, \"\", absoluteFullHref);\n } else {\n window.history.pushState({}, \"\", absoluteFullHref);\n }\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }\n }\n };\n\n // Remove props that shouldn't be on <a>\n const { passHref: _p, ...anchorProps } = restWithoutLocale;\n\n const linkStatusValue = React.useMemo(() => ({ pending }), [pending]);\n\n // Block dangerous URI schemes (javascript:, data:, vbscript:).\n // Render an inert <a> without href to prevent XSS while preserving\n // styling and attributes like className, id, aria-*.\n // This check is placed after all hooks to satisfy the Rules of Hooks.\n if (isDangerous) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(`<Link> blocked dangerous href: ${resolvedHref}`);\n }\n return <a {...anchorProps}>{children}</a>;\n }\n\n return (\n <LinkStatusContext.Provider value={linkStatusValue}>\n <a ref={setRefs} href={fullHref} onClick={handleClick} {...anchorProps}>\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n});\n\nexport default Link;\n"],"mappings":"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"}
1
+ {"version":3,"file":"link.js","names":[],"sources":["../../src/shims/link.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * next/link shim\n *\n * Renders an <a> tag with client-side navigation support.\n * On click, prevents full page reload and triggers client-side\n * page swap via the router's navigation system.\n */\nimport React, {\n forwardRef,\n useRef,\n useEffect,\n useCallback,\n useContext,\n createContext,\n useState,\n type AnchorHTMLAttributes,\n type MouseEvent,\n} from \"react\";\n// Import shared RSC prefetch utilities from navigation shim (relative path\n// so this resolves both via the Vite plugin and in direct vitest imports)\nimport {\n getCurrentInterceptionContext,\n toRscUrl,\n getPrefetchedUrls,\n getMountedSlotsHeader,\n navigateClientSide,\n prefetchRscResponse,\n} from \"./navigation.js\";\nimport { createAppPayloadCacheKey } from \"../server/app-elements.js\";\nimport { isDangerousScheme } from \"./url-safety.js\";\nimport {\n resolveRelativeHref,\n toBrowserNavigationHref,\n toSameOriginAppPath,\n withBasePath,\n} from \"./url-utils.js\";\nimport { appendSearchParamsToUrl, type UrlQuery, urlQueryToSearchParams } from \"../utils/query.js\";\nimport { addLocalePrefix, getDomainLocaleUrl, type DomainLocale } from \"../utils/domain-locale.js\";\nimport { getI18nContext } from \"./i18n-context.js\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\n\ntype NavigateEvent = {\n url: URL;\n /** Call to prevent the Link's default navigation (e.g. for View Transitions). */\n preventDefault(): void;\n /** Whether preventDefault() has been called. */\n defaultPrevented: boolean;\n};\n\ntype LinkProps = {\n href: string | { pathname?: string; query?: UrlQuery };\n /** URL displayed in the browser (when href is a route pattern like /user/[id]) */\n as?: string;\n /** Replace the current history entry instead of pushing */\n replace?: boolean;\n /** Prefetch the page in the background (default: true, uses IntersectionObserver) */\n prefetch?: boolean;\n /** Whether to pass the href to the child element */\n passHref?: boolean;\n /** Scroll to top on navigation (default: true) */\n scroll?: boolean;\n /** Locale for i18n (used for locale-prefixed URLs) */\n locale?: string | false;\n /** Called before navigation happens (Next.js 16). Return value is ignored. */\n onNavigate?: (event: NavigateEvent) => void;\n children?: React.ReactNode;\n} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\">;\n\n// ---------------------------------------------------------------------------\n// useLinkStatus — reports the pending state of a parent <Link> navigation\n// ---------------------------------------------------------------------------\n\ntype LinkStatusContextValue = {\n pending: boolean;\n};\n\nconst LinkStatusContext = createContext<LinkStatusContextValue>({ pending: false });\n\n/**\n * useLinkStatus returns the pending state of the enclosing <Link>.\n * In Next.js, this is used to show loading indicators while a\n * prefetch-triggered navigation is in progress.\n */\nexport function useLinkStatus(): LinkStatusContextValue {\n return useContext(LinkStatusContext);\n}\n\n/** basePath from next.config.js, injected by the plugin at build time */\nconst __basePath: string = process.env.__NEXT_ROUTER_BASEPATH ?? \"\";\n\nfunction resolveHref(href: LinkProps[\"href\"]): string {\n if (typeof href === \"string\") return href;\n let url = href.pathname ?? \"/\";\n if (href.query) {\n const params = urlQueryToSearchParams(href.query);\n url = appendSearchParamsToUrl(url, params);\n }\n return url;\n}\n\n// ---------------------------------------------------------------------------\n// Prefetching infrastructure\n// ---------------------------------------------------------------------------\n\n/**\n * Prefetch a URL for faster navigation.\n *\n * For App Router (RSC): fetches the .rsc payload in the background and\n * stores it in an in-memory cache for instant use during navigation.\n * For Pages Router: injects a <link rel=\"prefetch\"> for the page module.\n *\n * Uses `requestIdleCallback` (or `setTimeout` fallback) to avoid blocking\n * the main thread during initial page load.\n */\nfunction prefetchUrl(href: string): void {\n if (typeof window === \"undefined\") return;\n\n // Normalize same-origin absolute URLs to local paths before prefetching\n let prefetchHref = href;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n const localPath = toSameOriginAppPath(href, __basePath);\n if (localPath == null) return; // truly external — don't prefetch\n prefetchHref = localPath;\n }\n\n const fullHref = toBrowserNavigationHref(prefetchHref, window.location.href, __basePath);\n\n // Distinguish the same visible URL when it is prefetched from different\n // interception sources such as /feed vs /gallery.\n const rscUrl = toRscUrl(fullHref);\n const interceptionContext = getCurrentInterceptionContext();\n const cacheKey = createAppPayloadCacheKey(rscUrl, interceptionContext);\n const prefetched = getPrefetchedUrls();\n if (prefetched.has(cacheKey)) return;\n prefetched.add(cacheKey);\n\n const schedule = window.requestIdleCallback ?? ((fn: () => void) => setTimeout(fn, 100));\n\n schedule(() => {\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n const mountedSlotsHeader = getMountedSlotsHeader();\n const headers = new Headers({ Accept: \"text/x-component\" });\n if (mountedSlotsHeader) {\n headers.set(\"X-Vinext-Mounted-Slots\", mountedSlotsHeader);\n }\n if (interceptionContext !== null) {\n headers.set(\"X-Vinext-Interception-Context\", interceptionContext);\n }\n prefetchRscResponse(\n rscUrl,\n fetch(rscUrl, {\n headers,\n credentials: \"include\",\n priority: \"low\" as const,\n // @ts-expect-error — purpose is a valid fetch option in some browsers\n purpose: \"prefetch\",\n }),\n interceptionContext,\n mountedSlotsHeader,\n );\n } else if ((window.__NEXT_DATA__ as VinextNextData | undefined)?.__vinext?.pageModuleUrl) {\n // Pages Router: inject a prefetch link for the target page module\n // We can't easily resolve the target page's module URL from the Link,\n // so we create a <link rel=\"prefetch\"> for the HTML page which helps\n // the browser's preload scanner.\n const link = document.createElement(\"link\");\n link.rel = \"prefetch\";\n link.href = fullHref;\n link.as = \"document\";\n document.head.appendChild(link);\n }\n });\n}\n\n/**\n * Shared IntersectionObserver for viewport-based prefetching.\n * All Link elements use the same observer to minimize resource usage.\n */\nlet sharedObserver: IntersectionObserver | null = null;\nconst observerCallbacks = new WeakMap<Element, () => void>();\n\nfunction getSharedObserver(): IntersectionObserver | null {\n if (typeof window === \"undefined\" || typeof IntersectionObserver === \"undefined\") return null;\n if (sharedObserver) return sharedObserver;\n\n sharedObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const callback = observerCallbacks.get(entry.target);\n if (callback) {\n callback();\n // Unobserve after prefetching — only prefetch once\n sharedObserver?.unobserve(entry.target);\n observerCallbacks.delete(entry.target);\n }\n }\n }\n },\n {\n // Start prefetching when the link is within 250px of the viewport.\n // This gives the browser a head start before the user scrolls to it.\n rootMargin: \"250px\",\n },\n );\n\n return sharedObserver;\n}\n\nfunction getDefaultLocale(): string | undefined {\n if (typeof window !== \"undefined\") {\n return window.__VINEXT_DEFAULT_LOCALE__;\n }\n return getI18nContext()?.defaultLocale;\n}\n\nfunction getDomainLocales(): readonly DomainLocale[] | undefined {\n if (typeof window !== \"undefined\") {\n return (window.__NEXT_DATA__ as VinextNextData | undefined)?.domainLocales;\n }\n return getI18nContext()?.domainLocales;\n}\n\nfunction getCurrentHostname(): string | undefined {\n if (typeof window !== \"undefined\") return window.location.hostname;\n return getI18nContext()?.hostname;\n}\n\nfunction getDomainLocaleHref(href: string, locale: string): string | undefined {\n // Only cross-domain locale switches need a special absolute URL here.\n // Same-domain cases fall back to the standard locale-prefix logic below.\n return getDomainLocaleUrl(href, locale, {\n basePath: __basePath,\n currentHostname: getCurrentHostname(),\n domainItems: getDomainLocales(),\n });\n}\n\n/**\n * Apply locale prefix to a URL path based on the locale prop.\n * - locale=\"fr\" → prepend /fr (unless it already has a locale prefix)\n * - locale={false} → use the href as-is (no locale prefix, link to default)\n * - locale=undefined → use current locale (href as-is in most cases)\n */\nfunction applyLocaleToHref(href: string, locale: string | false | undefined): string {\n if (locale === false) {\n // Explicit false: no locale prefix\n return href;\n }\n\n if (locale === undefined) {\n // No locale prop: keep current behavior (href as-is)\n return href;\n }\n\n // Absolute and protocol-relative URLs must not be prefixed — locale\n // only applies to local paths.\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\")) {\n return href;\n }\n\n const domainLocaleHref = getDomainLocaleHref(href, locale);\n if (domainLocaleHref) {\n return domainLocaleHref;\n }\n\n return addLocalePrefix(href, locale, getDefaultLocale() ?? \"\");\n}\n\nconst Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n {\n href,\n as,\n replace = false,\n prefetch: prefetchProp,\n scroll = true,\n children,\n onClick,\n onNavigate,\n ...rest\n },\n forwardedRef,\n) {\n // Extract locale from rest props\n const { locale, ...restWithoutLocale } = rest;\n\n // If `as` is provided, use it as the actual URL (legacy Next.js pattern\n // where href is a route pattern like \"/user/[id]\" and as is \"/user/1\")\n const resolvedHref = as ?? resolveHref(href);\n\n const isDangerous = typeof resolvedHref === \"string\" && isDangerousScheme(resolvedHref);\n\n // Apply locale prefix if specified (safe even for dangerous hrefs since we\n // won't use the result when isDangerous is true)\n const localizedHref = applyLocaleToHref(isDangerous ? \"/\" : resolvedHref, locale);\n // Full href with basePath for browser URLs and fetches\n const fullHref = withBasePath(localizedHref, __basePath);\n\n // Track pending state for useLinkStatus()\n const [pending, setPending] = useState(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Prefetching: observe the element when it enters the viewport.\n // prefetch={false} disables, prefetch={true} or undefined/null (default) enables.\n const internalRef = useRef<HTMLAnchorElement | null>(null);\n const shouldPrefetch = prefetchProp !== false && !isDangerous;\n\n const setRefs = useCallback(\n (node: HTMLAnchorElement | null) => {\n internalRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef)\n (forwardedRef as React.MutableRefObject<HTMLAnchorElement | null>).current = node;\n },\n [forwardedRef],\n );\n\n useEffect(() => {\n if (!shouldPrefetch || typeof window === \"undefined\") return;\n const node = internalRef.current;\n if (!node) return;\n\n // Normalize same-origin absolute URLs; skip truly external ones\n let hrefToPrefetch = localizedHref;\n if (\n localizedHref.startsWith(\"http://\") ||\n localizedHref.startsWith(\"https://\") ||\n localizedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(localizedHref, __basePath);\n if (localPath == null) return; // truly external\n hrefToPrefetch = localPath;\n }\n\n const observer = getSharedObserver();\n if (!observer) return;\n\n observerCallbacks.set(node, () => prefetchUrl(hrefToPrefetch));\n observer.observe(node);\n\n return () => {\n observer.unobserve(node);\n observerCallbacks.delete(node);\n };\n }, [shouldPrefetch, localizedHref]);\n\n const handleClick = async (e: MouseEvent<HTMLAnchorElement>) => {\n if (onClick) onClick(e);\n if (e.defaultPrevented) return;\n\n // Only intercept left clicks without modifiers (standard link behavior)\n if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n // Don't intercept links with target (e.g. target=\"_blank\")\n if (e.currentTarget.target && e.currentTarget.target !== \"_self\") {\n return;\n }\n\n // External links: let the browser handle it.\n // Same-origin absolute URLs (e.g. http://localhost:3000/about) are\n // normalized to local paths so they get client-side navigation.\n let navigateHref = localizedHref;\n if (\n resolvedHref.startsWith(\"http://\") ||\n resolvedHref.startsWith(\"https://\") ||\n resolvedHref.startsWith(\"//\")\n ) {\n const localPath = toSameOriginAppPath(resolvedHref, __basePath);\n if (localPath == null) return; // truly external\n navigateHref = localPath;\n }\n\n e.preventDefault();\n\n // Resolve relative hrefs (#hash, ?query) against the current URL once so\n // onNavigate and the actual navigation target stay in sync.\n const absoluteHref = resolveRelativeHref(navigateHref, window.location.href, __basePath);\n const absoluteFullHref = toBrowserNavigationHref(\n navigateHref,\n window.location.href,\n __basePath,\n );\n\n // Call onNavigate callback if provided (Next.js 16 View Transitions support)\n if (onNavigate) {\n try {\n const navUrl = new URL(absoluteFullHref, window.location.origin);\n let prevented = false;\n const navEvent: NavigateEvent = {\n url: navUrl,\n preventDefault() {\n prevented = true;\n },\n get defaultPrevented() {\n return prevented;\n },\n };\n onNavigate(navEvent);\n // If the callback called preventDefault(), skip Link's default navigation.\n // The callback is responsible for its own navigation (e.g. via View Transitions API).\n if (navEvent.defaultPrevented) {\n return;\n }\n } catch {\n // Ignore URL parsing errors for relative/hash hrefs\n }\n }\n\n // App Router: delegate to navigateClientSide which handles scroll save,\n // hash-only changes, RSC fetch, and two-phase URL commit.\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n setPending(true);\n try {\n await navigateClientSide(navigateHref, replace ? \"replace\" : \"push\", scroll);\n } finally {\n if (mountedRef.current) setPending(false);\n }\n } else {\n // Next.js only consumes onRouterTransitionStart in the App Router.\n // Pages Router still executes instrumentation-client side effects\n // during startup, but it does not invoke the named export on navigation.\n // Pages Router: use the Router singleton\n try {\n const routerModule = await import(\"next/router\");\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- vinext's Router shim accepts (url, as, options)\n const Router = routerModule.default as any;\n if (replace) {\n await Router.replace(absoluteHref, undefined, { scroll });\n } else {\n await Router.push(absoluteHref, undefined, { scroll });\n }\n } catch {\n // Fallback to hard navigation if router fails\n if (replace) {\n window.history.replaceState({}, \"\", absoluteFullHref);\n } else {\n window.history.pushState({}, \"\", absoluteFullHref);\n }\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }\n }\n };\n\n // Remove props that shouldn't be on <a>\n const { passHref: _p, ...anchorProps } = restWithoutLocale;\n\n const linkStatusValue = React.useMemo(() => ({ pending }), [pending]);\n\n // Block dangerous URI schemes (javascript:, data:, vbscript:).\n // Render an inert <a> without href to prevent XSS while preserving\n // styling and attributes like className, id, aria-*.\n // This check is placed after all hooks to satisfy the Rules of Hooks.\n if (isDangerous) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(`<Link> blocked dangerous href: ${resolvedHref}`);\n }\n return <a {...anchorProps}>{children}</a>;\n }\n\n return (\n <LinkStatusContext.Provider value={linkStatusValue}>\n <a ref={setRefs} href={fullHref} onClick={handleClick} {...anchorProps}>\n {children}\n </a>\n </LinkStatusContext.Provider>\n );\n});\n\nexport default Link;\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
1
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/shims/metadata.d.ts
4
4
  type Viewport = {
@@ -32,7 +32,7 @@ declare function ViewportHead({
32
32
  viewport
33
33
  }: {
34
34
  viewport: Viewport;
35
- }): react_jsx_runtime0.JSX.Element;
35
+ }): _$react_jsx_runtime0.JSX.Element;
36
36
  type Metadata = {
37
37
  title?: string | {
38
38
  default?: string;
@@ -251,7 +251,7 @@ declare function MetadataHead({
251
251
  metadata
252
252
  }: {
253
253
  metadata: Metadata;
254
- }): react_jsx_runtime0.JSX.Element;
254
+ }): _$react_jsx_runtime0.JSX.Element;
255
255
  //#endregion
256
256
  export { DEFAULT_VIEWPORT, Metadata, MetadataHead, Viewport, ViewportHead, mergeMetadata, mergeViewport, resolveModuleMetadata, resolveModuleViewport };
257
257
  //# sourceMappingURL=metadata.d.ts.map
@@ -10,6 +10,7 @@ type NavigationState = {
10
10
  * Ensures per-request isolation for navigation context and
11
11
  * useServerInsertedHTML callbacks on concurrent runtimes.
12
12
  */
13
+ declare function runWithNavigationContext<T>(fn: () => Promise<T>): Promise<T>;
13
14
  declare function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Promise<T>;
14
15
  /**
15
16
  * Run a function with a fresh useServerInsertedHTML callback list while
@@ -19,6 +20,7 @@ declare function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Prom
19
20
  * but it needs a fresh callback collection so CSS-in-JS insertions from the
20
21
  * streamed render cannot accumulate into the cache-fill render.
21
22
  */
23
+ declare function runWithServerInsertedHTMLState<T>(fn: () => Promise<T>): Promise<T>;
22
24
  declare function runWithServerInsertedHTMLState<T>(fn: () => T | Promise<T>): T | Promise<T>;
23
25
  //#endregion
24
26
  export { NavigationState, runWithNavigationContext, runWithServerInsertedHTMLState };
@@ -26,11 +26,6 @@ function _getState() {
26
26
  if (isInsideUnifiedScope()) return getRequestContext();
27
27
  return _als.getStore() ?? _fallbackState;
28
28
  }
29
- /**
30
- * Run a function within a navigation ALS scope.
31
- * Ensures per-request isolation for navigation context and
32
- * useServerInsertedHTML callbacks on concurrent runtimes.
33
- */
34
29
  function runWithNavigationContext(fn) {
35
30
  if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((uCtx) => {
36
31
  uCtx.serverContext = null;
@@ -41,14 +36,6 @@ function runWithNavigationContext(fn) {
41
36
  serverInsertedHTMLCallbacks: []
42
37
  }, fn);
43
38
  }
44
- /**
45
- * Run a function with a fresh useServerInsertedHTML callback list while
46
- * preserving the current navigation context.
47
- *
48
- * Used by the Pages Router ISR cache-fill pass: it is the same request/path,
49
- * but it needs a fresh callback collection so CSS-in-JS insertions from the
50
- * streamed render cannot accumulate into the cache-fill render.
51
- */
52
39
  function runWithServerInsertedHTMLState(fn) {
53
40
  if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((uCtx) => {
54
41
  uCtx.serverInsertedHTMLCallbacks = [];
@@ -1 +1 @@
1
- {"version":3,"file":"navigation-state.js","names":[],"sources":["../../src/shims/navigation-state.ts"],"sourcesContent":["/**\n * Server-only navigation state backed by AsyncLocalStorage.\n *\n * This module provides request-scoped isolation for navigation context\n * and useServerInsertedHTML callbacks. Without ALS, concurrent requests\n * on Cloudflare Workers would share module-level state and leak data\n * (pathnames, params, CSS-in-JS styles) between requests.\n *\n * This module is server-only — it imports node:async_hooks and must NOT\n * be bundled for the browser. The dual-environment navigation.ts shim\n * uses a registration pattern so it works in both environments.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n _registerStateAccessors,\n type NavigationContext,\n GLOBAL_ACCESSORS_KEY,\n} from \"./navigation.js\";\nimport {\n isInsideUnifiedScope,\n getRequestContext,\n runWithUnifiedStateMutation,\n} from \"./unified-request-context.js\";\n\n// ---------------------------------------------------------------------------\n// ALS setup — same pattern as headers.ts\n// ---------------------------------------------------------------------------\n\nexport type NavigationState = {\n serverContext: NavigationContext | null;\n serverInsertedHTMLCallbacks: Array<() => unknown>;\n};\n\nconst _ALS_KEY = Symbol.for(\"vinext.navigation.als\");\nconst _FALLBACK_KEY = Symbol.for(\"vinext.navigation.fallback\");\nconst _g = globalThis as unknown as Record<PropertyKey, unknown>;\nconst _als = (_g[_ALS_KEY] ??=\n new AsyncLocalStorage<NavigationState>()) as AsyncLocalStorage<NavigationState>;\n\nconst _fallbackState = (_g[_FALLBACK_KEY] ??= {\n serverContext: null,\n serverInsertedHTMLCallbacks: [],\n} satisfies NavigationState) as NavigationState;\n\nfunction _getState(): NavigationState {\n if (isInsideUnifiedScope()) {\n return getRequestContext();\n }\n return _als.getStore() ?? _fallbackState;\n}\n\n/**\n * Run a function within a navigation ALS scope.\n * Ensures per-request isolation for navigation context and\n * useServerInsertedHTML callbacks on concurrent runtimes.\n */\nexport function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Promise<T> {\n if (isInsideUnifiedScope()) {\n return runWithUnifiedStateMutation((uCtx) => {\n uCtx.serverContext = null;\n uCtx.serverInsertedHTMLCallbacks = [];\n }, fn);\n }\n const state: NavigationState = {\n serverContext: null,\n serverInsertedHTMLCallbacks: [],\n };\n return _als.run(state, fn);\n}\n\n/**\n * Run a function with a fresh useServerInsertedHTML callback list while\n * preserving the current navigation context.\n *\n * Used by the Pages Router ISR cache-fill pass: it is the same request/path,\n * but it needs a fresh callback collection so CSS-in-JS insertions from the\n * streamed render cannot accumulate into the cache-fill render.\n */\nexport function runWithServerInsertedHTMLState<T>(fn: () => T | Promise<T>): T | Promise<T> {\n if (isInsideUnifiedScope()) {\n return runWithUnifiedStateMutation((uCtx) => {\n uCtx.serverInsertedHTMLCallbacks = [];\n }, fn);\n }\n\n const parentState = _als.getStore() ?? _fallbackState;\n const state: NavigationState = {\n serverContext: parentState.serverContext,\n serverInsertedHTMLCallbacks: [],\n };\n return _als.run(state, fn);\n}\n\n// ---------------------------------------------------------------------------\n// Register ALS-backed accessors into navigation.ts\n//\n// Two registration paths (issue #688):\n// 1. _registerStateAccessors — updates the module-level function pointers\n// in the same module instance that imported us (the SSR entry's copy).\n// 2. globalThis[Symbol.for(...)] — makes the accessors discoverable by ANY\n// module instance of navigation.ts, even if Vite created a separate one\n// for \"use client\" components due to pre-bundling or env separation.\n// ---------------------------------------------------------------------------\n\nconst _accessors = {\n getServerContext(): NavigationContext | null {\n return _getState().serverContext;\n },\n\n setServerContext(ctx: NavigationContext | null): void {\n _getState().serverContext = ctx;\n },\n\n getInsertedHTMLCallbacks(): Array<() => unknown> {\n return _getState().serverInsertedHTMLCallbacks;\n },\n\n clearInsertedHTMLCallbacks(): void {\n _getState().serverInsertedHTMLCallbacks = [];\n },\n} satisfies Parameters<typeof _registerStateAccessors>[0];\n\n_registerStateAccessors(_accessors);\n(globalThis as unknown as Record<PropertyKey, unknown>)[GLOBAL_ACCESSORS_KEY] = _accessors;\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,MAAM,WAAW,OAAO,IAAI,wBAAwB;AACpD,MAAM,gBAAgB,OAAO,IAAI,6BAA6B;AAC9D,MAAM,KAAK;AACX,MAAM,OAAQ,GAAG,cACf,IAAI,mBAAoC;AAE1C,MAAM,iBAAkB,GAAG,mBAAmB;CAC5C,eAAe;CACf,6BAA6B,EAAE;CAChC;AAED,SAAS,YAA6B;AACpC,KAAI,sBAAsB,CACxB,QAAO,mBAAmB;AAE5B,QAAO,KAAK,UAAU,IAAI;;;;;;;AAQ5B,SAAgB,yBAA4B,IAA0C;AACpF,KAAI,sBAAsB,CACxB,QAAO,6BAA6B,SAAS;AAC3C,OAAK,gBAAgB;AACrB,OAAK,8BAA8B,EAAE;IACpC,GAAG;AAMR,QAAO,KAAK,IAJmB;EAC7B,eAAe;EACf,6BAA6B,EAAE;EAChC,EACsB,GAAG;;;;;;;;;;AAW5B,SAAgB,+BAAkC,IAA0C;AAC1F,KAAI,sBAAsB,CACxB,QAAO,6BAA6B,SAAS;AAC3C,OAAK,8BAA8B,EAAE;IACpC,GAAG;CAIR,MAAM,QAAyB;EAC7B,gBAFkB,KAAK,UAAU,IAAI,gBAEV;EAC3B,6BAA6B,EAAE;EAChC;AACD,QAAO,KAAK,IAAI,OAAO,GAAG;;AAc5B,MAAM,aAAa;CACjB,mBAA6C;AAC3C,SAAO,WAAW,CAAC;;CAGrB,iBAAiB,KAAqC;AACpD,aAAW,CAAC,gBAAgB;;CAG9B,2BAAiD;AAC/C,SAAO,WAAW,CAAC;;CAGrB,6BAAmC;AACjC,aAAW,CAAC,8BAA8B,EAAE;;CAE/C;AAED,wBAAwB,WAAW;AACnC,WAAwD,wBAAwB"}
1
+ {"version":3,"file":"navigation-state.js","names":[],"sources":["../../src/shims/navigation-state.ts"],"sourcesContent":["/**\n * Server-only navigation state backed by AsyncLocalStorage.\n *\n * This module provides request-scoped isolation for navigation context\n * and useServerInsertedHTML callbacks. Without ALS, concurrent requests\n * on Cloudflare Workers would share module-level state and leak data\n * (pathnames, params, CSS-in-JS styles) between requests.\n *\n * This module is server-only — it imports node:async_hooks and must NOT\n * be bundled for the browser. The dual-environment navigation.ts shim\n * uses a registration pattern so it works in both environments.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport {\n _registerStateAccessors,\n type NavigationContext,\n GLOBAL_ACCESSORS_KEY,\n} from \"./navigation.js\";\nimport {\n isInsideUnifiedScope,\n getRequestContext,\n runWithUnifiedStateMutation,\n} from \"./unified-request-context.js\";\n\n// ---------------------------------------------------------------------------\n// ALS setup — same pattern as headers.ts\n// ---------------------------------------------------------------------------\n\nexport type NavigationState = {\n serverContext: NavigationContext | null;\n serverInsertedHTMLCallbacks: Array<() => unknown>;\n};\n\nconst _ALS_KEY = Symbol.for(\"vinext.navigation.als\");\nconst _FALLBACK_KEY = Symbol.for(\"vinext.navigation.fallback\");\nconst _g = globalThis as unknown as Record<PropertyKey, unknown>;\nconst _als = (_g[_ALS_KEY] ??=\n new AsyncLocalStorage<NavigationState>()) as AsyncLocalStorage<NavigationState>;\n\nconst _fallbackState = (_g[_FALLBACK_KEY] ??= {\n serverContext: null,\n serverInsertedHTMLCallbacks: [],\n} satisfies NavigationState) as NavigationState;\n\nfunction _getState(): NavigationState {\n if (isInsideUnifiedScope()) {\n return getRequestContext();\n }\n return _als.getStore() ?? _fallbackState;\n}\n\n/**\n * Run a function within a navigation ALS scope.\n * Ensures per-request isolation for navigation context and\n * useServerInsertedHTML callbacks on concurrent runtimes.\n */\nexport function runWithNavigationContext<T>(fn: () => Promise<T>): Promise<T>;\nexport function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Promise<T>;\nexport function runWithNavigationContext<T>(fn: () => T | Promise<T>): T | Promise<T> {\n if (isInsideUnifiedScope()) {\n return runWithUnifiedStateMutation((uCtx) => {\n uCtx.serverContext = null;\n uCtx.serverInsertedHTMLCallbacks = [];\n }, fn);\n }\n const state: NavigationState = {\n serverContext: null,\n serverInsertedHTMLCallbacks: [],\n };\n return _als.run(state, fn);\n}\n\n/**\n * Run a function with a fresh useServerInsertedHTML callback list while\n * preserving the current navigation context.\n *\n * Used by the Pages Router ISR cache-fill pass: it is the same request/path,\n * but it needs a fresh callback collection so CSS-in-JS insertions from the\n * streamed render cannot accumulate into the cache-fill render.\n */\nexport function runWithServerInsertedHTMLState<T>(fn: () => Promise<T>): Promise<T>;\nexport function runWithServerInsertedHTMLState<T>(fn: () => T | Promise<T>): T | Promise<T>;\nexport function runWithServerInsertedHTMLState<T>(fn: () => T | Promise<T>): T | Promise<T> {\n if (isInsideUnifiedScope()) {\n return runWithUnifiedStateMutation((uCtx) => {\n uCtx.serverInsertedHTMLCallbacks = [];\n }, fn);\n }\n\n const parentState = _als.getStore() ?? _fallbackState;\n const state: NavigationState = {\n serverContext: parentState.serverContext,\n serverInsertedHTMLCallbacks: [],\n };\n return _als.run(state, fn);\n}\n\n// ---------------------------------------------------------------------------\n// Register ALS-backed accessors into navigation.ts\n//\n// Two registration paths (issue #688):\n// 1. _registerStateAccessors — updates the module-level function pointers\n// in the same module instance that imported us (the SSR entry's copy).\n// 2. globalThis[Symbol.for(...)] — makes the accessors discoverable by ANY\n// module instance of navigation.ts, even if Vite created a separate one\n// for \"use client\" components due to pre-bundling or env separation.\n// ---------------------------------------------------------------------------\n\nconst _accessors = {\n getServerContext(): NavigationContext | null {\n return _getState().serverContext;\n },\n\n setServerContext(ctx: NavigationContext | null): void {\n _getState().serverContext = ctx;\n },\n\n getInsertedHTMLCallbacks(): Array<() => unknown> {\n return _getState().serverInsertedHTMLCallbacks;\n },\n\n clearInsertedHTMLCallbacks(): void {\n _getState().serverInsertedHTMLCallbacks = [];\n },\n} satisfies Parameters<typeof _registerStateAccessors>[0];\n\n_registerStateAccessors(_accessors);\n(globalThis as unknown as Record<PropertyKey, unknown>)[GLOBAL_ACCESSORS_KEY] = _accessors;\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,MAAM,WAAW,OAAO,IAAI,wBAAwB;AACpD,MAAM,gBAAgB,OAAO,IAAI,6BAA6B;AAC9D,MAAM,KAAK;AACX,MAAM,OAAQ,GAAG,cACf,IAAI,mBAAoC;AAE1C,MAAM,iBAAkB,GAAG,mBAAmB;CAC5C,eAAe;CACf,6BAA6B,EAAE;CAChC;AAED,SAAS,YAA6B;AACpC,KAAI,sBAAsB,CACxB,QAAO,mBAAmB;AAE5B,QAAO,KAAK,UAAU,IAAI;;AAU5B,SAAgB,yBAA4B,IAA0C;AACpF,KAAI,sBAAsB,CACxB,QAAO,6BAA6B,SAAS;AAC3C,OAAK,gBAAgB;AACrB,OAAK,8BAA8B,EAAE;IACpC,GAAG;AAMR,QAAO,KAAK,IAJmB;EAC7B,eAAe;EACf,6BAA6B,EAAE;EAChC,EACsB,GAAG;;AAa5B,SAAgB,+BAAkC,IAA0C;AAC1F,KAAI,sBAAsB,CACxB,QAAO,6BAA6B,SAAS;AAC3C,OAAK,8BAA8B,EAAE;IACpC,GAAG;CAIR,MAAM,QAAyB;EAC7B,gBAFkB,KAAK,UAAU,IAAI,gBAEV;EAC3B,6BAA6B,EAAE;EAChC;AACD,QAAO,KAAK,IAAI,OAAO,GAAG;;AAc5B,MAAM,aAAa;CACjB,mBAA6C;AAC3C,SAAO,WAAW,CAAC;;CAGrB,iBAAiB,KAAqC;AACpD,aAAW,CAAC,gBAAgB;;CAG9B,2BAAiD;AAC/C,SAAO,WAAW,CAAC;;CAGrB,6BAAmC;AACjC,aAAW,CAAC,8BAA8B,EAAE;;CAE/C;AAED,wBAAwB,WAAW;AACnC,WAAwD,wBAAwB"}
@@ -57,6 +57,7 @@ declare const PREFETCH_CACHE_TTL = 30000;
57
57
  type CachedRscResponse = {
58
58
  buffer: ArrayBuffer;
59
59
  contentType: string;
60
+ mountedSlotsHeader?: string | null;
60
61
  paramsHeader: string | null;
61
62
  url: string;
62
63
  };
@@ -71,11 +72,13 @@ type PrefetchCacheEntry = {
71
72
  * are consistent regardless of the `trailingSlash` config setting.
72
73
  */
73
74
  declare function toRscUrl(href: string): string;
75
+ declare function getCurrentInterceptionContext(): string | null;
76
+ declare function getCurrentNextUrl(): string;
74
77
  /** Get or create the shared in-memory RSC prefetch cache on window. */
75
78
  declare function getPrefetchCache(): Map<string, PrefetchCacheEntry>;
76
79
  /**
77
80
  * Get or create the shared set of already-prefetched RSC URLs on window.
78
- * Keyed by rscUrl so that the browser entry can clear entries when consumed.
81
+ * Keyed by interception-aware cache key so distinct source routes do not alias.
79
82
  */
80
83
  declare function getPrefetchedUrls(): Set<string>;
81
84
  /**
@@ -85,13 +88,16 @@ declare function getPrefetchedUrls(): Set<string>;
85
88
  * (the caller falls back to a fresh fetch, which is acceptable).
86
89
  *
87
90
  * Prefer prefetchRscResponse() for new call-sites — it handles the full
88
- * prefetch lifecycle including dedup. storePrefetchResponse() is kept for
89
- * backward compatibility and test helpers.
91
+ * prefetch lifecycle including dedup and explicit slot context.
92
+ * storePrefetchResponse() is kept for backward compatibility and test
93
+ * helpers. It is slot-unaware: the snapshot's mountedSlotsHeader comes
94
+ * from the response headers, not the caller, so consumePrefetchResponse
95
+ * may reject the entry if the caller's slot context differs.
90
96
  *
91
97
  * NB: Caller is responsible for managing getPrefetchedUrls() — this
92
98
  * function only stores the response in the prefetch cache.
93
99
  */
94
- declare function storePrefetchResponse(rscUrl: string, response: Response): void;
100
+ declare function storePrefetchResponse(rscUrl: string, response: Response, interceptionContext?: string | null): void;
95
101
  /**
96
102
  * Snapshot an RSC response to an ArrayBuffer for caching and replay.
97
103
  * Consumes the response body and stores it with content-type and URL metadata.
@@ -120,13 +126,35 @@ declare function restoreRscResponse(cached: CachedRscResponse, copy?: boolean):
120
126
  * Enforces a maximum cache size to prevent unbounded memory growth on
121
127
  * link-heavy pages.
122
128
  */
123
- declare function prefetchRscResponse(rscUrl: string, fetchPromise: Promise<Response>): void;
129
+ declare function prefetchRscResponse(rscUrl: string, fetchPromise: Promise<Response>, interceptionContext?: string | null, mountedSlotsHeader?: string | null): void;
124
130
  /**
125
131
  * Consume a prefetched response for a given rscUrl.
126
132
  * Only returns settled (non-pending) snapshots synchronously.
127
133
  * Returns null if the entry is still in flight or doesn't exist.
128
134
  */
129
- declare function consumePrefetchResponse(rscUrl: string): CachedRscResponse | null;
135
+ declare function consumePrefetchResponse(rscUrl: string, interceptionContext?: string | null, mountedSlotsHeader?: string | null): CachedRscResponse | null;
136
+ type NavigationListener = () => void;
137
+ type ClientNavigationState = {
138
+ listeners: Set<NavigationListener>;
139
+ cachedSearch: string;
140
+ cachedReadonlySearchParams: ReadonlyURLSearchParams;
141
+ cachedPathname: string;
142
+ clientParams: Record<string, string | string[]>;
143
+ clientParamsJson: string;
144
+ pendingClientParams: Record<string, string | string[]> | null;
145
+ pendingClientParamsJson: string | null;
146
+ pendingPathname: string | null;
147
+ pendingPathnameNavId: number | null;
148
+ originalPushState: typeof window.history.pushState;
149
+ originalReplaceState: typeof window.history.replaceState;
150
+ patchInstalled: boolean;
151
+ hasPendingNavigationUpdate: boolean;
152
+ suppressUrlNotifyCount: number;
153
+ navigationSnapshotActiveCount: number;
154
+ };
155
+ declare function setMountedSlotsHeader(header: string | null): void;
156
+ declare function getMountedSlotsHeader(): string | null;
157
+ declare function getClientNavigationState(): ClientNavigationState | null;
130
158
  /**
131
159
  * Mark a navigation snapshot as active. Called before startTransition
132
160
  * in renderNavigationPayload. While active, hooks prefer the snapshot
@@ -146,6 +174,18 @@ declare function setClientParams(params: Record<string, string | string[]>): voi
146
174
  declare function replaceClientParamsWithoutNotify(params: Record<string, string | string[]>): void;
147
175
  /** Get the current client params (for testing referential stability). */
148
176
  declare function getClientParams(): Record<string, string | string[]>;
177
+ /**
178
+ * Set the pending pathname for client-side navigation.
179
+ * Strips the base path before storing. Associates the pathname with the given navId
180
+ * so only that navigation (or a newer one) can clear it.
181
+ */
182
+ declare function setPendingPathname(pathname: string, navId: number): void;
183
+ /**
184
+ * Clear the pending pathname, but only if the given navId matches the one
185
+ * that set it, or if pendingPathnameNavId is null (no active owner).
186
+ * This prevents superseded navigations from clearing state belonging to newer navigations.
187
+ */
188
+ declare function clearPendingPathname(navId: number): void;
149
189
  /**
150
190
  * Returns the current pathname.
151
191
  * Server: from request context. Client: from window.location.
@@ -159,13 +199,20 @@ declare function useSearchParams(): ReadonlyURLSearchParams;
159
199
  * Returns the dynamic params for the current route.
160
200
  */
161
201
  declare function useParams<T extends Record<string, string | string[]> = Record<string, string | string[]>>(): T;
162
- declare function commitClientNavigationState(): void;
202
+ /**
203
+ * Commit pending client navigation state to committed snapshots.
204
+ *
205
+ * navId is optional: callers that don't own pendingPathname (for example,
206
+ * superseded pre-paint cleanup) may pass undefined to flush snapshot/params
207
+ * state without clearing pendingPathname owned by the active navigation.
208
+ */
209
+ declare function commitClientNavigationState(navId?: number): void;
163
210
  declare function pushHistoryStateWithoutNotify(data: unknown, unused: string, url?: string | URL | null): void;
164
211
  declare function replaceHistoryStateWithoutNotify(data: unknown, unused: string, url?: string | URL | null): void;
165
212
  /**
166
213
  * Navigate to a URL, handling external URLs, hash-only changes, and RSC navigation.
167
214
  */
168
- declare function navigateClientSide(href: string, mode: "push" | "replace", scroll: boolean): Promise<void>;
215
+ declare function navigateClientSide(href: string, mode: "push" | "replace", scroll: boolean, programmaticTransition?: boolean): Promise<void>;
169
216
  /**
170
217
  * App Router's useRouter — returns push/replace/back/forward/refresh.
171
218
  * Different from Pages Router's useRouter (next/router).
@@ -297,5 +344,5 @@ declare function forbidden(): never;
297
344
  */
298
345
  declare function unauthorized(): never;
299
346
  //#endregion
300
- export { CachedRscResponse, ClientNavigationRenderSnapshot, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, NavigationContext, PREFETCH_CACHE_TTL, PrefetchCacheEntry, ReadonlyURLSearchParams, RedirectType, SegmentMap, ServerInsertedHTMLContext, __basePath, _registerStateAccessors, activateNavigationSnapshot, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, createClientNavigationRenderSnapshot, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getClientNavigationRenderContext, getClientParams, getLayoutSegmentContext, getNavigationContext, getPrefetchCache, getPrefetchedUrls, isHTTPAccessFallbackError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, restoreRscResponse, setClientParams, setNavigationContext, snapshotRscResponse, storePrefetchResponse, toRscUrl, unauthorized, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
347
+ export { CachedRscResponse, ClientNavigationRenderSnapshot, GLOBAL_ACCESSORS_KEY, HTTP_ERROR_FALLBACK_ERROR_CODE, MAX_PREFETCH_CACHE_SIZE, NavigationContext, PREFETCH_CACHE_TTL, PrefetchCacheEntry, ReadonlyURLSearchParams, RedirectType, SegmentMap, ServerInsertedHTMLContext, __basePath, _registerStateAccessors, activateNavigationSnapshot, clearPendingPathname, clearServerInsertedHTML, commitClientNavigationState, consumePrefetchResponse, createClientNavigationRenderSnapshot, flushServerInsertedHTML, forbidden, getAccessFallbackHTTPStatus, getClientNavigationRenderContext, getClientNavigationState, getClientParams, getCurrentInterceptionContext, getCurrentNextUrl, getLayoutSegmentContext, getMountedSlotsHeader, getNavigationContext, getPrefetchCache, getPrefetchedUrls, isHTTPAccessFallbackError, navigateClientSide, notFound, permanentRedirect, prefetchRscResponse, pushHistoryStateWithoutNotify, redirect, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, restoreRscResponse, setClientParams, setMountedSlotsHeader, setNavigationContext, setPendingPathname, snapshotRscResponse, storePrefetchResponse, toRscUrl, unauthorized, useParams, usePathname, useRouter, useSearchParams, useSelectedLayoutSegment, useSelectedLayoutSegments, useServerInsertedHTML };
301
348
  //# sourceMappingURL=navigation.d.ts.map