vinext 1.4.1 → 1.4.3

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 (143) hide show
  1. package/README.md +5 -2
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/build/prerender-paths.d.ts +11 -1
  4. package/dist/build/prerender-paths.js +36 -10
  5. package/dist/check.js +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/client/dev-error-overlay.d.ts +2 -1
  8. package/dist/client/dev-error-overlay.js +8 -3
  9. package/dist/config/config-matchers.js +8 -4
  10. package/dist/config/destination-params.js +5 -2
  11. package/dist/config/next-config.d.ts +25 -1
  12. package/dist/config/next-config.js +31 -1
  13. package/dist/entries/app-rsc-entry.js +65 -30
  14. package/dist/entries/app-rsc-manifest.js +4 -0
  15. package/dist/entries/pages-server-entry.js +19 -4
  16. package/dist/index.js +37 -37
  17. package/dist/init-cloudflare.js +18 -2
  18. package/dist/init-platform.js +2 -2
  19. package/dist/init.js +42 -5
  20. package/dist/plugins/css-target.d.ts +18 -0
  21. package/dist/plugins/css-target.js +93 -0
  22. package/dist/routing/app-route-graph.d.ts +10 -0
  23. package/dist/routing/app-route-graph.js +15 -1
  24. package/dist/server/api-handler.d.ts +2 -0
  25. package/dist/server/api-handler.js +10 -8
  26. package/dist/server/app-action-forwarding.js +3 -1
  27. package/dist/server/app-browser-entry.js +37 -41
  28. package/dist/server/app-browser-error.d.ts +1 -1
  29. package/dist/server/app-browser-error.js +13 -4
  30. package/dist/server/app-browser-supplemental-refresh.d.ts +13 -0
  31. package/dist/server/app-browser-supplemental-refresh.js +36 -4
  32. package/dist/server/app-elements.d.ts +3 -0
  33. package/dist/server/app-elements.js +14 -1
  34. package/dist/server/app-fallback-renderer.js +2 -2
  35. package/dist/server/app-middleware.d.ts +2 -0
  36. package/dist/server/app-middleware.js +1 -0
  37. package/dist/server/app-optimistic-routing.d.ts +12 -1
  38. package/dist/server/app-optimistic-routing.js +38 -16
  39. package/dist/server/app-page-boundary-render.js +4 -2
  40. package/dist/server/app-page-cache-render.js +1 -1
  41. package/dist/server/app-page-dispatch.d.ts +11 -1
  42. package/dist/server/app-page-dispatch.js +78 -31
  43. package/dist/server/app-page-element-builder.d.ts +11 -20
  44. package/dist/server/app-page-element-builder.js +39 -50
  45. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  46. package/dist/server/app-page-intercept-segment-config.js +90 -0
  47. package/dist/server/app-page-navigation-params.d.ts +23 -0
  48. package/dist/server/app-page-navigation-params.js +49 -0
  49. package/dist/server/app-page-render.d.ts +5 -2
  50. package/dist/server/app-page-render.js +17 -3
  51. package/dist/server/app-page-request.d.ts +24 -2
  52. package/dist/server/app-page-request.js +10 -10
  53. package/dist/server/app-page-route-wiring.d.ts +17 -5
  54. package/dist/server/app-page-route-wiring.js +19 -13
  55. package/dist/server/app-page-segment-plan.js +9 -1
  56. package/dist/server/app-page-segment-state.d.ts +9 -0
  57. package/dist/server/app-page-segment-state.js +54 -1
  58. package/dist/server/app-pages-bridge.d.ts +6 -0
  59. package/dist/server/app-pages-bridge.js +8 -3
  60. package/dist/server/app-request-stage-independent-entry.js +2 -3
  61. package/dist/server/app-response-stage-entry.js +1 -0
  62. package/dist/server/app-route-module-loader.d.ts +2 -0
  63. package/dist/server/app-route-module-loader.js +1 -0
  64. package/dist/server/app-router-entry.js +2 -3
  65. package/dist/server/app-rsc-cache-busting.d.ts +8 -0
  66. package/dist/server/app-rsc-cache-busting.js +13 -1
  67. package/dist/server/app-rsc-combined-handler.js +2 -1
  68. package/dist/server/app-rsc-handler.d.ts +14 -1
  69. package/dist/server/app-rsc-handler.js +111 -62
  70. package/dist/server/app-rsc-response-finalizer.d.ts +18 -3
  71. package/dist/server/app-rsc-response-finalizer.js +61 -7
  72. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  73. package/dist/server/app-rsc-route-matching.js +42 -18
  74. package/dist/server/app-server-action-execution.js +16 -10
  75. package/dist/server/app-ssr-entry.js +8 -0
  76. package/dist/server/cache-control.d.ts +2 -0
  77. package/dist/server/cache-control.js +3 -1
  78. package/dist/server/config-headers.d.ts +7 -0
  79. package/dist/server/config-headers.js +20 -1
  80. package/dist/server/image-animation.d.ts +9 -0
  81. package/dist/server/image-animation.js +118 -0
  82. package/dist/server/image-local-patterns.d.ts +21 -0
  83. package/dist/server/image-local-patterns.js +59 -0
  84. package/dist/server/image-optimization.d.ts +48 -27
  85. package/dist/server/image-optimization.js +316 -22
  86. package/dist/server/image-source-reader.d.ts +34 -0
  87. package/dist/server/image-source-reader.js +126 -0
  88. package/dist/server/metadata-route-response.d.ts +6 -1
  89. package/dist/server/metadata-route-response.js +46 -10
  90. package/dist/server/middleware-matcher.d.ts +1 -0
  91. package/dist/server/middleware-matcher.js +1 -1
  92. package/dist/server/middleware-runtime.d.ts +5 -0
  93. package/dist/server/middleware-runtime.js +48 -15
  94. package/dist/server/middleware.d.ts +1 -1
  95. package/dist/server/middleware.js +2 -1
  96. package/dist/server/nitro-public-files-plugin.js +20 -8
  97. package/dist/server/nitro-request-target-plugin.d.ts +17 -0
  98. package/dist/server/nitro-request-target-plugin.js +22 -0
  99. package/dist/server/open-redirect.d.ts +2 -1
  100. package/dist/server/open-redirect.js +2 -1
  101. package/dist/server/pages-client-assets.d.ts +1 -0
  102. package/dist/server/pages-data-route.d.ts +5 -1
  103. package/dist/server/pages-data-route.js +14 -6
  104. package/dist/server/pages-i18n.d.ts +7 -0
  105. package/dist/server/pages-i18n.js +14 -1
  106. package/dist/server/pages-node-compat.js +7 -3
  107. package/dist/server/pages-page-handler.js +4 -4
  108. package/dist/server/pages-request-pipeline.d.ts +9 -0
  109. package/dist/server/pages-request-pipeline.js +35 -15
  110. package/dist/server/pages-request-stage-entry.js +8 -2
  111. package/dist/server/pages-router-entry.js +3 -0
  112. package/dist/server/prod-server.d.ts +1 -1
  113. package/dist/server/prod-server.js +64 -62
  114. package/dist/server/request-pipeline.d.ts +5 -0
  115. package/dist/server/request-pipeline.js +11 -1
  116. package/dist/server/response-stage-cacheability.d.ts +7 -0
  117. package/dist/server/response-stage-cacheability.js +6 -5
  118. package/dist/server/srvx-middleware-origin.d.ts +13 -0
  119. package/dist/server/srvx-middleware-origin.js +19 -0
  120. package/dist/server/srvx-request-target.d.ts +12 -0
  121. package/dist/server/srvx-request-target.js +20 -0
  122. package/dist/server/static-file-cache.js +10 -22
  123. package/dist/server/worker-image-optimization.d.ts +10 -3
  124. package/dist/server/worker-image-optimization.js +43 -9
  125. package/dist/server/worker-revalidation-context.js +1 -0
  126. package/dist/server/worker-utils.d.ts +8 -0
  127. package/dist/server/worker-utils.js +19 -2
  128. package/dist/shims/error-boundary.d.ts +6 -2
  129. package/dist/shims/error-boundary.js +4 -2
  130. package/dist/shims/image-props.d.ts +6 -0
  131. package/dist/shims/image-props.js +39 -3
  132. package/dist/shims/image.js +3 -3
  133. package/dist/shims/request-context.d.ts +3 -1
  134. package/dist/shims/slot.d.ts +3 -0
  135. package/dist/shims/slot.js +67 -9
  136. package/dist/shims/url-utils.js +6 -1
  137. package/dist/utils/base-path.d.ts +7 -0
  138. package/dist/utils/base-path.js +11 -1
  139. package/dist/utils/client-entry-manifest.d.ts +5 -0
  140. package/dist/utils/client-entry-manifest.js +1 -0
  141. package/dist/utils/client-runtime-metadata.d.ts +5 -0
  142. package/dist/utils/client-runtime-metadata.js +33 -7
  143. package/package.json +1 -1
