vinext 0.1.0 → 0.1.1

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 (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
@@ -99,7 +99,8 @@ function createBfcacheSegmentStateKeyMap(options) {
99
99
  return stateKeys;
100
100
  }
101
101
  function createNextBfcacheIdMap(options) {
102
- for (const value of Object.values(options.current)) rememberBfcacheId(value);
102
+ const current = options.reuseCurrent === false ? {} : options.current;
103
+ for (const value of Object.values(current)) rememberBfcacheId(value);
103
104
  for (const value of Object.values(options.restored ?? {})) rememberBfcacheId(value);
104
105
  const currentMetadata = readAppElementsMetadata(options.currentElements);
105
106
  const nextMetadata = readAppElementsMetadata(options.elements);
@@ -113,7 +114,7 @@ function createNextBfcacheIdMap(options) {
113
114
  }) === createBfcacheSegmentIdentity(id, {
114
115
  metadata: nextMetadata,
115
116
  pathname: nextPathname
116
- }) ? options.current[id] : void 0;
117
+ }) ? current[id] : void 0;
117
118
  const value = options.restored?.[id] ?? currentValue ?? mintBfcacheId();
118
119
  ids[id] = value;
119
120
  rememberBfcacheId(value);
@@ -396,7 +397,8 @@ async function createPendingNavigationCommit(options) {
396
397
  currentPathname: options.currentState.navigationSnapshot.pathname,
397
398
  elements,
398
399
  nextPathname: options.navigationSnapshot.pathname,
399
- restored: options.restoredBfcacheIds
400
+ restored: options.restoredBfcacheIds,
401
+ reuseCurrent: options.reuseCurrentBfcacheIds
400
402
  }),
401
403
  ...cacheEntryReuseProof ? { cacheEntryReuseProof } : {},
402
404
  elements,
