@qzsy/vinext 0.1.12 → 0.1.122

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 (77) hide show
  1. package/dist/check.d.ts +8 -0
  2. package/dist/check.js +20 -9
  3. package/dist/cli.js +2 -2
  4. package/dist/deploy.js +10 -11
  5. package/dist/entries/app-rsc-entry.js +37 -8
  6. package/dist/entries/app-rsc-manifest.js +8 -0
  7. package/dist/entries/pages-client-entry.js +1 -0
  8. package/dist/entries/pages-server-entry.js +1 -0
  9. package/dist/index.js +12 -8
  10. package/dist/init.js +2 -1
  11. package/dist/plugins/middleware-server-only.d.ts +8 -6
  12. package/dist/plugins/middleware-server-only.js +8 -7
  13. package/dist/routing/app-route-graph.d.ts +6 -2
  14. package/dist/routing/app-route-graph.js +60 -12
  15. package/dist/routing/app-router.d.ts +5 -0
  16. package/dist/routing/app-router.js +5 -0
  17. package/dist/routing/file-matcher.d.ts +5 -0
  18. package/dist/routing/file-matcher.js +7 -1
  19. package/dist/server/app-browser-history-controller.d.ts +2 -1
  20. package/dist/server/app-browser-history-controller.js +6 -2
  21. package/dist/server/app-fallback-renderer.d.ts +1 -1
  22. package/dist/server/app-fallback-renderer.js +2 -1
  23. package/dist/server/app-page-boundary-render.d.ts +1 -0
  24. package/dist/server/app-page-boundary-render.js +12 -3
  25. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  26. package/dist/server/app-page-cache-finalizer.js +8 -2
  27. package/dist/server/app-page-dispatch.d.ts +11 -3
  28. package/dist/server/app-page-dispatch.js +54 -15
  29. package/dist/server/app-page-element-builder.d.ts +5 -1
  30. package/dist/server/app-page-element-builder.js +55 -19
  31. package/dist/server/app-page-head.d.ts +12 -0
  32. package/dist/server/app-page-head.js +42 -19
  33. package/dist/server/app-page-params.d.ts +2 -1
  34. package/dist/server/app-page-params.js +8 -1
  35. package/dist/server/app-page-probe.d.ts +1 -0
  36. package/dist/server/app-page-probe.js +1 -1
  37. package/dist/server/app-page-render.d.ts +4 -1
  38. package/dist/server/app-page-render.js +8 -3
  39. package/dist/server/app-page-request.d.ts +8 -1
  40. package/dist/server/app-page-request.js +23 -11
  41. package/dist/server/app-page-route-wiring.d.ts +6 -1
  42. package/dist/server/app-page-route-wiring.js +30 -8
  43. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  44. package/dist/server/app-page-search-params-observation.js +11 -7
  45. package/dist/server/app-route-handler-dispatch.js +1 -0
  46. package/dist/server/app-route-handler-execution.js +2 -1
  47. package/dist/server/app-route-module-loader.d.ts +2 -0
  48. package/dist/server/app-route-module-loader.js +1 -0
  49. package/dist/server/app-router-entry.js +7 -6
  50. package/dist/server/app-rsc-errors.js +7 -1
  51. package/dist/server/app-rsc-handler.js +4 -1
  52. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  53. package/dist/server/app-rsc-route-matching.js +36 -3
  54. package/dist/server/app-segment-config.d.ts +1 -0
  55. package/dist/server/app-segment-config.js +32 -2
  56. package/dist/server/app-server-action-execution.d.ts +4 -0
  57. package/dist/server/app-server-action-execution.js +41 -10
  58. package/dist/server/app-static-generation.d.ts +1 -0
  59. package/dist/server/app-static-generation.js +1 -0
  60. package/dist/server/headers.d.ts +3 -1
  61. package/dist/server/headers.js +3 -1
  62. package/dist/server/prod-server.js +15 -6
  63. package/dist/server/worker-utils.d.ts +2 -1
  64. package/dist/server/worker-utils.js +7 -1
  65. package/dist/shims/error-boundary.d.ts +19 -1
  66. package/dist/shims/error-boundary.js +11 -1
  67. package/dist/shims/headers.d.ts +3 -1
  68. package/dist/shims/headers.js +16 -5
  69. package/dist/shims/metadata.d.ts +3 -2
  70. package/dist/shims/metadata.js +8 -4
  71. package/dist/shims/router.js +13 -2
  72. package/dist/typegen.js +6 -5
  73. package/dist/utils/path.d.ts +2 -1
  74. package/dist/utils/path.js +1 -1
  75. package/dist/utils/project.d.ts +4 -0
  76. package/dist/utils/project.js +5 -1
  77. package/package.json +1 -1