@@ -14,5 +14,7 @@ export declare function handleApiRoute(runner: ModuleImporter, req: IncomingMess
14
14
  allowedRevalidateHeaderKeys?: readonly string[];
15
15
  trailingSlash?: boolean;
16
16
  skipTrailingSlashRedirect?: boolean;
17
+ /** Edge API request pathname, with any basePath the dev server stripped restored. */
18
+ requestPathname?: string;
17
19
  }): Promise<boolean>;
18
20
  //#endregion
@@ -1,4 +1,3 @@
1
- import { hasBasePath } from "../utils/base-path.js";
2
1
  import { matchRoute } from "../routing/pages-router.js";
3
2
  import "./server-globals.js";
4
3
  import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
@@ -94,7 +93,7 @@ function readEdgeRequestBody(req) {
94
93
  req.on("error", (error) => controller.error(error));
95
94
  } });
96
95
  }
97
- function createEdgeApiRequest(req, res, url, params, nextConfig) {
96
+ function createEdgeApiRequest(req, res, url, params, requestPathname) {
98
97
  const headers = new Headers();
99
98
  for (const [name, value] of Object.entries(req.headers)) {
100
99
  if (name.startsWith(":")) continue;
@@ -104,8 +103,7 @@ function createEdgeApiRequest(req, res, url, params, nextConfig) {
104
103
  const proto = resolveRequestProtocol(req);
105
104
  const host = resolveRequestHost(req, "localhost");
106
105
  const requestUrl = new URL(req.url ?? url, `${proto}://${host}`);
107
- const basePath = nextConfig?.basePath;
108
- if (basePath && !hasBasePath(requestUrl.pathname, basePath)) requestUrl.pathname = `${basePath}${requestUrl.pathname}`;
106
+ if (requestPathname !== void 0) requestUrl.pathname = requestPathname;
109
107
  const query = mergeRouteParamsIntoQuery(parseQueryString(url), params);
110
108
  requestUrl.search = urlQueryToSearchParams(query).toString();
111
109
  const signal = signalFromNodeResponse(res);
@@ -188,10 +186,14 @@ function enhanceApiObjects(req, res, query, body, trustedRevalidateOrigin, allow
188
186
  return this;
189
187
  },
190
188
  json(data) {
191
- this.setHeader("Content-Type", "application/json");
189
+ this.setHeader("Content-Type", "application/json; charset=utf-8");
192
190
  this.end(JSON.stringify(data));
193
191
  },
194
192
  send(data) {
193
+ if (data === null || data === void 0) {
194
+ this.end();
195
+ return;
196
+ }
195
197
  if (this.statusCode === 204 || this.statusCode === 304) {
196
198
  this.removeHeader("Content-Type");
197
199
  this.removeHeader("Content-Length");
@@ -210,8 +212,8 @@ function enhanceApiObjects(req, res, query, body, trustedRevalidateOrigin, allow
210
212
  this.end(data);
211
213
  return;
212
214
  }
213
- if (typeof data === "object" && data !== null) {
214
- this.setHeader("Content-Type", "application/json");
215
+ if (typeof data === "object" || typeof data === "number" || typeof data === "boolean") {
216
+ this.setHeader("Content-Type", "application/json; charset=utf-8");
215
217
  this.end(JSON.stringify(data));
216
218
  } else {
217
219
  if (!this.getHeader("Content-Type")) this.setHeader("Content-Type", "text/plain");
@@ -250,7 +252,7 @@ async function handleApiRoute(runner, req, res, url, apiRoutes, nextConfig) {
250
252
  try {
251
253
  const apiModule = await importModule(runner, route.filePath);
252
254
  if (isEdgeApiRouteModule(apiModule)) {
253
- const nextRequest = new NextRequest(createEdgeApiRequest(req, res, url, params, nextConfig), nextConfig ? { nextConfig: {
255
+ const nextRequest = new NextRequest(createEdgeApiRequest(req, res, url, params, nextConfig?.requestPathname), nextConfig ? { nextConfig: {
254
256
  basePath: nextConfig.basePath,
255
257
  i18n: nextConfig.i18n ?? void 0,
256
258
  trailingSlash: nextConfig.trailingSlash,
@@ -6,6 +6,7 @@ import { RequestCookies, ResponseCookies } from "../shims/server.js";
6
6
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
7
7
  import { getSetCookieName } from "./cookie-utils.js";
8
8
  import "../shims/internal/cookies.js";
9
+ import { isStreamedActionRedirectResponse, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
9
10
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage } from "./server-action-not-found.js";
10
11
  //#region src/server/app-action-forwarding.ts
11
12
  const ACTION_FORWARD_FORBIDDEN_HEADERS = /* @__PURE__ */ new Set([
@@ -71,11 +72,12 @@ function filterActionForwardResponse(response, sourceMiddlewareHeaders, sourceMi
71
72
  for (const [key, value] of response.headers) if (!ACTION_FORWARD_FORBIDDEN_HEADERS.has(key.toLowerCase())) headers.set(key, value);
72
73
  for (const setCookie of response.headers.getSetCookie()) headers.append("set-cookie", setCookie);
73
74
  const preserveResponseStatus = response.headers.get("x-nextjs-action-not-found") === "1" || response.headers.has("x-action-redirect");
74
- return new Response(response.body, {
75
+ const forwardedResponse = new Response(response.body, {
75
76
  headers,
76
77
  status: preserveResponseStatus ? response.status : sourceMiddlewareStatus ?? response.status,
77
78
  statusText: response.statusText
78
79
  });
80
+ return isStreamedActionRedirectResponse(response) ? markStreamedActionRedirectResponse(forwardedResponse) : forwardedResponse;
79
81
  }
80
82
  function emptyActionForwardResponse(middlewareContext) {
81
83
  const headers = new Headers(middlewareContext.headers ?? void 0);
@@ -1,8 +1,5 @@
1
- import { addBasePathToPathname, stripBasePath } from "../utils/base-path.js";
2
1
  import { VINEXT_MOUNTED_LAYOUTS_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER } from "./headers.js";
3
- import { splitPathnameForRouteMatch } from "../routing/utils.js";
4
2
  import { AppRouterContext } from "../shims/internal/app-router-context.js";
5
- import { matchRoutePattern } from "../routing/route-pattern.js";
6
3
  import { installWindowNext, setWindowNextInternalSourcePage } from "../client/window-next.js";
7
4
  import { retryScrollTo, scrollToHashTargetOnNextFrame } from "../shims/hash-scroll.js";
8
5
  import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, formatRscRequestPath, isRscCompatibilityIdCompatible } from "./app-rsc-cache-busting.js";
@@ -23,16 +20,16 @@ import { navigationPlanner } from "./navigation-planner.js";
23
20
  import { decodeRedirectError, isRedirectError } from "../shims/navigation-errors.js";
24
21
  import { beginAppRouterScrollIntent, consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
25
22
  import { getBfcacheIdMapContext, getNavigationContext, setNavigationContext } from "../shims/navigation-context-state.js";
26
- import { DYNAMIC_NAVIGATION_CACHE_TTL, PREFETCH_CACHE_TTL, __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, deletePrefetchResponseSnapshot, disableNavigationResponsePrefetchCacheReuse, getClientNavigationRenderContext, getMountedSlotsHeader, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidateDynamicPrefetchCache, invalidatePrefetchCache, invalidateVisitedResponsePrefetches, parseRenderedPathAndSearchHeader, preloadHybridClientRouteOwner, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveLoadedHybridClientRewriteHref, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, seedPrefetchResponseSnapshot, setClientParams, setMountedSlotsHeader, setPendingPathname, trackAppRouterNavigationInFlight, useRouter } from "../shims/navigation.js";
23
+ import { DYNAMIC_NAVIGATION_CACHE_TTL, PREFETCH_CACHE_TTL, __basePath, activateNavigationSnapshot, appRouterInstance, commitClientNavigationState, consumePrefetchResponse, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, deletePrefetchResponseSnapshot, disableNavigationResponsePrefetchCacheReuse, getClientNavigationRenderContext, getMountedSlotsHeader, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidateDynamicPrefetchCache, invalidatePrefetchCache, invalidateVisitedResponsePrefetches, parseRenderedPathAndSearchHeader, preloadHybridClientRouteOwner, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolveLoadedHybridClientRewriteHref, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, seedPrefetchResponseSnapshot, setClientParams, setMountedSlotsHeader, setPendingPathname, trackAppRouterNavigationInFlight, useRouter } from "../shims/navigation.js";
27
24
  import { AppRouterScrollCommitProvider } from "../shims/app-router-scroll.js";
28
25
  import { chunksToReadableStream, createProgressiveRscStream, getVinextBrowserGlobal } from "./app-browser-stream.js";
29
26
  import { createMountedLayoutsHeader, holdsSkippedLayouts } from "./app-mounted-layouts.js";
30
- import { BfcacheIdentityMapContext, bindAppElementsRenderedSearch, setAppElementsRenderedSearch } from "../shims/slot.js";
27
+ import { BfcacheIdentityMapContext, bindAppElementsRenderedSearch, isBfcacheIdentityRetained, setAppElementsRenderedSearch } from "../shims/slot.js";
31
28
  import { COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl } from "./app-browser-state.js";
32
29
  import { clearHardNavigationLoopGuard, createAppBrowserNavigationController, createBasePathStrippedPathAndSearch, navigateDocumentDirectly } from "./app-browser-navigation-controller.js";
33
30
  import { createAppRouteTree } from "./app-route-tree.js";
34
31
  import { shouldRecoverSamePathSearchCommitOnResponseCompletion } from "./app-browser-navigation-response.js";
35
- import { createSupplementalRefreshCoordinator, mergeRefreshedParallelSlot, requireCompleteSupplementalRefresh, resolvePersistedSourcePageRefreshes, resolveServerActionSupplementalRefresh, resolveSupplementalRefreshes } from "./app-browser-supplemental-refresh.js";
32
+ import { createSupplementalRefreshCoordinator, mergeRefreshedParallelSlot, requireCompleteSupplementalRefresh, resolvePersistedRefreshInterceptions, resolvePersistedSourcePageRefreshes, resolveServerActionSupplementalRefresh, resolveSupplementalRefreshes } from "./app-browser-supplemental-refresh.js";
36
33
  import { createAppBrowserNavigationAbortCoordinator } from "./app-browser-navigation-abort.js";
37
34
  import { consumeInitialFormState, createVinextHydrateRootOptions, hydrateRootInTransition, resolveFetchedHydrationLocation } from "./app-browser-hydration.js";
38
35
  import { AppBrowserHistoryController } from "./app-browser-history-controller.js";
@@ -195,6 +192,12 @@ const RSC_HMR_SETTLE_DELAY_MS = 150;
195
192
  const DEFAULT_GLOBAL_ERROR_COMPONENT = DefaultGlobalError;
196
193
  let latestRscHmrUpdateId = 0;
197
194
  let synchronousPopstateScrollRestoreNavigationId = null;
195
+ let pendingHistoryRestoreCommit = null;
196
+ function flushPendingHistoryRestoreCommit() {
197
+ const commit = pendingHistoryRestoreCommit;
198
+ pendingHistoryRestoreCommit = null;
199
+ commit?.();
200
+ }
198
201
  function waitForRscHmrSettle(delayMs = RSC_HMR_SETTLE_DELAY_MS) {
199
202
  return new Promise((resolve) => {
200
203
  window.setTimeout(resolve, delayMs);
@@ -212,6 +215,10 @@ function restoreHistoryStateSnapshot(historyState, navId, onApprovedBeforeCommit
212
215
  beforeCommit: () => {
213
216
  onApprovedBeforeCommit?.();
214
217
  beforeCommit();
218
+ if (!restoreCopiedExternalHistoryEntry) {
219
+ activateNavigationSnapshot();
220
+ pendingHistoryRestoreCommit = () => commitClientNavigationState(navId);
221
+ }
215
222
  },
216
223
  navId,
217
224
  state,
@@ -223,7 +230,8 @@ function restoreHistoryStateSnapshot(historyState, navId, onApprovedBeforeCommit
223
230
  clientNavigationCacheGeneration += 1;
224
231
  revokeHistoryRestoreNavigationReuse(visitedResponseCache);
225
232
  disableNavigationResponsePrefetchCacheReuse();
226
- commitClientNavigationState(navId, { releaseSnapshot: false });
233
+ if (restoreCopiedExternalHistoryEntry) commitClientNavigationState(navId, { releaseSnapshot: false });
234
+ else flushPendingHistoryRestoreCommit();
227
235
  return true;
228
236
  }
229
237
  function getBrowserRouterState() {
@@ -267,36 +275,6 @@ function clearDynamicClientNavigationCaches() {
267
275
  optimisticRouteTemplates.clear();
268
276
  historyController.invalidateRestorableClientState();
269
277
  }
270
- function getMatchedUrlPathname(matchedUrl) {
271
- return new URL(matchedUrl, "https://vinext.local").pathname;
272
- }
273
- function resolvePersistedRefreshInterceptions(state, routeManifest, refreshUrl, requestInterceptionContext) {
274
- if (routeManifest === null) return [];
275
- const refreshPathname = stripBasePath(refreshUrl.pathname, __basePath);
276
- const refreshPathParts = splitPathnameForRouteMatch(refreshPathname);
277
- const resolved = [];
278
- for (const binding of state.slotBindings) {
279
- if (binding.state !== "active" || !binding.activeRouteId) continue;
280
- if (!binding.interceptionId || !binding.interceptionSourceMatchedUrl) continue;
281
- const activeRoute = AppElementsWire.parseElementKey(binding.activeRouteId);
282
- if (activeRoute?.kind !== "route") continue;
283
- const match = routeManifest.segmentGraph.interceptions.get(binding.interceptionId);
284
- if (!match) continue;
285
- const retainedInterception = state.interception?.slotId === binding.slotId && matchRoutePattern(splitPathnameForRouteMatch(getMatchedUrlPathname(state.interception.targetMatchedUrl)), match.targetPatternParts) !== null ? state.interception : null;
286
- const targetPathname = retainedInterception?.targetMatchedUrl ?? activeRoute.path;
287
- const targetParts = splitPathnameForRouteMatch(getMatchedUrlPathname(targetPathname));
288
- if (requestInterceptionContext !== null && state.interception?.slotId === binding.slotId && matchRoutePattern(refreshPathParts, match.targetPatternParts) !== null && matchRoutePattern(targetParts, match.targetPatternParts) !== null) continue;
289
- const targetUrl = new URL(targetPathname, refreshUrl);
290
- targetUrl.pathname = addBasePathToPathname(targetUrl.pathname, __basePath);
291
- targetUrl.search = refreshUrl.search;
292
- resolved.push({
293
- interception: match,
294
- interceptionContext: retainedInterception?.sourceMatchedUrl ?? binding.interceptionSourceMatchedUrl,
295
- targetPathname: `${targetUrl.pathname}${targetUrl.search}`
296
- });
297
- }
298
- return resolved;
299
- }
300
278
  async function fetchPersistedInterceptedSlotRefresh(options) {
301
279
  const headers = createRscRequestHeaders({
302
280
  interceptionContext: options.interceptionContext,
@@ -471,7 +449,13 @@ async function commitSameUrlNavigatePayload(nextElements, actionInitiation, retu
471
449
  if (revalidation !== "none" && routeManifestLoaded) {
472
450
  const refreshUrl = new URL(actionInitiation.href);
473
451
  const requestInterceptionContext = resolveInterceptionContextFromPreviousNextUrl(actionInitiation.routerState.previousNextUrl, __basePath);
474
- const persistedInterceptions = resolvePersistedRefreshInterceptions(actionInitiation.routerState, getBrowserRouteManifest(), refreshUrl, requestInterceptionContext);
452
+ const persistedInterceptions = resolvePersistedRefreshInterceptions({
453
+ basePath: __basePath,
454
+ refreshUrl,
455
+ requestInterceptionContext,
456
+ routeManifest: getBrowserRouteManifest(),
457
+ state: actionInitiation.routerState
458
+ });
475
459
  const sourcePageRefreshes = resolvePersistedSourcePageRefreshes({
476
460
  basePath: __basePath,
477
461
  refreshUrl,
@@ -1083,7 +1067,7 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1083
1067
  const formState = consumeInitialFormState(getVinextBrowserGlobal());
1084
1068
  const hydrateRootOptions = import.meta.env.DEV && devErrorOverlay ? createVinextHydrateRootOptions({
1085
1069
  formState,
1086
- onCaughtError: invalidateOnCaughtError(createDevOnCaughtError(devErrorOverlay.devOnCaughtError, onUncaughtError)),
1070
+ onCaughtError: invalidateOnCaughtError(createDevOnCaughtError(devErrorOverlay.devOnCaughtError, onUncaughtError, devErrorOverlay.reportRootErrorBoundaryCatch)),
1087
1071
  onUncaughtError
1088
1072
  }) : createVinextHydrateRootOptions({
1089
1073
  formState,
@@ -1184,7 +1168,13 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1184
1168
  const requestInterceptionContext = requestState.interceptionContext;
1185
1169
  const requestPreviousNextUrl = requestState.previousNextUrl;
1186
1170
  const shouldSupplementVisibleBranches = navigationKind === "refresh" || navigationKind === "traverse" && !reuseCurrentBfcacheIds;
1187
- const persistedRefreshInterceptions = navigationKind === "refresh" ? resolvePersistedRefreshInterceptions(navigationInitiationState, getBrowserRouteManifest(), url, requestInterceptionContext) : [];
1171
+ const persistedRefreshInterceptions = navigationKind === "refresh" ? resolvePersistedRefreshInterceptions({
1172
+ basePath: __basePath,
1173
+ refreshUrl: url,
1174
+ requestInterceptionContext,
1175
+ routeManifest: getBrowserRouteManifest(),
1176
+ state: navigationInitiationState
1177
+ }) : [];
1188
1178
  const persistedSourcePageRefreshes = shouldSupplementVisibleBranches ? resolvePersistedSourcePageRefreshes({
1189
1179
  activeRoutePaths: navigationKind === "traverse" ? readHistoryStateActiveRoutePaths(activeTraversalIntent?.historyState) ?? [] : void 0,
1190
1180
  basePath: __basePath,
@@ -1393,7 +1383,10 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1393
1383
  currentElements: navigationInitiationState.elements,
1394
1384
  currentLayoutIds: navigationInitiationState.layoutIds,
1395
1385
  currentParams: navigationInitiationState.navigationSnapshot.params,
1386
+ isBfcacheIdentityRetained,
1396
1387
  routeManifest,
1388
+ shownElements: getBrowserRouterState().elements,
1389
+ targetElements: optimisticPayload.elements,
1397
1390
  targetRouteParams: optimisticPayload.routeParams,
1398
1391
  targetUrlParts: optimisticPayload.urlParts,
1399
1392
  template: optimisticPayload.template
@@ -1635,7 +1628,10 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1635
1628
  discardPendingNavigation: () => {
1636
1629
  if (browserNavigationController.discardPendingNavigation(historyController.readCurrentTreeSnapshot())) navigationAbortCoordinator.abortActive();
1637
1630
  },
1638
- flushCommittingNavigationUrl: () => browserNavigationController.flushCommittingNavigationUrl()
1631
+ flushCommittingNavigationUrl: () => {
1632
+ flushPendingHistoryRestoreCommit();
1633
+ browserNavigationController.flushCommittingNavigationUrl();
1634
+ }
1639
1635
  });
1640
1636
  const handlePopstate = createPopstateRestoreHandler({
1641
1637
  getActiveNavigationId: browserNavigationController.getActiveNavigationId.bind(browserNavigationController),
@@ -8,7 +8,7 @@ export declare function createOnUncaughtError(options?: {
8
8
  coerceToError?: boolean;
9
9
  }): HydrateRootErrorHandler;
10
10
  export declare function createProdOnCaughtError(onImplicitRootError: HydrateRootErrorHandler): HydrateRootErrorHandler;
11
- export declare function createDevOnCaughtError(onCaughtError: HydrateRootErrorHandler, onImplicitRootError: HydrateRootErrorHandler): HydrateRootErrorHandler;
11
+ export declare function createDevOnCaughtError(onCaughtError: HydrateRootErrorHandler, onImplicitRootError: HydrateRootErrorHandler, onRootErrorBoundaryCatch?: () => void): HydrateRootErrorHandler;
12
12
  export declare function createProdOnRecoverableError(onReportedError: () => void): (error: unknown) => void;
13
13
  //#endregion
14
14
  export {};
@@ -2,10 +2,18 @@ import { isUnknownRecord } from "../utils/record.js";
2
2
  import { isNavigationSignalError } from "../utils/navigation-signal.js";
3
3
  import { isBailoutToCSRError } from "../shims/navigation-errors.js";
4
4
  //#region src/server/app-browser-error.ts
5
- function isImplicitRootErrorBoundary(errorInfo) {
6
- if (!isUnknownRecord(errorInfo.errorBoundary)) return false;
5
+ function getErrorBoundaryProps(errorInfo) {
6
+ if (!isUnknownRecord(errorInfo.errorBoundary)) return {};
7
7
  const props = errorInfo.errorBoundary.props;
8
- return isUnknownRecord(props) && props.isImplicitRootErrorBoundary === true;
8
+ return isUnknownRecord(props) ? props : {};
9
+ }
10
+ function isImplicitRootErrorBoundary(errorInfo) {
11
+ return getErrorBoundaryProps(errorInfo).isImplicitRootErrorBoundary === true;
12
+ }
13
+ function isRootErrorBoundary(errorInfo) {
14
+ const props = getErrorBoundaryProps(errorInfo);
15
+ if (props.isGlobalErrorBoundary === true) return true;
16
+ return props.isImplicitRootErrorBoundary === true && props.wrapsGlobalErrorBoundary !== true;
9
17
  }
10
18
  function logCaughtError(error, errorInfo) {
11
19
  console.error(error);
@@ -38,8 +46,9 @@ function createProdOnCaughtError(onImplicitRootError) {
38
46
  logCaughtError(error, errorInfo);
39
47
  };
40
48
  }
41
- function createDevOnCaughtError(onCaughtError, onImplicitRootError) {
49
+ function createDevOnCaughtError(onCaughtError, onImplicitRootError, onRootErrorBoundaryCatch) {
42
50
  return (error, errorInfo) => {
51
+ if (!isNavigationSignalError(error) && isRootErrorBoundary(errorInfo)) onRootErrorBoundaryCatch?.();
43
52
  if (isImplicitRootErrorBoundary(errorInfo)) {
44
53
  onImplicitRootError(error, errorInfo);
45
54
  return;
@@ -1,3 +1,4 @@
1
+ import { BrowserRouteManifest, BrowserRouteManifestInterception } from "../routing/browser-route-manifest.js";
1
2
  import { AppElements } from "./app-elements-wire.js";
2
3
  import "./app-elements.js";
3
4
  import { AppRouterState } from "./app-browser-state.js";
@@ -31,6 +32,18 @@ export declare function resolvePersistedSourcePageRefreshes(options: {
31
32
  refreshUrl: URL;
32
33
  state: Pick<AppRouterState, "previousNextUrl" | "slotBindings">;
33
34
  }): string[];
35
+ export type PersistedRefreshInterception = {
36
+ interception: BrowserRouteManifestInterception;
37
+ interceptionContext: string;
38
+ targetPathname: string;
39
+ };
40
+ export declare function resolvePersistedRefreshInterceptions(options: {
41
+ basePath: string;
42
+ refreshUrl: URL;
43
+ requestInterceptionContext: string | null;
44
+ routeManifest: BrowserRouteManifest | null;
45
+ state: Pick<AppRouterState, "interception" | "slotBindings">;
46
+ }): PersistedRefreshInterception[];
34
47
  export declare function createSupplementalRefreshCoordinator(): {
35
48
  abortAll(): void;
36
49
  begin(options: {
@@ -1,6 +1,7 @@
1
- import { addBasePathToPathname, removeTrailingSlash, stripBasePath } from "../utils/base-path.js";
2
- import { normalizePathnameForRouteMatch } from "../routing/utils.js";
1
+ import { removeTrailingSlash, restoreBasePath, stripBasePath } from "../utils/base-path.js";
2
+ import { normalizePathnameForRouteMatch, splitPathnameForRouteMatch } from "../routing/utils.js";
3
3
  import { normalizePath } from "./normalize-path.js";
4
+ import { matchRoutePattern } from "../routing/route-pattern.js";
4
5
  import { APP_BFCACHE_SEGMENT_IDENTITIES_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_SLOT_BINDINGS_KEY, AppElementsWire, normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
5
6
  import "./app-elements.js";
6
7
  import { isNestedBfcacheSlotSegmentIdFor } from "./bfcache-identity.js";
@@ -131,7 +132,7 @@ function resolvePersistedSourcePageRefreshes(options) {
131
132
  });
132
133
  for (const activeRoutePath of activeRoutePaths) {
133
134
  if (activeRoutePath !== refreshRoutePathname && (refreshRoutePathname === "/" || activeRoutePath.startsWith(refreshRoutePathname + "/"))) continue;
134
- const pathname = addBasePathToPathname(activeRoutePath, options.basePath);
135
+ const pathname = restoreBasePath(activeRoutePath, options.basePath);
135
136
  if (sourceUrlsByPathname.has(pathname)) continue;
136
137
  const sourceUrl = new URL(pathname, options.refreshUrl);
137
138
  sourceUrl.search = options.refreshUrl.search;
@@ -145,6 +146,37 @@ function resolvePersistedSourcePageRefreshes(options) {
145
146
  }
146
147
  return resolved;
147
148
  }
149
+ function getMatchedUrlPathname(matchedUrl) {
150
+ return new URL(matchedUrl, "https://vinext.local").pathname;
151
+ }
152
+ function resolvePersistedRefreshInterceptions(options) {
153
+ const { basePath, refreshUrl, requestInterceptionContext, routeManifest, state } = options;
154
+ if (routeManifest === null) return [];
155
+ const refreshPathname = stripBasePath(refreshUrl.pathname, basePath);
156
+ const refreshPathParts = splitPathnameForRouteMatch(refreshPathname);
157
+ const resolved = [];
158
+ for (const binding of state.slotBindings) {
159
+ if (binding.state !== "active" || !binding.activeRouteId) continue;
160
+ if (!binding.interceptionId || !binding.interceptionSourceMatchedUrl) continue;
161
+ const activeRoute = AppElementsWire.parseElementKey(binding.activeRouteId);
162
+ if (activeRoute?.kind !== "route") continue;
163
+ const match = routeManifest.segmentGraph.interceptions.get(binding.interceptionId);
164
+ if (!match) continue;
165
+ const retainedInterception = state.interception?.slotId === binding.slotId && matchRoutePattern(splitPathnameForRouteMatch(getMatchedUrlPathname(state.interception.targetMatchedUrl)), match.targetPatternParts) !== null ? state.interception : null;
166
+ const targetPathname = retainedInterception?.targetMatchedUrl ?? activeRoute.path;
167
+ const targetParts = splitPathnameForRouteMatch(getMatchedUrlPathname(targetPathname));
168
+ if (requestInterceptionContext !== null && state.interception?.slotId === binding.slotId && matchRoutePattern(refreshPathParts, match.targetPatternParts) !== null && matchRoutePattern(targetParts, match.targetPatternParts) !== null) continue;
169
+ const targetUrl = new URL(targetPathname, refreshUrl);
170
+ targetUrl.pathname = restoreBasePath(targetUrl.pathname, basePath);
171
+ targetUrl.search = refreshUrl.search;
172
+ resolved.push({
173
+ interception: match,
174
+ interceptionContext: retainedInterception?.sourceMatchedUrl ?? binding.interceptionSourceMatchedUrl,
175
+ targetPathname: `${targetUrl.pathname}${targetUrl.search}`
176
+ });
177
+ }
178
+ return resolved;
179
+ }
148
180
  function createSupplementalRefreshCoordinator() {
149
181
  const controllers = /* @__PURE__ */ new Set();
150
182
  return {
@@ -202,4 +234,4 @@ async function resolveSupplementalRefreshes(options) {
202
234
  }
203
235
  }
204
236
  //#endregion
205
- export { SupplementalRefreshError, createSupplementalRefreshCoordinator, mergeRefreshedParallelSlot, requireCompleteSupplementalRefresh, resolvePersistedSourcePageRefreshes, resolveServerActionSupplementalRefresh, resolveSupplementalRefreshes };
237
+ export { SupplementalRefreshError, createSupplementalRefreshCoordinator, mergeRefreshedParallelSlot, requireCompleteSupplementalRefresh, resolvePersistedRefreshInterceptions, resolvePersistedSourcePageRefreshes, resolveServerActionSupplementalRefresh, resolveSupplementalRefreshes };
@@ -1,6 +1,9 @@
1
1
  import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_BFCACHE_SEGMENT_IDENTITIES_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_SEGMENTS_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementValue, AppElements, AppElementsBfcacheSegmentIdentities, AppElementsInterception, AppElementsSlotBinding, AppElementsWire, AppOutgoingElements, AppWireElements, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata } from "./app-elements-wire.js";
2
2
  //#region src/server/app-elements.d.ts
3
3
  export declare const APP_PREFETCH_LOADING_SHELL_MARKER_KEY = "__prefetchLoadingShell";
4
+ type AppElementsStateKeyRebinder = (stateKey: string) => string;
5
+ export declare function setOptimisticStateKeyRebinder(elements: Record<string, unknown>, rebind: AppElementsStateKeyRebinder): void;
6
+ export declare function readOptimisticStateKeyRebinder(elements: AppElements): AppElementsStateKeyRebinder | null;
4
7
  /** Prefix of the element that streams a route's generateMetadata() tags in the body. */
5
8
  export declare const APP_STREAMING_METADATA_BODY_ID_PREFIX = "__vinext_streaming_metadata_body:";
6
9
  export declare function getMountedSlotIds(elements: AppElements): string[];
@@ -2,6 +2,19 @@ import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
2
2
  import { APP_ARTIFACT_COMPATIBILITY_KEY, APP_BFCACHE_SEGMENT_IDENTITIES_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_SEGMENTS_KEY, APP_STATIC_SIBLINGS_KEY, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata } from "./app-elements-wire.js";
3
3
  //#region src/server/app-elements.ts
4
4
  const APP_PREFETCH_LOADING_SHELL_MARKER_KEY = "__prefetchLoadingShell";
5
+ /**
6
+ * An optimistic shell's rebinder from the learned URL's segment state keys to
7
+ * the target's. Only the browser sets it, on the elements it builds a shell
8
+ * from, so it never reaches the wire.
9
+ */
10
+ const APP_OPTIMISTIC_STATE_KEY_REBINDER_KEY = "__optimisticStateKeyRebinder";
11
+ function setOptimisticStateKeyRebinder(elements, rebind) {
12
+ elements[APP_OPTIMISTIC_STATE_KEY_REBINDER_KEY] = rebind;
13
+ }
14
+ function readOptimisticStateKeyRebinder(elements) {
15
+ const rebind = Reflect.get(elements, APP_OPTIMISTIC_STATE_KEY_REBINDER_KEY);
16
+ return typeof rebind === "function" ? rebind : null;
17
+ }
5
18
  /** Prefix of the element that streams a route's generateMetadata() tags in the body. */
6
19
  const APP_STREAMING_METADATA_BODY_ID_PREFIX = "__vinext_streaming_metadata_body:";
7
20
  function getMountedSlotIds(elements) {
@@ -18,4 +31,4 @@ function resolveVisitedResponseInterceptionContext(requestInterceptionContext, p
18
31
  return payloadInterceptionContext ?? requestInterceptionContext;
19
32
  }
20
33
  //#endregion
21
- export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_BFCACHE_SEGMENT_IDENTITIES_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_SEGMENTS_KEY, APP_STATIC_SIBLINGS_KEY, APP_STREAMING_METADATA_BODY_ID_PREFIX, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, resolveVisitedResponseInterceptionContext };
34
+ export { APP_ARTIFACT_COMPATIBILITY_KEY, APP_BFCACHE_SEGMENT_IDENTITIES_KEY, APP_CACHE_ENTRY_REUSE_PROOF_KEY, APP_DYNAMIC_STALE_TIME_KEY, APP_INTERCEPTION_CONTEXT_KEY, APP_INTERCEPTION_KEY, APP_LAYOUT_IDS_KEY, APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_ROOT_LAYOUT_KEY, APP_ROUTE_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_SLOT_BINDINGS_KEY, APP_SOURCE_PAGE_SEGMENTS_KEY, APP_STATIC_SIBLINGS_KEY, APP_STREAMING_METADATA_BODY_ID_PREFIX, APP_UNMATCHED_SLOT_WIRE_VALUE, AppElementsWire, UNMATCHED_SLOT, buildOutgoingAppPayload, compareAppElementsSlotIds, getMountedSlotIds, getMountedSlotIdsHeader, isAppElementsRecord, normalizeAppElements, normalizeAppElementsSlotBindings, readAppElementsMetadata, readOptimisticStateKeyRebinder, resolveVisitedResponseInterceptionContext, setOptimisticStateKeyRebinder };
@@ -1,10 +1,10 @@
1
1
  import { runOutsideRequestScopes } from "../shims/internal/als-registry.js";
2
2
  import { shareIsolatePromise } from "./isolate-shared-promise.js";
3
- import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
4
- import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
5
3
  import { renderAppPageErrorBoundary, renderAppPageHttpAccessFallback } from "./app-page-boundary-render.js";
4
+ import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
6
5
  import { DEFAULT_GLOBAL_NOT_FOUND_MODULE } from "./default-global-not-found-module.js";
7
6
  import { DEFAULT_NOT_FOUND_MODULE } from "./default-http-access-fallback-modules.js";
7
+ import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
8
8
  //#region src/server/app-fallback-renderer.ts
9
9
  const EMPTY_MW_CTX = {
10
10
  headers: null,
@@ -26,6 +26,8 @@ export type ApplyAppMiddlewareOptions = {
26
26
  middlewareRequest?: Request;
27
27
  module: MiddlewareModule;
28
28
  request: Request;
29
+ /** Forwarded to `executeMiddleware`. */
30
+ restoreLocaleRootTrailingSlash?: boolean;
29
31
  validateExternalRewriteRequest?: () => Promise<Response | null>;
30
32
  /**
31
33
  * Forwarded to `executeMiddleware` so the NextRequest exposes a NextURL with
@@ -203,6 +203,7 @@ async function applyAppMiddleware(options) {
203
203
  },
204
204
  requestBodyAlreadyIsolated: true,
205
205
  request: middlewareRequest,
206
+ restoreLocaleRootTrailingSlash: options.restoreLocaleRootTrailingSlash,
206
207
  skipTrailingSlashRedirect: options.skipTrailingSlashRedirect,
207
208
  trailingSlash: options.trailingSlash
208
209
  });
@@ -10,6 +10,8 @@ export type OptimisticRouteTemplate = {
10
10
  omittedBfcacheSegmentIds: readonly string[];
11
11
  omittedLayoutIds: readonly string[];
12
12
  pageElementIds: readonly string[];
13
+ /** The navigation params of the URL the template was learned from. */
14
+ params: Readonly<Record<string, string | string[]>>;
13
15
  routeId: string;
14
16
  };
15
17
  type OptimisticNavigationPayload = {
@@ -48,7 +50,7 @@ export declare function createOptimisticRouteTemplate(options: {
48
50
  mountedSlotsHeader: string | null;
49
51
  routeManifest: BrowserRouteManifest;
50
52
  }): OptimisticRouteTemplate | null;
51
- export declare function createOptimisticRouteElements(template: OptimisticRouteTemplate): AppElements;
53
+ export declare function createOptimisticRouteElements(template: OptimisticRouteTemplate, params?: Readonly<Record<string, string | string[]>>): AppElements;
52
54
  /**
53
55
  * The optimistic shell reveals the prefetch's streamed metadata, such as its
54
56
  * title. React won't hide revealed content again in a transition, so while the
@@ -77,7 +79,16 @@ export declare function canCommitOptimisticRouteTemplate(options: {
77
79
  currentElements: AppElements;
78
80
  currentLayoutIds: readonly string[];
79
81
  currentParams: Readonly<Record<string, string | string[]>>;
82
+ /** Whether a hidden BFCache entry retains a segment identity. */
83
+ isBfcacheIdentityRetained?: (identity: string) => boolean;
80
84
  routeManifest: BrowserRouteManifest;
85
+ /**
86
+ * The elements on screen, when a shell earlier in this navigation replaced
87
+ * the current ones. Defaults to `currentElements`.
88
+ */
89
+ shownElements?: AppElements;
90
+ /** The shell's elements for the target. */
91
+ targetElements?: AppElements;
81
92
  targetRouteParams: Readonly<Record<string, string | string[]>>;
82
93
  targetUrlParts: readonly string[];
83
94
  template: OptimisticRouteTemplate;
@@ -4,10 +4,10 @@ import { decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/util
4
4
  import { extractRawRoutePatternParams, matchRoutePattern } from "../routing/route-pattern.js";
5
5
  import { buildOptimisticRouteTrie, matchOptimisticRouteTrie } from "../routing/optimistic-route-trie.js";
6
6
  import { stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
7
- import { AppElementsWire } from "./app-elements-wire.js";
8
- import "./app-elements.js";
7
+ import { APP_BFCACHE_SEGMENT_IDENTITIES_KEY, AppElementsWire } from "./app-elements-wire.js";
8
+ import { setOptimisticStateKeyRebinder } from "./app-elements.js";
9
9
  import { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId } from "./bfcache-identity.js";
10
- import { canonicalizeAppPageParams, resolveAppPagePatternStateKey, resolveAppPageSemanticSegmentStateKey } from "./app-page-segment-state.js";
10
+ import { APP_PAGE_DEFAULT_SEGMENT_KEY, APP_PAGE_SEGMENT_KEY, canonicalizeAppPageParams, createAppPageSegmentStateKeyRebinder, resolveAppPagePatternStateKey, resolveAppPageSemanticSegmentStateKey } from "./app-page-segment-state.js";
11
11
  import { Suspense, createElement, isValidElement } from "react";
12
12
  //#region src/server/app-optimistic-routing.ts
13
13
  const routeTrieCache = /* @__PURE__ */ new WeakMap();
@@ -114,6 +114,18 @@ function resolveTargetBfcacheSegmentIdentity(options) {
114
114
  const segmentParams = binding.state === "active" && binding.slotPatternParts !== void 0 && binding.slotPatternParts.length > 0 && !binding.slotParamNames?.every((name) => routeParamNames.has(name)) ? matchRoutePattern(options.targetUrlParts, binding.slotPatternParts) ?? options.targetRouteParams : options.targetRouteParams;
115
115
  const ownerLayout = binding.ownerLayoutId ? options.routeManifest.segmentGraph.layouts.get(binding.ownerLayoutId) : void 0;
116
116
  const ownerStateKey = ownerLayout ? resolveAppPagePatternStateKey(ownerLayout.patternParts, options.targetRouteParams) : "";
117
+ const deriveLevelIdentity = (segmentKeys) => {
118
+ const identityKey = JSON.stringify(segmentKeys);
119
+ return deriveBfcacheSegmentIdentity({
120
+ activeRouteGraphId: null,
121
+ boundSegmentKey: ownerStateKey ? JSON.stringify([ownerStateKey, identityKey]) : identityKey,
122
+ interceptionTargetRouteGraphId: null,
123
+ kind: "slot",
124
+ ownerLayoutGraphId: binding.ownerLayoutId,
125
+ slotGraphId: binding.slotId,
126
+ state: binding.state
127
+ });
128
+ };
117
129
  const boundSegmentKeys = [];
118
130
  let level = 0;
119
131
  for (const segment of binding.routeSegments ?? []) {
@@ -125,17 +137,9 @@ function resolveTargetBfcacheSegmentIdentity(options) {
125
137
  segment
126
138
  }, segmentParams));
127
139
  if (createNestedBfcacheSlotSegmentId(binding.slotId, level) !== options.segmentId) continue;
128
- const identityKey = JSON.stringify(boundSegmentKeys);
129
- return deriveBfcacheSegmentIdentity({
130
- activeRouteGraphId: null,
131
- boundSegmentKey: ownerStateKey ? JSON.stringify([ownerStateKey, identityKey]) : identityKey,
132
- interceptionTargetRouteGraphId: null,
133
- kind: "slot",
134
- ownerLayoutGraphId: binding.ownerLayoutId,
135
- slotGraphId: binding.slotId,
136
- state: binding.state
137
- });
140
+ return deriveLevelIdentity(boundSegmentKeys);
138
141
  }
142
+ if (level > 0 && createNestedBfcacheSlotSegmentId(binding.slotId, level + 1) === options.segmentId) return deriveLevelIdentity([...boundSegmentKeys, binding.state === "default" ? APP_PAGE_DEFAULT_SEGMENT_KEY : APP_PAGE_SEGMENT_KEY]);
139
143
  if (level === 0 && createNestedBfcacheSlotSegmentId(binding.slotId, 1) === options.segmentId) return deriveBfcacheSegmentIdentity({
140
144
  activeRouteGraphId: null,
141
145
  boundSegmentKey: ownerStateKey ? JSON.stringify([ownerStateKey, ""]) : "",
@@ -188,18 +192,31 @@ function createOptimisticRouteTemplate(options) {
188
192
  if (options.allowLoadingShell && (routeElement === void 0 || routeElement === null)) return null;
189
193
  const pageElementIds = getPageElementIds(options.elements, match.route);
190
194
  if (pageElementIds.length === 0) return null;
195
+ const urlParts = hrefToRouteParts(options.href, options.basePath);
196
+ if (urlParts === null) return null;
191
197
  return {
192
198
  elements: options.elements,
193
199
  mountedSlotsHeader: options.mountedSlotsHeader,
194
200
  omittedBfcacheSegmentIds: options.elements["__prefetchLoadingShell"] === "LoadingBoundary" ? getOmittedBfcacheSegmentIds(options.elements) : [],
195
201
  omittedLayoutIds: options.elements["__prefetchLoadingShell"] === "LoadingBoundary" ? metadata.layoutIds.filter((layoutId) => !Object.hasOwn(options.elements, layoutId)) : [],
196
202
  pageElementIds,
203
+ params: resolveOptimisticNavigationParams({
204
+ match,
205
+ rawUrlParts: urlParts.raw,
206
+ routeManifest: options.routeManifest,
207
+ urlParts: urlParts.normalized
208
+ }).navigationParams,
197
209
  routeId: match.route.id
198
210
  };
199
211
  }
200
- function createOptimisticRouteElements(template) {
212
+ function createOptimisticRouteElements(template, params = template.params) {
201
213
  const elements = { ...template.elements };
202
214
  for (const pageElementId of template.pageElementIds) elements[pageElementId] = createElement(OptimisticRouteSegment);
215
+ const reify = createAppPageSegmentStateKeyRebinder(template.params, params);
216
+ if (reify === null) return elements;
217
+ setOptimisticStateKeyRebinder(elements, reify);
218
+ const identities = AppElementsWire.readMetadata(template.elements).bfcacheSegmentIdentities;
219
+ if (Object.keys(identities).length > 0) elements[APP_BFCACHE_SEGMENT_IDENTITIES_KEY] = Object.fromEntries(Object.entries(identities).map(([id, identity]) => [id, reify(identity)]));
203
220
  return elements;
204
221
  }
205
222
  function findStreamingMetadataBody(elements) {
@@ -244,9 +261,14 @@ function keepOptimisticStreamingMetadata(elements, optimisticElements, navigatio
244
261
  * loading boundary does not re-trigger.
245
262
  */
246
263
  function canCommitOptimisticRouteTemplate(options) {
264
+ const currentIdentities = AppElementsWire.readMetadata(options.currentElements).bfcacheSegmentIdentities;
265
+ const { isBfcacheIdentityRetained, targetElements } = options;
266
+ if (isBfcacheIdentityRetained && targetElements) {
267
+ const shownIdentities = new Set(Object.values(options.shownElements ? AppElementsWire.readMetadata(options.shownElements).bfcacheSegmentIdentities : currentIdentities));
268
+ for (const identity of Object.values(AppElementsWire.readMetadata(targetElements).bfcacheSegmentIdentities)) if (!shownIdentities.has(identity) && isBfcacheIdentityRetained(identity)) return false;
269
+ }
247
270
  if (options.template.omittedLayoutIds.length === 0 && options.template.omittedBfcacheSegmentIds.length === 0) return true;
248
271
  const currentLayoutIds = new Set(options.currentLayoutIds);
249
- const currentIdentities = AppElementsWire.readMetadata(options.currentElements).bfcacheSegmentIdentities;
250
272
  for (const layoutId of options.template.omittedLayoutIds) {
251
273
  if (!currentLayoutIds.has(layoutId)) continue;
252
274
  if (!Object.hasOwn(options.currentElements, layoutId)) continue;
@@ -302,7 +324,7 @@ function resolveOptimisticNavigationPayload(options) {
302
324
  urlParts: urlParts.normalized
303
325
  });
304
326
  return {
305
- elements: createOptimisticRouteElements(template),
327
+ elements: createOptimisticRouteElements(template, navigationParams),
306
328
  params: navigationParams,
307
329
  routeParams,
308
330
  template,
@@ -20,9 +20,9 @@ import { hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRou
20
20
  import { renderAppPageBoundaryResponse, resolveAppPageErrorBoundary, resolveAppPageHttpAccessBoundaryModule, wrapAppPageBoundaryElement } from "./app-page-boundary.js";
21
21
  import { createRouteNotFoundHeadPlanOptions, recoverAppPageRouteHead, resolveHttpAccessFallbackMetadata, resolveHttpAccessFallbackViewport } from "./app-page-http-access-fallback-metadata.js";
22
22
  import "./app-rsc-route-matching.js";
23
- import { createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage } from "./app-page-route-wiring.js";
24
- import { resolveSlotParamOverrides } from "./app-page-element-builder.js";
23
+ import { resolveSlotParamOverrides } from "./app-page-navigation-params.js";
25
24
  import { createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlResponse } from "./app-page-stream.js";
25
+ import { createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageSourcePage } from "./app-page-route-wiring.js";
26
26
  import { Fragment, Suspense, createElement } from "react";
27
27
  //#region src/server/app-page-boundary-render.ts
28
28
  async function AppPageBoundaryStreamedMetadata(props) {
@@ -85,8 +85,10 @@ function wrapRenderedBoundaryElement(options) {
85
85
  renderErrorBoundary(GlobalErrorComponent, children) {
86
86
  return createElement(GlobalErrorBoundary, {
87
87
  fallback: DEFAULT_GLOBAL_ERROR_COMPONENT,
88
+ wrapsGlobalErrorBoundary: true,
88
89
  children: createElement(ErrorBoundary, {
89
90
  fallback: GlobalErrorComponent,
91
+ isGlobalErrorBoundary: true,
90
92
  children
91
93
  })
92
94
  });