@@ -414,6 +416,7 @@ async function createPendingNavigationCommit(options) {
414
416
  previousNextUrl,
415
417
  renderId: options.renderId,
416
418
  rootLayoutTreePath: metadata.rootLayoutTreePath,
419
+ reuseCurrentBfcacheIds: options.reuseCurrentBfcacheIds ?? true,
417
420
  routeId: metadata.routeId,
418
421
  skippedLayoutIds: metadata.skippedLayoutIds,
419
422
  type: options.type
@@ -56,29 +56,31 @@ function reduceApprovedVisibleCommitState(state, commit) {
56
56
  switch (action.type) {
57
57
  case "traverse":
58
58
  case "navigate": {
59
+ const preserveElementIds = action.reuseCurrentBfcacheIds ? commit.decision.preserveElementIds : [];
60
+ const preservePreviousSlotIds = action.reuseCurrentBfcacheIds ? commit.decision.preservePreviousSlotIds : [];
59
61
  const mergedElements = mergeElements(state.elements, action.elements, {
60
- clearAbsentSlots: action.type === "traverse",
61
- preserveAbsentSlots: commit.decision.preserveAbsentSlots,
62
- preserveElementIds: commit.decision.preserveElementIds,
63
- preservePreviousSlotIds: commit.decision.preservePreviousSlotIds
62
+ clearAbsentSlots: action.type === "traverse" || !action.reuseCurrentBfcacheIds,
63
+ preserveAbsentSlots: action.reuseCurrentBfcacheIds && commit.decision.preserveAbsentSlots,
64
+ preserveElementIds,
65
+ preservePreviousSlotIds
64
66
  });
65
67
  return commitVisibleRouterState(state, {
66
68
  bfcacheIds: preserveBfcacheIdsForMergedElements({
67
69
  elements: mergedElements,
68
70
  next: action.bfcacheIds,
69
- previous: state.bfcacheIds
71
+ previous: action.reuseCurrentBfcacheIds ? state.bfcacheIds : {}
70
72
  }),
71
73
  elements: mergedElements,
72
74
  interception: action.interception,
73
75
  interceptionContext: action.interceptionContext,
74
- layoutFlags: mergeLayoutFlags(state.layoutFlags, action.layoutFlags, commit.decision.preserveElementIds),
76
+ layoutFlags: mergeLayoutFlags(state.layoutFlags, action.layoutFlags, preserveElementIds),
75
77
  layoutIds: action.layoutIds,
76
78
  navigationSnapshot: action.navigationSnapshot,
77
79
  previousNextUrl: action.previousNextUrl,
78
80
  renderId: action.renderId,
79
81
  rootLayoutTreePath: action.rootLayoutTreePath,
80
82
  routeId: action.routeId,
81
- slotBindings: mergeSlotBindings(state.slotBindings, action.slotBindings, action.layoutIds, commit.decision.preservePreviousSlotIds)
83
+ slotBindings: mergeSlotBindings(state.slotBindings, action.slotBindings, action.layoutIds, preservePreviousSlotIds)
82
84
  }, action.operation);
83
85
  }
84
86
  case "replace": return commitVisibleRouterState(state, {
@@ -10,6 +10,50 @@ type HistoryTraversalIntent = {
10
10
  historyState: unknown;
11
11
  targetHistoryIndex: number | null;
12
12
  };
13
+ type HistoryStateSnapshotRestoreDecision<TState> = {
14
+ kind: "restore";
15
+ state: TState;
16
+ targetHistoryIndex: number;
17
+ } | {
18
+ kind: "skip";
19
+ reason: "guarded" | "missing-history-index" | "missing-snapshot" | "stale-bfcache-version";
20
+ targetHistoryIndex: number | null;
21
+ };
22
+ declare class HistoryStateSnapshotCache<TState> {
23
+ #private;
24
+ constructor(options: {
25
+ maxEntries: number;
26
+ });
27
+ clear(): void;
28
+ remember(options: {
29
+ bfcacheVersion: number;
30
+ historyIndex: number | null;
31
+ state: TState;
32
+ }): void;
33
+ resolveRestore(options: {
34
+ currentBfcacheVersion: number;
35
+ guarded: boolean;
36
+ historyState: unknown;
37
+ }): HistoryStateSnapshotRestoreDecision<TState>;
38
+ }
39
+ declare class RestorableClientStateController<TState> {
40
+ #private;
41
+ constructor(options: {
42
+ initialHistoryState: unknown;
43
+ maxHistoryStateSnapshots: number;
44
+ });
45
+ get currentBfcacheVersion(): number;
46
+ beginCacheInvalidationGuard(): () => void;
47
+ isCacheInvalidationGuarded(): boolean;
48
+ isCurrentBfcacheVersion(historyState: unknown): boolean;
49
+ readCurrentBfcacheVersionHistoryIds(historyState: unknown): BfcacheIdMap | null;
50
+ invalidateClientState(): void;
51
+ rememberHistoryStateSnapshot(options: {
52
+ historyIndex: number | null;
53
+ state: TState;
54
+ }): void;
55
+ resolveHistoryStateSnapshotRestore(historyState: unknown): HistoryStateSnapshotRestoreDecision<TState>;
56
+ }
13
57
  declare function createHistoryStateWithPreviousNextUrl(state: unknown, previousNextUrl: string | null): HistoryStateRecord | null;
14
58
  declare function createHistoryStateWithNavigationMetadata(state: unknown, metadata: {
15
59
  bfcacheIds?: BfcacheIdMap | null;
@@ -39,4 +83,4 @@ declare function resolveHistoryTraversalIntent(options: {
39
83
  historyState: unknown;
40
84
  }): HistoryTraversalIntent;
41
85
  //#endregion
42
- export { BfcacheIdMap, HistoryTraversalIntent, createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent };
86
+ export { BfcacheIdMap, HistoryStateSnapshotCache, HistoryTraversalIntent, RestorableClientStateController, createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent };
@@ -6,6 +6,114 @@ const VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY = "__vinext_previousNextUrl";
6
6
  const VINEXT_HISTORY_INDEX_HISTORY_STATE_KEY = "__vinext_historyIndex";
7
7
  const VINEXT_BFCACHE_IDS_HISTORY_STATE_KEY = "__vinext_bfcacheIds";
8
8
  const VINEXT_BFCACHE_VERSION_HISTORY_STATE_KEY = "__vinext_bfcacheVersion";
9
+ var HistoryStateSnapshotCache = class {
10
+ #maxEntries;
11
+ #snapshots = /* @__PURE__ */ new Map();
12
+ constructor(options) {
13
+ this.#maxEntries = options.maxEntries;
14
+ }
15
+ clear() {
16
+ this.#snapshots.clear();
17
+ }
18
+ remember(options) {
19
+ if (options.historyIndex === null) return;
20
+ this.#snapshots.delete(options.historyIndex);
21
+ this.#snapshots.set(options.historyIndex, {
22
+ bfcacheVersion: options.bfcacheVersion,
23
+ state: options.state
24
+ });
25
+ if (this.#snapshots.size <= this.#maxEntries) return;
26
+ const oldestIndex = this.#snapshots.keys().next().value;
27
+ if (typeof oldestIndex === "number") this.#snapshots.delete(oldestIndex);
28
+ }
29
+ resolveRestore(options) {
30
+ const targetHistoryIndex = readHistoryStateTraversalIndex(options.historyState);
31
+ if (targetHistoryIndex === null) return {
32
+ kind: "skip",
33
+ reason: "missing-history-index",
34
+ targetHistoryIndex
35
+ };
36
+ const snapshot = this.#snapshots.get(targetHistoryIndex);
37
+ if (!snapshot) return {
38
+ kind: "skip",
39
+ reason: "missing-snapshot",
40
+ targetHistoryIndex
41
+ };
42
+ if (options.guarded) return {
43
+ kind: "skip",
44
+ reason: "guarded",
45
+ targetHistoryIndex
46
+ };
47
+ if (snapshot.bfcacheVersion !== options.currentBfcacheVersion) {
48
+ this.#snapshots.delete(targetHistoryIndex);
49
+ return {
50
+ kind: "skip",
51
+ reason: "stale-bfcache-version",
52
+ targetHistoryIndex
53
+ };
54
+ }
55
+ return {
56
+ kind: "restore",
57
+ state: snapshot.state,
58
+ targetHistoryIndex
59
+ };
60
+ }
61
+ };
62
+ var RestorableClientStateController = class {
63
+ #currentBfcacheVersion;
64
+ #pendingCacheInvalidationGuards = 0;
65
+ #snapshots;
66
+ constructor(options) {
67
+ const initialHistoryBfcacheVersion = readHistoryStateBfcacheVersion(options.initialHistoryState);
68
+ this.#currentBfcacheVersion = initialHistoryBfcacheVersion === null ? 0 : initialHistoryBfcacheVersion + 1;
69
+ this.#snapshots = new HistoryStateSnapshotCache({ maxEntries: options.maxHistoryStateSnapshots });
70
+ }
71
+ get currentBfcacheVersion() {
72
+ return this.#currentBfcacheVersion;
73
+ }
74
+ beginCacheInvalidationGuard() {
75
+ this.#pendingCacheInvalidationGuards += 1;
76
+ let released = false;
77
+ return () => {
78
+ if (released) return;
79
+ released = true;
80
+ this.#pendingCacheInvalidationGuards = Math.max(0, this.#pendingCacheInvalidationGuards - 1);
81
+ };
82
+ }
83
+ isCacheInvalidationGuarded() {
84
+ return this.#pendingCacheInvalidationGuards > 0;
85
+ }
86
+ isCurrentBfcacheVersion(historyState) {
87
+ return isHistoryStateBfcacheVersionCurrent(historyState, this.#currentBfcacheVersion);
88
+ }
89
+ readCurrentBfcacheVersionHistoryIds(historyState) {
90
+ if (this.isCacheInvalidationGuarded()) return null;
91
+ const ids = readHistoryStateBfcacheIds(historyState);
92
+ if (ids === null) return null;
93
+ return this.isCurrentBfcacheVersion(historyState) ? ids : null;
94
+ }
95
+ #invalidateBfcacheIds() {
96
+ this.#currentBfcacheVersion += 1;
97
+ }
98
+ invalidateClientState() {
99
+ this.#snapshots.clear();
100
+ this.#invalidateBfcacheIds();
101
+ }
102
+ rememberHistoryStateSnapshot(options) {
103
+ this.#snapshots.remember({
104
+ bfcacheVersion: this.#currentBfcacheVersion,
105
+ historyIndex: options.historyIndex,
106
+ state: options.state
107
+ });
108
+ }
109
+ resolveHistoryStateSnapshotRestore(historyState) {
110
+ return this.#snapshots.resolveRestore({
111
+ currentBfcacheVersion: this.#currentBfcacheVersion,
112
+ guarded: this.isCacheInvalidationGuarded(),
113
+ historyState
114
+ });
115
+ }
116
+ };
9
117
  function cloneHistoryState(state) {
10
118
  if (!state || typeof state !== "object") return {};
11
119
  const nextState = {};
@@ -112,4 +220,4 @@ function resolveHistoryTraversalIntent(options) {
112
220
  };
113
221
  }
114
222
  //#endregion
115
- export { createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent };
223
+ export { HistoryStateSnapshotCache, RestorableClientStateController, createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent };
@@ -1,15 +1,19 @@
1
1
  import { AppElementsWire } from "./app-elements-wire.js";
2
2
  import "./app-elements.js";
3
- import { ErrorBoundary } from "../shims/error-boundary.js";
3
+ import { isNavigationSignalError } from "../utils/navigation-signal.js";
4
+ import { ErrorBoundary, GlobalErrorBoundary } from "../shims/error-boundary.js";
4
5
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
5
6
  import { MetadataHead, ViewportHead } from "../shims/metadata.js";
7
+ import { resolveAppPageSpecialError } from "./app-page-execution.js";
6
8
  import { resolveAppPageHead } from "./app-page-head.js";
7
9
  import { createAppPageLayoutEntries } from "./app-page-route-wiring.js";
8
10
  import { buildClientHookErrorMessage } from "../shims/client-hook-error.js";
11
+ import DefaultGlobalError from "../shims/default-global-error.js";
9
12
  import { renderAppPageBoundaryResponse, resolveAppPageErrorBoundary, resolveAppPageHttpAccessBoundaryModule, wrapAppPageBoundaryElement } from "./app-page-boundary.js";
10
13
  import { createAppPageFontData, renderAppPageHtmlResponse } from "./app-page-stream.js";
11
14
  import { Fragment, createElement } from "react";
12
15
  //#region src/server/app-page-boundary-render.ts
16
+ const DEFAULT_GLOBAL_ERROR_COMPONENT = DefaultGlobalError;
13
17
  function getDefaultExport(module) {
14
18
  return module?.default ?? null;
15
19
  }
@@ -25,9 +29,12 @@ function wrapRenderedBoundaryElement(options) {
25
29
  makeThenableParams: options.makeThenableParams,
26
30
  matchedParams: options.matchedParams,
27
31
  renderErrorBoundary(GlobalErrorComponent, children) {
28
- return createElement(ErrorBoundary, {
29
- fallback: GlobalErrorComponent,
30
- children
32
+ return createElement(GlobalErrorBoundary, {
33
+ fallback: DEFAULT_GLOBAL_ERROR_COMPONENT,
34
+ children: createElement(ErrorBoundary, {
35
+ fallback: GlobalErrorComponent,
36
+ children
37
+ })
31
38
  });
32
39
  },
33
40
  renderLayout(LayoutComponent, children, asyncParams) {
@@ -233,22 +240,28 @@ async function renderAppPageErrorBoundary(options) {
233
240
  } catch (error) {
234
241
  console.error(`[vinext] App page error boundary head resolution failed for ${options.route?.pattern ?? pathname}:`, error);
235
242
  }
236
- const element = wrapRenderedBoundaryElement({
237
- element: createElement(Fragment, null, ...headElements, createElement(errorBoundary.component, { error: errorObject })),
238
- globalErrorModule: options.globalErrorModule,
239
- includeGlobalErrorBoundary: !errorBoundary.isGlobalError,
240
- isRscRequest: options.isRscRequest,
241
- layoutModules,
242
- layoutTreePositions: options.route?.layoutTreePositions,
243
- makeThenableParams: options.makeThenableParams,
244
- matchedParams,
245
- resolveChildSegments: options.resolveChildSegments,
246
- routeSegments: options.route?.routeSegments,
247
- skipLayoutWrapping: errorBoundary.isGlobalError
248
- });
249
- return renderAppPageBoundaryElementResponse({
243
+ const buildElement = (BoundaryComponent) => {
244
+ const boundaryElement = createElement(BoundaryComponent, { error: errorObject });
245
+ return wrapRenderedBoundaryElement({
246
+ element: createElement(Fragment, null, ...headElements, errorBoundary.isGlobalError ? createElement(GlobalErrorBoundary, {
247
+ fallback: DEFAULT_GLOBAL_ERROR_COMPONENT,
248
+ children: boundaryElement
249
+ }) : boundaryElement),
250
+ globalErrorModule: options.globalErrorModule,
251
+ includeGlobalErrorBoundary: !errorBoundary.isGlobalError,
252
+ isRscRequest: options.isRscRequest,
253
+ layoutModules,
254
+ layoutTreePositions: options.route?.layoutTreePositions,
255
+ makeThenableParams: options.makeThenableParams,
256
+ matchedParams,
257
+ resolveChildSegments: options.resolveChildSegments,
258
+ routeSegments: options.route?.routeSegments,
259
+ skipLayoutWrapping: errorBoundary.isGlobalError
260
+ });
261
+ };
262
+ const renderWith = (BoundaryComponent) => renderAppPageBoundaryElementResponse({
250
263
  ...options,
251
- element,
264
+ element: buildElement(BoundaryComponent),
252
265
  initialDevServerError: rawError,
253
266
  layoutModules,
254
267
  navigationParams: matchedParams,
@@ -256,6 +269,15 @@ async function renderAppPageErrorBoundary(options) {
256
269
  routePattern: options.route?.pattern,
257
270
  status: 200
258
271
  });
272
+ try {
273
+ return await renderWith(errorBoundary.component);
274
+ } catch (renderError) {
275
+ if (errorBoundary.isGlobalError && !isNavigationSignalError(renderError) && !resolveAppPageSpecialError(renderError)) {
276
+ console.error(`[vinext] global-error.tsx threw while rendering for ${options.route?.pattern ?? pathname}; falling back to the built-in default global-error:`, renderError);
277
+ return renderWith(DEFAULT_GLOBAL_ERROR_COMPONENT);
278
+ }
279
+ throw renderError;
280
+ }
259
281
  }
260
282
  const _clientHookPattern = /\b(useState|useEffect|useReducer|useRef|useContext|useLayoutEffect|useInsertionEffect|useSyncExternalStore|useTransition|useImperativeHandle|useDeferredValue|useActionState|useOptimistic|useEffectEvent)\b.*is not a function/;
261
283
  function rewriteClientHookError(error) {
@@ -78,6 +78,12 @@ type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
78
78
  * SSR head. Undefined or empty disables emission entirely.
79
79
  */
80
80
  clientTraceMetadata?: readonly string[];
81
+ /**
82
+ * Maximum total length (in characters) of the preload `Link` header emitted
83
+ * during SSR. `0` disables emission. From `reactMaxHeadersLength` in
84
+ * `next.config`. Undefined falls back to the React default downstream.
85
+ */
86
+ reactMaxHeadersLength?: number;
81
87
  buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, layoutParamAccess?: AppLayoutParamAccessTracker) => Promise<AppPageElement>;
82
88
  clientReuseManifest?: ClientReuseManifestParseResult;
83
89
  cleanPathname: string;
@@ -217,7 +217,7 @@ async function dispatchAppPageInner(options) {
217
217
  expireSeconds: options.expireSeconds,
218
218
  revalidateSeconds: currentRevalidateSeconds ?? 0,
219
219
  renderFreshPageForCache: async () => {
220
- const revalidationTarget = resolveAppPageInterceptionRerenderTarget({
220
+ const revalidationTarget = await resolveAppPageInterceptionRerenderTarget({
221
221
  cleanPathname: options.cleanPathname,
222
222
  currentParams: options.params,
223
223
  currentRoute: route,
@@ -256,6 +256,7 @@ async function dispatchAppPageInner(options) {
256
256
  }, {
257
257
  basePath: options.basePath,
258
258
  clientTraceMetadata: options.clientTraceMetadata,
259
+ reactMaxHeadersLength: options.reactMaxHeadersLength,
259
260
  rootParams: options.rootParams,
260
261
  waitForAllReady: true,
261
262
  ...revalidatedRscCapture.sideStream ? {
@@ -390,6 +391,7 @@ async function dispatchAppPageInner(options) {
390
391
  return renderAppPageLifecycle({
391
392
  basePath: options.basePath,
392
393
  clientTraceMetadata: options.clientTraceMetadata,
394
+ reactMaxHeadersLength: options.reactMaxHeadersLength,
393
395
  cleanPathname: options.cleanPathname,
394
396
  clearRequestContext: options.clearRequestContext,
395
397
  consumeDynamicUsage,
@@ -37,6 +37,7 @@ type BuildPageElementsOptions<TModule extends AppPageModule = AppPageModule, TEr
37
37
  route: AppPageBuildRoute<TModule, TErrorModule>;
38
38
  params: AppPageParams;
39
39
  routePath: string;
40
+ displayPathname?: string;
40
41
  pageRequest: AppPagePageRequest<TModule>; /** Root-level global-error.tsx module. Present when the app defines this file. */
41
42
  globalErrorModule?: TErrorModule | null; /** Root-level not-found.tsx module. Present when the app defines this file. */
42
43
  rootNotFoundModule?: TModule | null; /** Root-level forbidden.tsx module. Present when the app defines this file. */
@@ -9,6 +9,7 @@ import { makeObservedAppPageSearchParamsThenable } from "./app-page-search-param
9
9
  import { buildAppPageElements, createAppPageTreePath } from "./app-page-route-wiring.js";
10
10
  import { DEFAULT_GLOBAL_ERROR_MODULE } from "./default-global-error-module.js";
11
11
  import { shouldServeStreamingMetadata } from "./streaming-metadata.js";
12
+ import "./app-rsc-route-matching.js";
12
13
  import { createElement } from "react";
13
14
  //#region src/server/app-page-element-builder.ts
14
15
  /**
@@ -28,18 +29,21 @@ import { createElement } from "react";
28
29
  * {@link https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/create-metadata.tsx|create-metadata.tsx}.
29
30
  */
30
31
  async function buildPageElements(options) {
31
- const { route, params, routePath, pageRequest, globalErrorModule, rootNotFoundModule, rootForbiddenModule, rootUnauthorizedModule, metadataRoutes } = options;
32
+ const { route, params, routePath, displayPathname = routePath, pageRequest, globalErrorModule, rootNotFoundModule, rootForbiddenModule, rootUnauthorizedModule, metadataRoutes } = options;
32
33
  const { opts, searchParams, isRscRequest, mountedSlotsHeader, renderMode = APP_RSC_RENDER_MODE_NAVIGATION } = pageRequest;
33
34
  const pageModule = route.page;
34
- const PageComponent = pageModule?.default;
35
+ const isSiblingIntercept = opts?.interceptSlotKey === "__vinext_page_intercept" && !!opts?.interceptPage;
36
+ const effectivePageModule = isSiblingIntercept ? opts.interceptPage : pageModule;
37
+ const EffectivePageComponent = effectivePageModule?.default;
38
+ const effectiveParams = isSiblingIntercept ? opts.interceptParams ?? params : params;
35
39
  const hasPageModule = !!pageModule;
36
40
  const renderIdentity = createAppPageRenderIdentity({
37
- displayPathname: routePath,
41
+ displayPathname,
38
42
  interceptionContext: opts?.interceptionContext ?? null,
39
43
  interceptSourceMatchedUrl: opts?.interceptSourceMatchedUrl ?? null,
40
- interceptSlotId: opts?.interceptSlotId ?? null
44
+ interceptSlotId: isSiblingIntercept ? null : opts?.interceptSlotId ?? null
41
45
  });
42
- if (hasPageModule && !PageComponent) {
46
+ if ((hasPageModule || isSiblingIntercept) && !EffectivePageComponent) {
43
47
  let noExportRootLayout = null;
44
48
  const noExportLayoutIds = route.ids?.layouts ?? route.layouts.map((_, index) => AppElementsWire.encodeLayoutId(createAppPageTreePath(route.routeSegments, route.layoutTreePositions?.[index] ?? 0)));
45
49
  if (route.layouts?.length > 0) {
@@ -62,7 +66,7 @@ async function buildPageElements(options) {
62
66
  layoutModules: route.layouts,
63
67
  layoutTreePositions: route.layoutTreePositions,
64
68
  metadataRoutes,
65
- pageModule: route.page ?? null,
69
+ pageModule: effectivePageModule ?? null,
66
70
  parallelRoutes: resolveActiveParallelRouteHeadInputs({
67
71
  interceptLayouts: opts?.interceptLayouts ?? null,
68
72
  interceptPage: opts?.interceptPage ?? null,
@@ -72,12 +76,12 @@ async function buildPageElements(options) {
72
76
  routeSegments: route.routeSegments ?? [],
73
77
  slots: route.slots ?? null
74
78
  }),
75
- params,
79
+ params: effectiveParams,
76
80
  routePath: route.pattern,
77
81
  routeSegments: route.routeSegments ?? null,
78
82
  searchParams
79
83
  });
80
- const pageProps = { params: makeThenableParams(params) };
84
+ const pageProps = { params: makeThenableParams(effectiveParams) };
81
85
  let pageSearchParamsThenable;
82
86
  if (searchParams) {
83
87
  pageSearchParamsThenable = !shouldSuppressLoadingBoundaries(renderMode) && Boolean(route.loading?.default) ? makeObservedAppPageSearchParamsThenable(pageSearchParams) : makeThenableParams(pageSearchParams);
@@ -86,8 +90,16 @@ async function buildPageElements(options) {
86
90
  const mountedSlotIds = mountedSlotsHeader ? new Set(mountedSlotsHeader.split(" ")) : null;
87
91
  const slotOverrides = buildSlotOverrides(route, params, routePath, opts);
88
92
  const metadataPlacement = hasDynamicMetadata && shouldServeStreamingMetadata(pageRequest.request.headers.get("user-agent") ?? "", options.htmlLimitedBots) ? "body" : "head";
93
+ let siblingInterceptElement = isSiblingIntercept && EffectivePageComponent ? createElement(EffectivePageComponent, pageProps) : null;
94
+ if (isSiblingIntercept && siblingInterceptElement !== null && opts?.interceptLayouts?.length) {
95
+ const siblingThenableParams = makeThenableParams(effectiveParams);
96
+ for (let i = opts.interceptLayouts.length - 1; i >= 0; i--) {
97
+ const LayoutComponent = opts.interceptLayouts[i]?.default;
98
+ if (LayoutComponent) siblingInterceptElement = createElement(LayoutComponent, { params: siblingThenableParams }, siblingInterceptElement);
99
+ }
100
+ }
89
101
  return buildAppPageElements({
90
- element: PageComponent ? createElement(PageComponent, pageProps) : null,
102
+ element: isSiblingIntercept ? siblingInterceptElement : EffectivePageComponent ? createElement(EffectivePageComponent, pageProps) : null,
91
103
  globalErrorModule: globalErrorModule ?? DEFAULT_GLOBAL_ERROR_MODULE,
92
104
  isRscRequest,
93
105
  layoutParamAccess: options.layoutParamAccess,
@@ -125,7 +137,7 @@ async function buildPageElements(options) {
125
137
  */
126
138
  function buildSlotOverrides(route, routeParams, routePath, opts) {
127
139
  const overrides = {};
128
- if (opts && opts.interceptSlotKey && opts.interceptPage) overrides[opts.interceptSlotKey] = {
140
+ if (opts && opts.interceptSlotKey && opts.interceptPage && opts.interceptSlotKey !== "__vinext_page_intercept") overrides[opts.interceptSlotKey] = {
129
141
  layoutModules: opts.interceptLayouts || null,
130
142
  pageModule: opts.interceptPage,
131
143
  params: opts.interceptParams || routeParams
@@ -28,6 +28,12 @@ type RenderAppPageLifecycleOptions = {
28
28
  * Undefined or empty disables emission.
29
29
  */
30
30
  clientTraceMetadata?: readonly string[];
31
+ /**
32
+ * Maximum total length (in characters) of the preload `Link` header emitted
33
+ * during SSR. `0` disables emission. From `reactMaxHeadersLength` in
34
+ * `next.config`.
35
+ */
36
+ reactMaxHeadersLength?: number;
31
37
  cleanPathname: string;
32
38
  clearRequestContext: () => void;
33
39
  consumeDynamicUsage: () => boolean;
@@ -10,7 +10,7 @@ import { DEFAULT_CACHE_VARIANT_BUDGET, buildCacheVariantWithRouteBudget, buildRe
10
10
  import { createAppPageHtmlOutputScope, createAppPageRenderObservation, createAppPageRscOutputScope, createEmptyAppPageRenderObservationState } from "./app-page-render-observation.js";
11
11
  import { finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse } from "./app-page-cache.js";
12
12
  import { createStaticLayoutClientReuseArtifactCompatibility, createStaticLayoutClientReusePayloadHash, createStaticLayoutClientReuseRouteId } from "./static-layout-client-reuse-proof.js";
13
- import { createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery, shouldRerenderAppPageWithGlobalError } from "./app-page-stream.js";
13
+ import { buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery, shouldRerenderAppPageWithGlobalError } from "./app-page-stream.js";
14
14
  import { getStaticLayoutObservationSkipRejection } from "./app-layout-param-observation.js";
15
15
  import { hasDigest } from "./app-rsc-errors.js";
16
16
  import { createClientReuseSkipTransportPlan, crossCheckClientReuseManifestEntryWithCache } from "./skip-cache-proof.js";
@@ -418,6 +418,7 @@ async function renderAppPageLifecycle(options) {
418
418
  navigationContext: options.getNavigationContext(),
419
419
  basePath: options.basePath,
420
420
  clientTraceMetadata: options.clientTraceMetadata,
421
+ reactMaxHeadersLength: options.reactMaxHeadersLength,
421
422
  rootParams: options.rootParams,
422
423
  formState: options.formState ?? null,
423
424
  rscStream: rscForResponse,
@@ -435,6 +436,7 @@ async function renderAppPageLifecycle(options) {
435
436
  if (htmlRender.response) return htmlRender.response;
436
437
  let htmlStream = htmlRender.htmlStream;
437
438
  if (!htmlStream) throw new Error("[vinext] Expected an HTML stream when no fallback response was returned");
439
+ const linkHeader = buildAppPageLinkHeader(htmlRender.linkHeader, fontLinkHeader, options.reactMaxHeadersLength);
438
440
  if (options.isPrerender === true) await htmlRender.metadataReady;
439
441
  if (options.hasLoadingBoundary) {
440
442
  const captured = rscErrorTracker.getCapturedSpecialError();
@@ -491,7 +493,7 @@ async function renderAppPageLifecycle(options) {
491
493
  if (htmlResponsePolicy.shouldWriteToCache || shouldSpeculativelyWriteCache) {
492
494
  const isrResponse = buildAppPageHtmlResponse(safeHtmlStream, {
493
495
  draftCookie,
494
- fontLinkHeader,
496
+ linkHeader,
495
497
  isEdgeRuntime: options.isEdgeRuntime,
496
498
  middlewareContext: options.middlewareContext,
497
499
  policy: htmlResponsePolicy,
@@ -551,7 +553,7 @@ async function renderAppPageLifecycle(options) {
551
553
  }
552
554
  return buildAppPageHtmlResponse(safeHtmlStream, {
553
555
  draftCookie,
554
- fontLinkHeader,
556
+ linkHeader,
555
557
  isEdgeRuntime: options.isEdgeRuntime,
556
558
  middlewareContext: options.middlewareContext,
557
559
  policy: htmlResponsePolicy,
@@ -6,6 +6,7 @@ type AppPageParams = Record<string, string | string[]>;
6
6
  type GenerateStaticParams = (args: {
7
7
  params: AppPageParams;
8
8
  }) => unknown;
9
+ type Awaitable<T> = T | Promise<T>;
9
10
  type GenerateStaticParamsModule = {
10
11
  generateStaticParams?: GenerateStaticParams | null;
11
12
  };
@@ -39,6 +40,7 @@ type BuildAppPageElementResult<TElement> = {
39
40
  type AppPageInterceptMatch<TPage = unknown> = {
40
41
  matchedParams: AppPageParams;
41
42
  page: TPage;
43
+ __pageLoader?: (() => Promise<TPage>) | null;
42
44
  slotId?: string | null;
43
45
  slotKey: string;
44
46
  sourceRouteIndex: number;
@@ -48,7 +50,7 @@ type ResolveAppPageInterceptMatchOptions<TRoute, TPage, TInterceptOpts> = {
48
50
  currentRoute: TRoute;
49
51
  findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
50
52
  getRouteParamNames: (route: TRoute) => readonly string[];
51
- getSourceRoute: (sourceRouteIndex: number) => TRoute | undefined;
53
+ getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
52
54
  isRscRequest: boolean;
53
55
  toInterceptOpts: (intercept: AppPageInterceptMatch<TPage>) => TInterceptOpts;
54
56
  };
@@ -64,7 +66,7 @@ type ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOp
64
66
  currentRoute: TRoute;
65
67
  findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
66
68
  getRouteParamNames: (route: TRoute) => readonly string[];
67
- getSourceRoute: (sourceRouteIndex: number) => TRoute | undefined;
69
+ getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
68
70
  isRscRequest: boolean;
69
71
  toInterceptOpts: (intercept: AppPageInterceptMatch<TPage>) => TInterceptOpts;
70
72
  };
@@ -82,7 +84,7 @@ type ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement> = {
82
84
  currentRoute: TRoute;
83
85
  findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
84
86
  getRouteParamNames: (route: TRoute) => readonly string[];
85
- getSourceRoute: (sourceRouteIndex: number) => TRoute | undefined;
87
+ getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
86
88
  isRscRequest: boolean;
87
89
  layoutParamAccess?: AppLayoutParamAccessTracker;
88
90
  renderInterceptResponse: (route: TRoute, element: TElement) => Promise<Response> | Response;
@@ -116,9 +118,9 @@ declare function validateAppPageDynamicParams(options: ValidateAppPageDynamicPar
116
118
  * `setNavigationContext` + element build + Response wrap) and the server-action
117
119
  * POST path (entries/app-rsc-entry.ts), which runs its own response pipeline.
118
120
  */
119
- declare function resolveAppPageInterceptMatch<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptMatchOptions<TRoute, TPage, TInterceptOpts>): ResolveAppPageInterceptMatchResult<TRoute, TInterceptOpts> | null;
120
- declare function resolveAppPageInterceptionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): ResolveAppPageInterceptionRerenderTargetResult<TRoute, TInterceptOpts>;
121
- declare function resolveAppPageActionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageActionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): ResolveAppPageActionRerenderTargetResult<TRoute, TInterceptOpts>;
121
+ declare function resolveAppPageInterceptMatch<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptMatchOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptMatchResult<TRoute, TInterceptOpts> | null>;
122
+ declare function resolveAppPageInterceptionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptionRerenderTargetResult<TRoute, TInterceptOpts>>;
123
+ declare function resolveAppPageActionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageActionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageActionRerenderTargetResult<TRoute, TInterceptOpts>>;
122
124
  declare function resolveAppPageIntercept<TRoute, TPage, TInterceptOpts, TElement>(options: ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement>): Promise<ResolveAppPageInterceptResult<TInterceptOpts>>;
123
125
  declare function buildAppPageElement<TElement>(options: BuildAppPageElementOptions<TElement>): Promise<BuildAppPageElementResult<TElement>>;
124
126
  //#endregion