@@ -360,8 +360,8 @@ async function buildAppRouteGraph(appDir, matcher) {
360
360
  const routePatterns = new Set(routes.map((route) => route.pattern));
361
361
  const ghostParentRoutes = [];
362
362
  for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
363
- const dir = path.dirname(file);
364
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
363
+ const dir = path.posix.dirname(file);
364
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
365
365
  if (!hasParallelSlotDirectory(routeDir)) continue;
366
366
  if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
367
367
  const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
@@ -434,11 +434,16 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
434
434
  const applySlotSubPages = (route, slotPages, rawSegments) => {
435
435
  route.parallelSlots = route.parallelSlots.map((slot) => {
436
436
  const subPage = slotPages.get(slot.key);
437
- if (subPage !== void 0) return {
438
- ...slot,
439
- pagePath: subPage,
440
- routeSegments: rawSegments
441
- };
437
+ if (subPage !== void 0) {
438
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
439
+ return {
440
+ ...slot,
441
+ pagePath: subPage,
442
+ configLayoutPaths,
443
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
444
+ routeSegments: rawSegments
445
+ };
446
+ }
442
447
  return slot;
443
448
  });
444
449
  };
@@ -495,9 +500,12 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
495
500
  if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
496
501
  const subSlots = parentRoute.parallelSlots.map((slot) => {
497
502
  const subPage = slotPages.get(slot.key);
503
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
498
504
  return {
499
505
  ...slot,
500
506
  pagePath: subPage || null,
507
+ configLayoutPaths,
508
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
501
509
  routeSegments: subPage ? rawSegments : null
502
510
  };
503
511
  });
@@ -574,6 +582,23 @@ function findSlotSubPages(slotDir, matcher) {
574
582
  perMatcher.set(slotDir, results);
575
583
  return results;
576
584
  }
585
+ function findSlotConfigLayoutPaths(slotDir, pagePath, matcher) {
586
+ if (!pagePath) return [];
587
+ const layouts = [];
588
+ let dir = path.dirname(pagePath);
589
+ while (dir !== slotDir && dir.startsWith(`${slotDir}${path.sep}`)) {
590
+ const layoutPath = findFile(dir, "layout", matcher);
591
+ if (layoutPath) layouts.unshift(layoutPath);
592
+ dir = path.dirname(dir);
593
+ }
594
+ return layouts;
595
+ }
596
+ function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
597
+ return layoutPaths.map((layoutPath) => {
598
+ const relativeDir = path.relative(slotDir, path.dirname(layoutPath));
599
+ return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
600
+ });
601
+ }
577
602
  /**
578
603
  * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
579
604
  * `[[...slug]]` directory that contains a `page` file. Returns the absolute
@@ -613,8 +638,14 @@ function fileToAppRoute(file, appDir, type, matcher) {
613
638
  }
614
639
  return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
615
640
  }
641
+ /**
642
+ * `dir` and `appDir` must both be forward-slash. `dir` is split on
643
+ * `path.posix.sep` and joined onto `appDir` with `path.posix.join`, and `appDir`
644
+ * is threaded to the layout/slot/boundary discovery below, which build paths the
645
+ * same way.
646
+ */
616
647
  function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
617
- const segments = dir === "." ? [] : dir.split(path.sep);
648
+ const segments = dir === "." ? [] : dir.split(path.posix.sep);
618
649
  const params = [];
619
650
  let isDynamic = false;
620
651
  const convertedRoute = convertSegmentsToRouteParts(segments);
@@ -631,7 +662,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
631
662
  const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
632
663
  const errorPaths = errorEntries.map((entry) => entry.path);
633
664
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
634
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
665
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
635
666
  const loadingPath = findFile(routeDir, "loading", matcher);
636
667
  const errorPath = findFile(routeDir, "error", matcher);
637
668
  const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
@@ -863,6 +894,10 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
863
894
  * Walk from appDir through each segment to the route's directory. At each level
864
895
  * that has @slot dirs, collect them. Slots at the route's own directory level
865
896
  * use page.tsx; slots at ancestor levels use default.tsx only.
897
+ *
898
+ * `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
899
+ * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
900
+ * below only matches when both share the canonical separator.
866
901
  */
867
902
  function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
868
903
  const slotMap = /* @__PURE__ */ new Map();
@@ -875,7 +910,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
875
910
  segmentIndex: 0
876
911
  });
877
912
  for (let i = 0; i < segments.length; i++) {
878
- currentDir = path.join(currentDir, segments[i]);
913
+ currentDir = path.posix.join(currentDir, segments[i]);
879
914
  if (findFile(currentDir, "layout", matcher)) layoutIdx++;
880
915
  dirsToCheck.push({
881
916
  dir: currentDir,
@@ -902,9 +937,12 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
902
937
  slotPatternParts = [...ownerUrl?.urlSegments ?? [], ...mirror.slotUrlSegments];
903
938
  slotParamNames = [...ownerUrl?.params ?? [], ...mirror.slotParamNames];
904
939
  }
940
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, mirror?.pagePath ?? null, matcher);
905
941
  const inheritedSlot = {
906
942
  ...slot,
907
943
  pagePath: mirror?.pagePath ?? null,
944
+ configLayoutPaths,
945
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
908
946
  layoutIndex: slotLayoutIdx,
909
947
  routeSegments: mirror?.segments ?? null,
910
948
  slotPatternParts,
@@ -1035,6 +1073,9 @@ function patternsStructurallyEquivalent(a, b) {
1035
1073
  *
1036
1074
  * Returns the absolute page path, or null if no root-level page is found.
1037
1075
  *
1076
+ * `slotDir` must be forward-slash: the `path.posix.join` descent stays a
1077
+ * canonical id only when the base already is.
1078
+ *
1038
1079
  * Only descends into route-group directories (those whose name starts with `(`
1039
1080
  * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1040
1081
  * are not transparent and are therefore not searched.
@@ -1051,7 +1092,7 @@ function findSlotRootPage(slotDir, matcher) {
1051
1092
  for (const entry of entries) {
1052
1093
  if (!entry.isDirectory()) continue;
1053
1094
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1054
- const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1095
+ const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
1055
1096
  if (found) return found;
1056
1097
  }
1057
1098
  return null;
@@ -1068,13 +1109,14 @@ function discoverParallelSlots(dir, appDir, matcher) {
1068
1109
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
1069
1110
  if (entry.name === "@children") continue;
1070
1111
  const slotName = entry.name.slice(1);
1071
- const slotDir = path.join(dir, entry.name);
1112
+ const slotDir = path.posix.join(dir, entry.name);
1072
1113
  const pagePath = findSlotRootPage(slotDir, matcher);
1073
1114
  const defaultPath = findFile(slotDir, "default", matcher);
1074
1115
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1075
1116
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
1076
1117
  const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1077
1118
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1119
+ const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1078
1120
  slots.push({
1079
1121
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1080
1122
  key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
@@ -1085,6 +1127,8 @@ function discoverParallelSlots(dir, appDir, matcher) {
1085
1127
  pagePath,
1086
1128
  defaultPath,
1087
1129
  layoutPath: findFile(slotDir, "layout", matcher),
1130
+ configLayoutPaths,
1131
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
1088
1132
  loadingPath: findFile(slotDir, "loading", matcher),
1089
1133
  errorPath: findFile(slotDir, "error", matcher),
1090
1134
  interceptingRoutes,
@@ -1272,8 +1316,12 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1272
1316
  if (targetPattern) {
1273
1317
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1274
1318
  results.push({
1319
+ branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1275
1320
  convention,
1276
1321
  layoutPaths: [...layoutPaths],
1322
+ layoutSegments: layoutPaths.map((layoutPath) => {
1323
+ return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
1324
+ }),
1277
1325
  targetPattern: targetPattern.pattern,
1278
1326
  sourceMatchPattern,
1279
1327
  pagePath: page,
@@ -12,11 +12,16 @@ declare function invalidateAppRouteCache(): void;
12
12
  * TODO(#726): Layer 4 should consume this read model directly once the
13
13
  * navigation planner owns route graph facts.
14
14
  *
15
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
16
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
17
+ *
15
18
  * @internal
16
19
  */
17
20
  declare function appRouteGraph(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraph>;
18
21
  /**
19
22
  * Scan the app/ directory and return a list of routes.
23
+ *
24
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
20
25
  */
21
26
  declare function appRouter(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraphRoute[]>;
22
27
  /**
@@ -30,6 +30,9 @@ function invalidateAppRouteCache() {
30
30
  * TODO(#726): Layer 4 should consume this read model directly once the
31
31
  * navigation planner owns route graph facts.
32
32
  *
33
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
34
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
35
+ *
33
36
  * @internal
34
37
  */
35
38
  async function appRouteGraph(appDir, pageExtensions, matcher) {
@@ -44,6 +47,8 @@ async function appRouteGraph(appDir, pageExtensions, matcher) {
44
47
  }
45
48
  /**
46
49
  * Scan the app/ directory and return a list of routes.
50
+ *
51
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
47
52
  */
48
53
  async function appRouter(appDir, pageExtensions, matcher) {
49
54
  return (await appRouteGraph(appDir, pageExtensions, matcher)).routes;
@@ -54,6 +54,11 @@ declare function buildViteResolveExtensions(pageExtensions?: readonly string[] |
54
54
  declare function normalizeViteResolveExtensions(extensions: readonly string[]): string[];
55
55
  /**
56
56
  * Use function-form exclude for Node < 22.14 compatibility.
57
+ *
58
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
59
+ * separators on Windows, so each match is normalized — this is the entry point
60
+ * that lets downstream consumers treat the scanned paths as canonical
61
+ * forward-slash ids.
57
62
  */
58
63
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
59
64
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import { escapeRegExp } from "../utils/regex.js";
2
3
  import { existsSync } from "node:fs";
3
4
  import path from "node:path";
@@ -135,13 +136,18 @@ function normalizeViteResolveExtensions(extensions) {
135
136
  }
136
137
  /**
137
138
  * Use function-form exclude for Node < 22.14 compatibility.
139
+ *
140
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
141
+ * separators on Windows, so each match is normalized — this is the entry point
142
+ * that lets downstream consumers treat the scanned paths as canonical
143
+ * forward-slash ids.
138
144
  */
139
145
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
140
146
  const pattern = buildExtensionGlob(stem, extensions);
141
147
  for await (const file of glob(pattern, {
142
148
  cwd,
143
149
  ...exclude ? { exclude } : {}
144
- })) yield file;
150
+ })) yield normalizePathSeparators(file);
145
151
  }
146
152
  //#endregion
147
153
  export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -45,6 +45,7 @@ type CommitNavigationHistoryOptions = {
45
45
  targetHistoryIndex?: number | null;
46
46
  stageClientParams: () => void;
47
47
  };
48
+ declare function createCanonicalBrowserHistoryHref(href: string): string;
48
49
  /**
49
50
  * Owns App Router browser-history metadata and traversal bookkeeping behind a
50
51
  * typed seam: traversal index allocation/commit, push/replace/traverse/hash-only
@@ -101,4 +102,4 @@ declare class AppBrowserHistoryController {
101
102
  restoreHistorySnapshot(options: RestoreHistorySnapshotOptions): boolean;
102
103
  }
103
104
  //#endregion
104
- export { AppBrowserHistoryController, RestorableSnapshotCandidate };
105
+ export { AppBrowserHistoryController, RestorableSnapshotCandidate, createCanonicalBrowserHistoryHref };
@@ -1,5 +1,9 @@
1
1
  import { RestorableClientStateController, createHistoryStateWithNavigationMetadata, readHistoryStateBfcacheIds, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
2
2
  //#region src/server/app-browser-history-controller.ts
3
+ function createCanonicalBrowserHistoryHref(href) {
4
+ const url = new URL(href);
5
+ return `${url.pathname}${url.search}${url.hash}`;
6
+ }
3
7
  function stripVinextScrollState(state) {
4
8
  if (!state || typeof state !== "object") return state;
5
9
  const nextState = {};
@@ -166,7 +170,7 @@ var AppBrowserHistoryController = class {
166
170
  this.#replaceHistoryState(createHistoryStateWithNavigationMetadata(this.#readHistoryState(), {
167
171
  previousNextUrl: null,
168
172
  traversalIndex: this.#currentHistoryTraversalIndex
169
- }), this.#readCurrentHref());
173
+ }), createCanonicalBrowserHistoryHref(this.#readCurrentHref()));
170
174
  }
171
175
  /** History write performed on the first committed (hydrated) render. */
172
176
  writeHydratedHistoryMetadata(options) {
@@ -207,4 +211,4 @@ function areBfcacheIdMapsEqual(a, b) {
207
211
  return aEntries.every(([key, value]) => b[key] === value);
208
212
  }
209
213
  //#endregion
210
- export { AppBrowserHistoryController };
214
+ export { AppBrowserHistoryController, createCanonicalBrowserHistoryHref };
@@ -75,7 +75,7 @@ type AppFallbackRendererCallContext = {
75
75
  sourcePageSegments?: readonly string[] | null;
76
76
  };
77
77
  type AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {
78
- renderErrorBoundary: (route: AppPageBoundaryRoute<TModule> | null, error: unknown, isRscRequest: boolean, request: Request, matchedParams: AppPageParams | undefined, scriptNonce: string | undefined, middlewareContext: AppPageMiddlewareContext, callContext?: AppFallbackRendererCallContext) => Promise<Response | null>;
78
+ renderErrorBoundary: (route: AppPageBoundaryRoute<TModule> | null, error: unknown, isRscRequest: boolean, request: Request, matchedParams: AppPageParams | undefined, scriptNonce: string | undefined, middlewareContext: AppPageMiddlewareContext, callContext?: AppFallbackRendererCallContext, errorOrigin?: "rsc" | "ssr") => Promise<Response | null>;
79
79
  renderHttpAccessFallback: (route: AppPageBoundaryRoute<TModule> | null, statusCode: number, isRscRequest: boolean, request: Request, opts: {
80
80
  boundaryComponent?: AppPageComponent | null;
81
81
  boundaryModule?: TModule | null;
@@ -97,7 +97,7 @@ function createAppFallbackRenderer(options) {
97
97
  renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, callContext) {
98
98
  return this.renderHttpAccessFallback(route, 404, isRscRequest, request, { matchedParams }, scriptNonce, middlewareContext, callContext);
99
99
  },
100
- renderErrorBoundary(route, error, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, callContext) {
100
+ renderErrorBoundary(route, error, isRscRequest, request, matchedParams, scriptNonce, middlewareContext, callContext, errorOrigin = "rsc") {
101
101
  return renderAppPageErrorBoundary({
102
102
  applyFileBasedMetadata,
103
103
  basePath,
@@ -108,6 +108,7 @@ function createAppFallbackRenderer(options) {
108
108
  return buildRscOnErrorHandler(request, pathname, routePath);
109
109
  },
110
110
  error,
111
+ errorOrigin,
111
112
  getFontLinks: fontProviders.getFontLinks,
112
113
  getFontPreloads: fontProviders.getFontPreloads,
113
114
  getFontStyles: fontProviders.getFontStyles,
@@ -75,6 +75,7 @@ type RenderAppPageHttpAccessFallbackOptions<TModule extends AppPageModule = AppP
75
75
  } & AppPageBoundaryRenderCommonOptions<TModule>;
76
76
  type RenderAppPageErrorBoundaryOptions<TModule extends AppPageModule = AppPageModule> = {
77
77
  error: unknown;
78
+ errorOrigin?: "rsc" | "ssr";
78
79
  matchedParams?: AppPageParams | null;
79
80
  route?: AppPageBoundaryRoute<TModule> | null;
80
81
  sanitizeErrorForClient: (error: Error) => Error;
@@ -3,7 +3,7 @@ import { AppElementsWire } from "./app-elements-wire.js";
3
3
  import "./app-elements.js";
4
4
  import DefaultGlobalError from "../shims/default-global-error.js";
5
5
  import { isNavigationSignalError } from "../utils/navigation-signal.js";
6
- import { ErrorBoundary, GlobalErrorBoundary } from "../shims/error-boundary.js";
6
+ import { ErrorBoundary, GlobalErrorBoundary, SerializedErrorBoundary } from "../shims/error-boundary.js";
7
7
  import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
8
8
  import { MetadataHead, ViewportHead } from "../shims/metadata.js";
9
9
  import { resolveAppPageSpecialError } from "./app-page-execution.js";
@@ -215,7 +215,7 @@ async function renderAppPageErrorBoundary(options) {
215
215
  if (!errorBoundary.component) return null;
216
216
  const rawError = options.error instanceof Error ? options.error : new Error(String(options.error));
217
217
  rewriteClientHookError(rawError);
218
- const errorObject = options.sanitizeErrorForClient(rawError);
218
+ const errorObject = options.errorOrigin === "ssr" ? rawError : options.sanitizeErrorForClient(rawError);
219
219
  const matchedParams = options.matchedParams ?? options.route?.params ?? {};
220
220
  const layoutModules = options.route?.layouts ?? options.rootLayouts;
221
221
  const pathname = new URL(options.requestUrl).pathname;
@@ -249,7 +249,16 @@ async function renderAppPageErrorBoundary(options) {
249
249
  console.error(`[vinext] App page error boundary head resolution failed for ${options.route?.pattern ?? pathname}:`, error);
250
250
  }
251
251
  const buildElement = (BoundaryComponent) => {
252
- const boundaryElement = createElement(BoundaryComponent, { error: errorObject });
252
+ const serializedError = {
253
+ digest: "digest" in errorObject ? String(errorObject.digest) : void 0,
254
+ message: errorObject.message,
255
+ name: errorObject.name,
256
+ stack: process.env.NODE_ENV !== "production" ? errorObject.stack : void 0
257
+ };
258
+ const boundaryElement = errorBoundary.isGlobalError && BoundaryComponent !== DEFAULT_GLOBAL_ERROR_COMPONENT ? createElement(SerializedErrorBoundary, {
259
+ error: serializedError,
260
+ fallback: BoundaryComponent
261
+ }) : createElement(BoundaryComponent, { error: errorObject });
253
262
  return wrapRenderedBoundaryElement({
254
263
  element: createElement(Fragment, null, ...headElements, errorBoundary.isGlobalError ? createElement(GlobalErrorBoundary, {
255
264
  fallback: DEFAULT_GLOBAL_ERROR_COMPONENT,
@@ -30,6 +30,7 @@ type FinalizeAppPageHtmlCacheResponseOptions = {
30
30
  isrRscKey: AppPageRscCacheKeyBuilder;
31
31
  isrSet: AppPageCacheSetter;
32
32
  interceptionContext?: string | null;
33
+ omitPendingDynamicCacheState?: boolean;
33
34
  preserveClientResponseHeaders?: boolean;
34
35
  expireSeconds?: number;
35
36
  revalidateSeconds: number | null;
@@ -1,15 +1,21 @@
1
+ import { NEXTJS_CACHE_HEADER, VINEXT_CACHE_HEADER } from "./headers.js";
1
2
  import { setCacheStateHeaders } from "./cache-headers.js";
2
3
  import { applyCdnResponseHeaders } from "./cache-control.js";
3
4
  import { buildAppPageCacheValue } from "./isr-cache.js";
4
5
  import { readStreamAsText } from "../utils/text-stream.js";
5
6
  import { createEmptyAppPageRenderObservationState } from "./app-page-render-observation.js";
6
7
  //#region src/server/app-page-cache-finalizer.ts
7
- function applyPendingDynamicCdnHeaders(headers, tags) {
8
+ function applyPendingDynamicCdnHeaders(headers, tags, options = {}) {
8
9
  applyCdnResponseHeaders(headers, {
9
10
  cacheControl: headers.get("Cache-Control") ?? "",
10
11
  pendingDynamicCheck: true,
11
12
  tags
12
13
  });
14
+ if (options.omitCacheState === true) {
15
+ headers.delete(VINEXT_CACHE_HEADER);
16
+ headers.delete(NEXTJS_CACHE_HEADER);
17
+ return;
18
+ }
13
19
  setCacheStateHeaders(headers, "MISS");
14
20
  }
15
21
  function resolveAppPageCacheWritePolicy(options) {
@@ -30,7 +36,7 @@ function finalizeAppPageHtmlCacheResponse(response, options) {
30
36
  const htmlKey = options.isrHtmlKey(options.cleanPathname);
31
37
  const rscKey = options.isrRscKey(options.cleanPathname, null, void 0, options.interceptionContext);
32
38
  const clientHeaders = new Headers(response.headers);
33
- if (options.preserveClientResponseHeaders !== true) applyPendingDynamicCdnHeaders(clientHeaders, options.getPageTags());
39
+ if (options.preserveClientResponseHeaders !== true) applyPendingDynamicCdnHeaders(clientHeaders, options.getPageTags(), { omitCacheState: options.omitPendingDynamicCacheState === true });
34
40
  const cachePromise = (async () => {
35
41
  try {
36
42
  const cachedHtml = await readStreamAsText(streamForCache);
@@ -34,7 +34,10 @@ type AppPageBackgroundRegenerationErrorContext = {
34
34
  type AppPageBackgroundRegenerator = (key: string, renderFn: () => Promise<void>, errorContext?: AppPageBackgroundRegenerationErrorContext) => void;
35
35
  type AppPageDispatchIntercept<TPage = unknown> = {
36
36
  interceptLayouts?: readonly unknown[] | null;
37
+ interceptLayoutSegments?: readonly (readonly string[])[] | null;
38
+ interceptBranchSegments?: readonly string[] | null;
37
39
  matchedParams: AppPageParams;
40
+ sourceMatchedParams?: AppPageParams;
38
41
  page: TPage;
39
42
  slotId?: string | null;
40
43
  slotKey: string;
@@ -44,6 +47,8 @@ type AppPageDispatchIntercept<TPage = unknown> = {
44
47
  type AppPageDispatchInterceptOptions<TPage = unknown> = {
45
48
  interceptionContext: string | null;
46
49
  interceptLayouts?: readonly unknown[] | null;
50
+ interceptLayoutSegments?: readonly (readonly string[])[] | null;
51
+ interceptBranchSegments?: readonly string[] | null;
47
52
  interceptPage: TPage;
48
53
  interceptParams: AppPageParams;
49
54
  interceptSlotId?: string | null;
@@ -104,7 +109,10 @@ type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
104
109
  * `next.config`. Undefined falls back to the React default downstream.
105
110
  */
106
111
  reactMaxHeadersLength?: number;
107
- buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, layoutParamAccess?: AppLayoutParamAccessTracker) => Promise<AppPageElement>;
112
+ buildPageElement: (route: TRoute, params: AppPageParams, opts: AppPageDispatchInterceptOptions | undefined, searchParams: URLSearchParams, layoutParamAccess?: AppLayoutParamAccessTracker, options?: {
113
+ observeMetadataSearchParamsAccess?: boolean;
114
+ observePageSearchParamsAccess?: boolean;
115
+ }) => Promise<AppPageElement>;
108
116
  clientReuseManifest?: ClientReuseManifestParseResult;
109
117
  cleanPathname: string;
110
118
  displayPathname?: string;
@@ -170,9 +178,9 @@ type DispatchAppPageOptions<TRoute extends AppPageDispatchRoute> = {
170
178
  staticParamsValidationParams?: AppPageParams;
171
179
  rootParams?: RootParams;
172
180
  probeLayoutAt: (layoutIndex: number, layoutParamAccess?: AppLayoutParamAccessTracker) => unknown;
173
- probePage: () => unknown;
181
+ probePage: (searchParams?: URLSearchParams) => unknown;
174
182
  expireSeconds?: number;
175
- renderErrorBoundaryPage: (error: unknown) => Promise<Response | null>;
183
+ renderErrorBoundaryPage: (error: unknown, errorOrigin?: "rsc" | "ssr") => Promise<Response | null>;
176
184
  renderHttpAccessFallbackPage: (statusCode: number, opts: {
177
185
  boundaryComponent?: unknown;
178
186
  boundaryModule?: AppPageModule | null;
@@ -3,7 +3,7 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
3
3
  import { AppElementsWire } from "./app-elements-wire.js";
4
4
  import { shouldSuppressLoadingBoundaries } from "./app-rsc-render-mode.js";
5
5
  import "./app-elements.js";
6
- import { consumeDynamicUsage, consumeInvalidDynamicUsageError, getAndClearPendingCookies, getDraftModeCookieHeader, isDraftModeRequest, markDynamicUsage, peekRenderRequestApiUsage, setHeadersContext } from "../shims/headers.js";
6
+ import { consumeDynamicUsage, consumeInvalidDynamicUsageError, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersContext, isDraftModeRequest, markDynamicUsage, peekDynamicUsage, peekRenderRequestApiUsage, setHeadersContext } from "../shims/headers.js";
7
7
  import { _consumeRequestScopedCacheLife, _peekRequestScopedCacheLife } from "../shims/cache-request-state.js";
8
8
  import { ensureFetchPatch, getCollectedFetchTags, peekDynamicFetchObservations, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault } from "../shims/fetch-cache.js";
9
9
  import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader } from "./app-rsc-cache-busting.js";
@@ -107,6 +107,7 @@ async function runAppPageRevalidationContext(options, renderFn) {
107
107
  const { createStaticGenerationHeadersContext } = await import("./app-static-generation.js");
108
108
  return runWithRequestContext(createRequestContext({
109
109
  headersContext: createStaticGenerationHeadersContext({
110
+ draftModeEnabled: false,
110
111
  draftModeSecret: options.draftModeSecret,
111
112
  dynamicConfig: options.dynamicConfig,
112
113
  routeKind: "page",
@@ -131,6 +132,8 @@ function toInterceptOptions(interceptionContext, intercept) {
131
132
  return {
132
133
  interceptionContext,
133
134
  interceptLayouts: intercept.interceptLayouts,
135
+ interceptLayoutSegments: intercept.interceptLayoutSegments,
136
+ interceptBranchSegments: intercept.interceptBranchSegments,
134
137
  interceptPage: intercept.page,
135
138
  interceptParams: intercept.matchedParams,
136
139
  interceptSlotId: intercept.slotId ?? null,
@@ -152,7 +155,11 @@ async function dispatchAppPageInner(options) {
152
155
  const isDynamicError = dynamicConfig === "error";
153
156
  const isForceDynamic = dynamicConfig === "force-dynamic";
154
157
  const isDraftMode = isDraftModeRequest(options.request, options.draftModeSecret);
158
+ const requestHeadersContext = getHeadersContext();
159
+ const hasRequestSearchParams = !isForceStatic && hasSearchParams(options.searchParams);
160
+ const pageSearchParams = isForceStatic ? new URLSearchParams() : options.searchParams;
155
161
  const layoutParamAccess = createAppLayoutParamAccessTracker();
162
+ const hasActiveLoadingBoundary = shouldSuppressLoadingBoundaries(options.renderMode ?? "navigation") ? false : Boolean(route.loading?.default);
156
163
  setCurrentFetchSoftTags(buildAppPageTags(options.cleanPathname, [], route.routeSegments));
157
164
  setCurrentFetchCacheMode(options.fetchCache ?? null);
158
165
  setCurrentForceDynamicFetchDefault(isForceDynamic);
@@ -172,9 +179,10 @@ async function dispatchAppPageInner(options) {
172
179
  options.clearRequestContext();
173
180
  return methodResponse;
174
181
  }
175
- if ((isForceStatic || isDynamicError) && !isDraftMode) {
182
+ if (isForceStatic || isDynamicError) {
176
183
  const { createStaticGenerationHeadersContext } = await import("./app-static-generation.js");
177
184
  setHeadersContext(createStaticGenerationHeadersContext({
185
+ draftModeEnabled: isDraftMode,
178
186
  draftModeSecret: options.draftModeSecret,
179
187
  dynamicConfig,
180
188
  routeKind: "page",
@@ -200,7 +208,7 @@ async function dispatchAppPageInner(options) {
200
208
  const cachedPageResponse = await readAppPageCacheResponse({
201
209
  cleanPathname: options.cleanPathname,
202
210
  clearRequestContext: options.clearRequestContext,
203
- hasRequestSearchParams: !isForceStatic && hasSearchParams(options.searchParams),
211
+ hasRequestSearchParams,
204
212
  isEdgeRuntime: options.isEdgeRuntime,
205
213
  isRscRequest: options.isRscRequest,
206
214
  isrDebug: options.isrDebug,
@@ -234,19 +242,23 @@ async function dispatchAppPageInner(options) {
234
242
  });
235
243
  revalidationTarget.navigationParams = resolveAppPageNavigationParams(revalidationTarget.route, revalidationTarget.navigationParams, options.cleanPathname, revalidationTarget.interceptOpts);
236
244
  await options.ensureRouteLoaded?.(revalidationTarget.route);
245
+ const revalidationDynamicConfig = options.resolveRouteDynamicConfig?.(revalidationTarget.route) ?? (revalidationTarget.route === route ? dynamicConfig : void 0);
237
246
  return runAppPageRevalidationContext({
238
247
  cleanPathname: options.cleanPathname,
239
248
  displayPathname: options.displayPathname,
240
249
  currentFetchCacheMode: options.resolveRouteFetchCacheMode?.(revalidationTarget.route) ?? (revalidationTarget.route === route ? options.fetchCache ?? null : null),
241
250
  draftModeSecret: options.draftModeSecret,
242
- dynamicConfig: options.resolveRouteDynamicConfig?.(revalidationTarget.route) ?? (revalidationTarget.route === route ? dynamicConfig : void 0),
251
+ dynamicConfig: revalidationDynamicConfig,
243
252
  params: revalidationTarget.navigationParams,
244
253
  routePattern: revalidationTarget.route.pattern,
245
254
  routeSegments: revalidationTarget.route.routeSegments,
246
255
  setNavigationContext: options.setNavigationContext
247
256
  }, async () => {
248
257
  const { renderAppPageCacheArtifacts } = await import("./app-page-cache-render.js");
249
- const revalidatedElement = await options.buildPageElement(revalidationTarget.route, revalidationTarget.params, revalidationTarget.interceptOpts, new URLSearchParams());
258
+ const revalidatedElement = await options.buildPageElement(revalidationTarget.route, revalidationTarget.params, revalidationTarget.interceptOpts, new URLSearchParams(), void 0, {
259
+ observeMetadataSearchParamsAccess: revalidationDynamicConfig !== "force-static",
260
+ observePageSearchParamsAccess: revalidationDynamicConfig !== "force-static"
261
+ });
250
262
  const revalidatedOnError = options.createRscOnErrorHandler(options.cleanPathname, revalidationTarget.route.pattern);
251
263
  const rendered = await renderAppPageCacheArtifacts({
252
264
  basePath: options.basePath,
@@ -301,12 +313,27 @@ async function dispatchAppPageInner(options) {
301
313
  }
302
314
  const fallbackShellResponse = options.pprRuntime ? await options.pprRuntime.tryServe(options, currentRevalidateSeconds, isDraftMode, isForceStatic, isForceDynamic) : null;
303
315
  if (fallbackShellResponse) return fallbackShellResponse;
316
+ let interceptDynamicConfig;
317
+ let interceptDynamicConfigResolved = false;
304
318
  const interceptResult = await resolveAppPageIntercept({
305
319
  async buildPageElement(interceptRoute, interceptParams, interceptOpts, interceptSearchParams, interceptLayoutParamAccess) {
306
- await options.ensureRouteLoaded?.(interceptRoute);
320
+ const sourceDynamicConfig = interceptDynamicConfigResolved ? interceptDynamicConfig : options.resolveRouteDynamicConfig?.(interceptRoute);
321
+ if (sourceDynamicConfig === "force-static" || sourceDynamicConfig === "error") {
322
+ const { createStaticGenerationHeadersContext } = await import("./app-static-generation.js");
323
+ setHeadersContext(createStaticGenerationHeadersContext({
324
+ draftModeEnabled: isDraftMode,
325
+ draftModeSecret: options.draftModeSecret,
326
+ dynamicConfig: sourceDynamicConfig,
327
+ routeKind: "page",
328
+ routePattern: interceptRoute.pattern
329
+ }));
330
+ } else setHeadersContext(requestHeadersContext);
307
331
  setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(interceptRoute) ?? null);
308
- setCurrentForceDynamicFetchDefault(options.resolveRouteDynamicConfig?.(interceptRoute) === "force-dynamic");
309
- return options.buildPageElement(interceptRoute, interceptParams, interceptOpts, interceptSearchParams, interceptLayoutParamAccess);
332
+ setCurrentForceDynamicFetchDefault(sourceDynamicConfig === "force-dynamic");
333
+ return options.buildPageElement(interceptRoute, interceptParams, interceptOpts, interceptSearchParams, interceptLayoutParamAccess, {
334
+ observeMetadataSearchParamsAccess: sourceDynamicConfig !== "force-static",
335
+ observePageSearchParamsAccess: sourceDynamicConfig !== "force-static"
336
+ });
310
337
  },
311
338
  cleanPathname: options.cleanPathname,
312
339
  currentRoute: route,
@@ -338,6 +365,12 @@ async function dispatchAppPageInner(options) {
338
365
  headers: interceptHeaders
339
366
  });
340
367
  },
368
+ async resolveSearchParams(sourceRoute, searchParams) {
369
+ await options.ensureRouteLoaded?.(sourceRoute);
370
+ interceptDynamicConfig = options.resolveRouteDynamicConfig?.(sourceRoute);
371
+ interceptDynamicConfigResolved = true;
372
+ return interceptDynamicConfig === "force-static" ? new URLSearchParams() : searchParams;
373
+ },
341
374
  searchParams: options.searchParams,
342
375
  setNavigationContext: options.setNavigationContext,
343
376
  toInterceptOpts(intercept) {
@@ -348,12 +381,15 @@ async function dispatchAppPageInner(options) {
348
381
  const buildCurrentPageElement = () => buildAppPageElement({
349
382
  buildPageElement() {
350
383
  if (options.actionFailed) throw options.actionError;
351
- return options.buildPageElement(route, options.params, interceptResult.interceptOpts, options.searchParams, layoutParamAccess);
384
+ return options.buildPageElement(route, options.params, interceptResult.interceptOpts, pageSearchParams, layoutParamAccess, {
385
+ observeMetadataSearchParamsAccess: !isForceStatic,
386
+ observePageSearchParamsAccess: !isForceStatic
387
+ });
352
388
  },
353
389
  async probePageSpecialError() {
354
390
  if (!shouldSuppressLoadingBoundaries(options.renderMode ?? "navigation") && route.loading?.default) return null;
355
391
  return resolveAppPageSpecialError(await probeAppPageThrownError({
356
- probePage: options.probePage,
392
+ probePage: () => options.probePage(pageSearchParams),
357
393
  runWithSuppressedHookWarning(probe) {
358
394
  return options.runWithSuppressedHookWarning(probe);
359
395
  }
@@ -384,7 +420,7 @@ async function dispatchAppPageInner(options) {
384
420
  const navigationParams = resolveAppPageNavigationParams(route, options.params, options.cleanPathname, interceptResult.interceptOpts);
385
421
  options.setNavigationContext({
386
422
  pathname: options.displayPathname ?? options.cleanPathname,
387
- searchParams: options.searchParams,
423
+ searchParams: pageSearchParams,
388
424
  params: navigationParams
389
425
  });
390
426
  const layoutClassifications = getEffectiveLayoutClassifications(route, options.debugClassification);
@@ -398,6 +434,7 @@ async function dispatchAppPageInner(options) {
398
434
  cleanPathname: options.cleanPathname,
399
435
  clearRequestContext: options.clearRequestContext,
400
436
  consumeDynamicUsage,
437
+ peekDynamicUsage,
401
438
  consumeInvalidDynamicUsageError,
402
439
  consumeRenderObservationState: consumeAppPageRenderObservationState,
403
440
  createRscOnErrorHandler(pathname, routePath) {
@@ -420,7 +457,8 @@ async function dispatchAppPageInner(options) {
420
457
  return _peekRequestScopedCacheLife();
421
458
  },
422
459
  handlerStart: options.handlerStart,
423
- hasLoadingBoundary: shouldSuppressLoadingBoundaries(options.renderMode ?? "navigation") ? false : Boolean(route.loading?.default),
460
+ hasLoadingBoundary: hasActiveLoadingBoundary,
461
+ omitPendingDynamicCacheState: !options.isRscRequest && hasRequestSearchParams,
424
462
  formState: options.formState ?? null,
425
463
  isProgressiveActionRender: options.isProgressiveActionRender === true,
426
464
  isDynamicError,
@@ -459,8 +497,9 @@ async function dispatchAppPageInner(options) {
459
497
  return options.probeLayoutAt(layoutIndex, layoutParamAccess);
460
498
  },
461
499
  probePage() {
462
- return options.probePage();
500
+ return options.probePage(pageSearchParams);
463
501
  },
502
+ probePageBeforeRender: options.isRscRequest,
464
503
  classification: {
465
504
  getLayoutId(index) {
466
505
  const treePosition = route.layoutTreePositions?.[index] ?? 0;
@@ -489,8 +528,8 @@ async function dispatchAppPageInner(options) {
489
528
  revalidateSeconds: currentRevalidateSeconds,
490
529
  mountedSlotsHeader: options.mountedSlotsHeader,
491
530
  renderMode: options.renderMode ?? "navigation",
492
- renderErrorBoundaryResponse(renderError) {
493
- return options.renderErrorBoundaryPage(renderError);
531
+ renderErrorBoundaryResponse(renderError, errorOrigin) {
532
+ return options.renderErrorBoundaryPage(renderError, errorOrigin);
494
533
  },
495
534
  renderLayoutSpecialError(specialError, layoutIndex) {
496
535
  return renderLayoutSpecialError(options, specialError, layoutIndex);