vinext 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -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,9 +84,10 @@ 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;
90
+ resolveNavigationParams: (route: TRoute, params: AppPageParams, pathname: string, interceptOpts: TInterceptOpts) => AppPageParams;
88
91
  renderInterceptResponse: (route: TRoute, element: TElement) => Promise<Response> | Response;
89
92
  searchParams: URLSearchParams;
90
93
  setNavigationContext: (context: {
@@ -116,9 +119,9 @@ declare function validateAppPageDynamicParams(options: ValidateAppPageDynamicPar
116
119
  * `setNavigationContext` + element build + Response wrap) and the server-action
117
120
  * POST path (entries/app-rsc-entry.ts), which runs its own response pipeline.
118
121
  */
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>;
122
+ declare function resolveAppPageInterceptMatch<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptMatchOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptMatchResult<TRoute, TInterceptOpts> | null>;
123
+ declare function resolveAppPageInterceptionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptionRerenderTargetResult<TRoute, TInterceptOpts>>;
124
+ declare function resolveAppPageActionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageActionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageActionRerenderTargetResult<TRoute, TInterceptOpts>>;
122
125
  declare function resolveAppPageIntercept<TRoute, TPage, TInterceptOpts, TElement>(options: ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement>): Promise<ResolveAppPageInterceptResult<TInterceptOpts>>;
123
126
  declare function buildAppPageElement<TElement>(options: BuildAppPageElementOptions<TElement>): Promise<BuildAppPageElementResult<TElement>>;
124
127
  //#endregion
@@ -90,8 +90,8 @@ async function validateAppPageDynamicParams(options) {
90
90
  * `setNavigationContext` + element build + Response wrap) and the server-action
91
91
  * POST path (entries/app-rsc-entry.ts), which runs its own response pipeline.
92
92
  */
93
- function resolveAppPageInterceptMatch(options) {
94
- const interceptState = resolveAppPageInterceptState(options);
93
+ async function resolveAppPageInterceptMatch(options) {
94
+ const interceptState = await resolveAppPageInterceptState(options);
95
95
  if (interceptState.kind !== "source-route") return null;
96
96
  return {
97
97
  interceptOpts: options.toInterceptOpts(interceptState.intercept),
@@ -100,11 +100,12 @@ function resolveAppPageInterceptMatch(options) {
100
100
  sourceRoute: interceptState.sourceRoute
101
101
  };
102
102
  }
103
- function resolveAppPageInterceptState(options) {
103
+ async function resolveAppPageInterceptState(options) {
104
104
  if (!options.isRscRequest) return { kind: "none" };
105
105
  const intercept = options.findIntercept(options.cleanPathname);
106
106
  if (!intercept) return { kind: "none" };
107
- const sourceRoute = options.getSourceRoute(intercept.sourceRouteIndex);
107
+ if (intercept.__pageLoader && intercept.page == null) intercept.page = await intercept.__pageLoader();
108
+ const sourceRoute = await options.getSourceRoute(intercept.sourceRouteIndex);
108
109
  if (!sourceRoute) return { kind: "none" };
109
110
  if (sourceRoute === options.currentRoute) return {
110
111
  kind: "current-route",
@@ -116,8 +117,8 @@ function resolveAppPageInterceptState(options) {
116
117
  sourceRoute
117
118
  };
118
119
  }
119
- function resolveAppPageInterceptionRerenderTarget(options) {
120
- const interceptState = resolveAppPageInterceptState({
120
+ async function resolveAppPageInterceptionRerenderTarget(options) {
121
+ const interceptState = await resolveAppPageInterceptState({
121
122
  cleanPathname: options.cleanPathname,
122
123
  currentRoute: options.currentRoute,
123
124
  findIntercept: options.findIntercept,
@@ -143,7 +144,7 @@ function resolveAppPageActionRerenderTarget(options) {
143
144
  return resolveAppPageInterceptionRerenderTarget(options);
144
145
  }
145
146
  async function resolveAppPageIntercept(options) {
146
- const interceptState = resolveAppPageInterceptState({
147
+ const interceptState = await resolveAppPageInterceptState({
147
148
  cleanPathname: options.cleanPathname,
148
149
  currentRoute: options.currentRoute,
149
150
  findIntercept: options.findIntercept,
@@ -153,15 +154,18 @@ async function resolveAppPageIntercept(options) {
153
154
  toInterceptOpts: options.toInterceptOpts
154
155
  });
155
156
  if (interceptState.kind === "source-route") {
157
+ const renderRoute = interceptState.sourceRoute;
158
+ const interceptOpts = options.toInterceptOpts(interceptState.intercept);
159
+ const renderParams = pickRouteParams(interceptState.intercept.matchedParams, options.getRouteParamNames(interceptState.sourceRoute));
156
160
  options.setNavigationContext({
157
- params: interceptState.intercept.matchedParams,
161
+ params: options.resolveNavigationParams(renderRoute, interceptState.intercept.matchedParams, options.cleanPathname, interceptOpts),
158
162
  pathname: options.cleanPathname,
159
163
  searchParams: options.searchParams
160
164
  });
161
- const interceptElement = await options.buildPageElement(interceptState.sourceRoute, pickRouteParams(interceptState.intercept.matchedParams, options.getRouteParamNames(interceptState.sourceRoute)), options.toInterceptOpts(interceptState.intercept), options.searchParams, options.layoutParamAccess);
165
+ const interceptElement = await options.buildPageElement(renderRoute, renderParams, interceptOpts, options.searchParams, options.layoutParamAccess);
162
166
  return {
163
167
  interceptOpts: void 0,
164
- response: await options.renderInterceptResponse(interceptState.sourceRoute, interceptElement)
168
+ response: await options.renderInterceptResponse(renderRoute, interceptElement)
165
169
  };
166
170
  }
167
171
  return {
@@ -45,8 +45,8 @@ type BuildAppPageRscResponseOptions = {
45
45
  timing?: AppPageResponseTiming;
46
46
  };
47
47
  type BuildAppPageHtmlResponseOptions = {
48
- draftCookie?: string | null;
49
- fontLinkHeader?: string;
48
+ draftCookie?: string | null; /** Combined preload `Link` header value (React hints + font preloads), already capped. */
49
+ linkHeader?: string;
50
50
  isEdgeRuntime?: boolean;
51
51
  middlewareContext: AppPageMiddlewareContext;
52
52
  policy: AppPageResponsePolicy;
@@ -1,7 +1,7 @@
1
1
  import { VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_PARAMS_HEADER, VINEXT_TIMING_HEADER } from "./headers.js";
2
2
  import { setCacheStateHeaders } from "./cache-headers.js";
3
- import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader } from "./app-rsc-cache-busting.js";
4
3
  import { NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildRevalidateCacheControl } from "./cache-control.js";
4
+ import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader } from "./app-rsc-cache-busting.js";
5
5
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
6
6
  //#region src/server/app-page-response.ts
7
7
  function applyTimingHeader(headers, timing) {
@@ -108,7 +108,7 @@ function buildAppPageHtmlResponse(body, options) {
108
108
  if (options.policy.cacheControl) headers.set("Cache-Control", options.policy.cacheControl);
109
109
  if (options.policy.cacheState) setCacheStateHeaders(headers, options.policy.cacheState);
110
110
  if (options.draftCookie) headers.append("Set-Cookie", options.draftCookie);
111
- if (options.fontLinkHeader) headers.set("Link", options.fontLinkHeader);
111
+ if (options.linkHeader) headers.set("Link", options.linkHeader);
112
112
  mergeMiddlewareResponseHeaders(headers, options.middlewareContext.headers);
113
113
  applyTimingHeader(headers, options.timing);
114
114
  return new Response(body, {
@@ -113,6 +113,7 @@ type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModu
113
113
  resolvedMetadata: Metadata | null;
114
114
  resolvedMetadataPathname?: string;
115
115
  resolvedViewport: Viewport;
116
+ trailingSlash?: boolean;
116
117
  rootForbiddenModule?: TModule | null;
117
118
  rootNotFoundModule?: TModule | null;
118
119
  rootUnauthorizedModule?: TModule | null;
@@ -142,6 +143,7 @@ declare function createAppPageLayoutEntries<TModule extends AppPageModule, TErro
142
143
  forbiddens?: readonly (TModule | null | undefined)[] | null;
143
144
  unauthorizeds?: readonly (TModule | null | undefined)[] | null;
144
145
  }): AppPageLayoutEntry<TModule, TErrorModule>[];
146
+ declare function createAppPageRouteBodyMetadata(metadata: Metadata | null, pathname: string, metadataPlacement: "body" | "head", trailingSlash?: boolean): ReactNode;
145
147
  declare function buildAppPageElements<TModule extends AppPageModule, TErrorModule extends AppPageErrorModule>(options: BuildAppPageElementsOptions<TModule, TErrorModule>): AppElements;
146
148
  //#endregion
147
- export { AppPageErrorModule, AppPageModule, AppPageRouteWiringRoute, AppPageSlotOverride, buildAppPageElements, createAppPageLayoutEntries, createAppPageTreePath, probeAppPageLayoutWithTracking, resolveAppPageChildSegments };
149
+ export { AppPageErrorModule, AppPageModule, AppPageRouteWiringRoute, AppPageSlotOverride, buildAppPageElements, createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageTreePath, probeAppPageLayoutWithTracking, resolveAppPageChildSegments };
@@ -139,21 +139,22 @@ function createAppPageSlotBindings(route, layoutEntries, resolveSlotOverride, op
139
139
  }
140
140
  return normalizeAppElementsSlotBindings(bindings, { layoutIds: layoutEntries.map((entry) => entry.id) });
141
141
  }
142
- function createAppPageRouteHead(metadata, viewport, pathname, metadataPlacement) {
142
+ function createAppPageRouteHead(metadata, viewport, pathname, metadataPlacement, trailingSlash) {
143
143
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
144
144
  /* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
145
145
  metadata && metadataPlacement === "head" ? /* @__PURE__ */ jsx(MetadataHead, {
146
146
  metadata,
147
- pathname
147
+ pathname,
148
+ trailingSlash
148
149
  }) : null,
149
150
  /* @__PURE__ */ jsx(ViewportHead, { viewport })
150
151
  ] });
151
152
  }
152
- function createAppPageRouteBodyMetadata(metadata, pathname, metadataPlacement) {
153
+ function createAppPageRouteBodyMetadata(metadata, pathname, metadataPlacement, trailingSlash) {
153
154
  if (!metadata || metadataPlacement !== "body") return null;
154
155
  return /* @__PURE__ */ jsx("div", {
155
156
  hidden: true,
156
- dangerouslySetInnerHTML: { __html: renderMetadataToHtml(metadata, pathname) }
157
+ dangerouslySetInnerHTML: { __html: renderMetadataToHtml(metadata, pathname, { trailingSlash }) }
157
158
  });
158
159
  }
159
160
  function buildAppPageElements(options) {
@@ -329,7 +330,7 @@ function buildAppPageElements(options) {
329
330
  }
330
331
  let routeChildren = /* @__PURE__ */ jsx(LayoutSegmentProvider, {
331
332
  segmentMap: { children: [] },
332
- children: /* @__PURE__ */ jsx(AppRouterScrollTarget, { children: /* @__PURE__ */ jsx(Slot, { id: pageId }) })
333
+ children: /* @__PURE__ */ jsx(Slot, { id: pageId })
333
334
  });
334
335
  if (isPrefetchLoadingShell) if (routeLoadingComponent === null) routeChildren = null;
335
336
  else routeChildren = /* @__PURE__ */ jsx(routeLoadingComponent, {});
@@ -339,6 +340,7 @@ function buildAppPageElements(options) {
339
340
  fallback: /* @__PURE__ */ jsx(routeLoadingComponent, {}),
340
341
  children: routeChildren
341
342
  }, routeResetKey);
343
+ routeChildren = /* @__PURE__ */ jsx(AppRouterScrollTarget, { children: routeChildren });
342
344
  }
343
345
  const lastLayoutErrorModule = errorEntries.length > 0 ? errorEntries[errorEntries.length - 1].errorModule : null;
344
346
  const notFoundComponent = getDefaultExport(options.route.notFound) ?? getDefaultExport(options.rootNotFoundModule);
@@ -430,12 +432,12 @@ function buildAppPageElements(options) {
430
432
  children: routeChildren
431
433
  });
432
434
  elements[routeId] = /* @__PURE__ */ jsxs(Fragment$1, { children: [
433
- createAppPageRouteHead(options.resolvedMetadata, options.resolvedViewport, options.resolvedMetadataPathname ?? options.routePath, metadataPlacement),
435
+ createAppPageRouteHead(options.resolvedMetadata, options.resolvedViewport, options.resolvedMetadataPathname ?? options.routePath, metadataPlacement, options.trailingSlash),
434
436
  routeChildren,
435
- createAppPageRouteBodyMetadata(options.resolvedMetadata, options.resolvedMetadataPathname ?? options.routePath, metadataPlacement)
437
+ createAppPageRouteBodyMetadata(options.resolvedMetadata, options.resolvedMetadataPathname ?? options.routePath, metadataPlacement, options.trailingSlash)
436
438
  ] });
437
439
  registerAppElementRenderDependencies(elements, renderDependenciesByElementId);
438
440
  return elements;
439
441
  }
440
442
  //#endregion
441
- export { buildAppPageElements, createAppPageLayoutEntries, createAppPageTreePath, probeAppPageLayoutWithTracking, resolveAppPageChildSegments };
443
+ export { buildAppPageElements, createAppPageLayoutEntries, createAppPageRouteBodyMetadata, createAppPageTreePath, probeAppPageLayoutWithTracking, resolveAppPageChildSegments };
@@ -18,8 +18,30 @@ type AppSsrRenderResult = {
18
18
  htmlStream: ReadableStream<Uint8Array>;
19
19
  metadataReady: Promise<void>;
20
20
  capturedRscData: Promise<ArrayBuffer> | null;
21
+ /**
22
+ * Preload `Link` header value emitted by React during SSR (via `onHeaders`),
23
+ * already capped to `reactMaxHeadersLength`. Empty/undefined when React
24
+ * emitted no preload headers (or emission was disabled with `0`).
25
+ */
26
+ linkHeader?: string;
21
27
  };
22
28
  declare function isAppSsrRenderResult(value: unknown): value is AppSsrRenderResult;
29
+ /**
30
+ * Combine the React-emitted preload `Link` header with vinext's font preload
31
+ * `Link` header, capping the result to `reactMaxHeadersLength`.
32
+ *
33
+ * React already caps its own portion, but vinext emits font preloads through a
34
+ * separate channel. Mirroring Next.js — where every preload flows through a
35
+ * single capped `onHeaders` callback — we cap the *combined* header here,
36
+ * keeping only whole entries that fit and dropping the rest once the limit is
37
+ * exceeded. `0` disables emission entirely (matches React); `undefined` falls
38
+ * back to the React default of 6000.
39
+ *
40
+ * React's hints (scripts/modules/styles) come first so that under a tight cap
41
+ * the render-critical entries survive and trailing font preloads are dropped
42
+ * first.
43
+ */
44
+ declare function buildAppPageLinkHeader(reactLinkHeader: string | undefined, fontLinkHeader: string | undefined, maxHeadersLength: number | undefined): string;
23
45
  type AppPageSsrHandler = {
24
46
  handleSsr: (rscStream: ReadableStream<Uint8Array>, navigationContext: NavigationContext | null, fontData: AppPageFontData, options?: {
25
47
  formState?: ReactFormState | null;
@@ -30,6 +52,12 @@ type AppPageSsrHandler = {
30
52
  * in the SSR head. Sourced from `experimental.clientTraceMetadata`.
31
53
  */
32
54
  clientTraceMetadata?: readonly string[];
55
+ /**
56
+ * Maximum total length (in characters) of the preload `Link` header
57
+ * emitted during SSR. `0` disables emission. From `reactMaxHeadersLength`
58
+ * in `next.config`.
59
+ */
60
+ reactMaxHeadersLength?: number;
33
61
  rootParams?: RootParams;
34
62
  sideStream?: ReadableStream<Uint8Array>;
35
63
  capturedRscDataRef?: {
@@ -52,6 +80,12 @@ type RenderAppPageHtmlStreamOptions = {
52
80
  * the SSR head. Undefined or empty disables emission.
53
81
  */
54
82
  clientTraceMetadata?: readonly string[];
83
+ /**
84
+ * Maximum total length (in characters) of the preload `Link` header emitted
85
+ * during SSR. `0` disables emission. From `reactMaxHeadersLength` in
86
+ * `next.config`.
87
+ */
88
+ reactMaxHeadersLength?: number;
55
89
  rootParams?: RootParams;
56
90
  ssrHandler: AppPageSsrHandler;
57
91
  /** Pre-split side stream for fused embed+capture (#981). When set,
@@ -74,7 +108,8 @@ type AppPageHtmlStreamRecoveryResult = {
74
108
  htmlStream: ReadableStream<Uint8Array> | null;
75
109
  response: Response | null;
76
110
  metadataReady: Promise<void>;
77
- capturedRscData: Promise<ArrayBuffer> | null;
111
+ capturedRscData: Promise<ArrayBuffer> | null; /** React-emitted preload `Link` header (already capped). */
112
+ linkHeader?: string;
78
113
  };
79
114
  type RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError> = {
80
115
  onShellRendered?: () => void;
@@ -94,10 +129,6 @@ type AppPageRscErrorTracker = {
94
129
  getCapturedSpecialError: () => unknown;
95
130
  onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;
96
131
  };
97
- type ShouldRerenderAppPageWithGlobalErrorOptions = {
98
- capturedError: unknown;
99
- hasLocalBoundary: boolean;
100
- };
101
132
  declare function createAppPageFontData(options: CreateAppPageFontDataOptions): AppPageFontData;
102
133
  declare function renderAppPageHtmlStream(options: RenderAppPageHtmlStreamOptions): Promise<AppSsrRenderResult>;
103
134
  /**
@@ -111,6 +142,5 @@ declare function deferUntilStreamConsumed(stream: ReadableStream<Uint8Array>, on
111
142
  declare function renderAppPageHtmlResponse(options: RenderAppPageHtmlResponseOptions): Promise<Response>;
112
143
  declare function renderAppPageHtmlStreamWithRecovery<TSpecialError>(options: RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError>): Promise<AppPageHtmlStreamRecoveryResult>;
113
144
  declare function createAppPageRscErrorTracker(baseOnError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown): AppPageRscErrorTracker;
114
- declare function shouldRerenderAppPageWithGlobalError(options: ShouldRerenderAppPageWithGlobalErrorOptions): boolean;
115
145
  //#endregion
116
- export { AppPageFontData, AppPageSsrHandler, AppSsrRenderResult, createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, isAppSsrRenderResult, renderAppPageHtmlResponse, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery, shouldRerenderAppPageWithGlobalError };
146
+ export { AppPageFontData, AppPageSsrHandler, AppSsrRenderResult, buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, isAppSsrRenderResult, renderAppPageHtmlResponse, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery };
@@ -14,6 +14,37 @@ function normalizeAppSsrRenderResult(raw, fallbackCapturedRscData = null) {
14
14
  capturedRscData: fallbackCapturedRscData
15
15
  };
16
16
  }
17
+ /**
18
+ * Combine the React-emitted preload `Link` header with vinext's font preload
19
+ * `Link` header, capping the result to `reactMaxHeadersLength`.
20
+ *
21
+ * React already caps its own portion, but vinext emits font preloads through a
22
+ * separate channel. Mirroring Next.js — where every preload flows through a
23
+ * single capped `onHeaders` callback — we cap the *combined* header here,
24
+ * keeping only whole entries that fit and dropping the rest once the limit is
25
+ * exceeded. `0` disables emission entirely (matches React); `undefined` falls
26
+ * back to the React default of 6000.
27
+ *
28
+ * React's hints (scripts/modules/styles) come first so that under a tight cap
29
+ * the render-critical entries survive and trailing font preloads are dropped
30
+ * first.
31
+ */
32
+ function buildAppPageLinkHeader(reactLinkHeader, fontLinkHeader, maxHeadersLength) {
33
+ const limit = typeof maxHeadersLength === "number" ? maxHeadersLength : 6e3;
34
+ if (limit <= 0) return "";
35
+ const entries = [];
36
+ for (const source of [reactLinkHeader, fontLinkHeader]) {
37
+ if (!source) continue;
38
+ for (const entry of source.split(", ")) if (entry.length > 0) entries.push(entry);
39
+ }
40
+ let header = "";
41
+ for (const entry of entries) {
42
+ const next = header.length === 0 ? entry : `${header}, ${entry}`;
43
+ if (next.length > limit) break;
44
+ header = next;
45
+ }
46
+ return header;
47
+ }
17
48
  function createAppPageFontData(options) {
18
49
  return {
19
50
  links: options.getLinks(),
@@ -27,6 +58,7 @@ async function renderAppPageHtmlStream(options) {
27
58
  scriptNonce: options.scriptNonce,
28
59
  basePath: options.basePath,
29
60
  clientTraceMetadata: options.clientTraceMetadata,
61
+ reactMaxHeadersLength: options.reactMaxHeadersLength,
30
62
  rootParams: options.rootParams,
31
63
  sideStream: options.sideStream,
32
64
  capturedRscDataRef: options.capturedRscDataRef,
@@ -89,13 +121,14 @@ async function renderAppPageHtmlResponse(options) {
89
121
  }
90
122
  async function renderAppPageHtmlStreamWithRecovery(options) {
91
123
  try {
92
- const { htmlStream, metadataReady, capturedRscData } = normalizeAppSsrRenderResult(await options.renderHtmlStream());
124
+ const { htmlStream, metadataReady, capturedRscData, linkHeader } = normalizeAppSsrRenderResult(await options.renderHtmlStream());
93
125
  options.onShellRendered?.();
94
126
  return {
95
127
  htmlStream,
96
128
  response: null,
97
129
  metadataReady,
98
- capturedRscData
130
+ capturedRscData,
131
+ linkHeader
99
132
  };
100
133
  } catch (error) {
101
134
  const specialError = options.resolveSpecialError(error);
@@ -133,8 +166,5 @@ function createAppPageRscErrorTracker(baseOnError) {
133
166
  }
134
167
  };
135
168
  }
136
- function shouldRerenderAppPageWithGlobalError(options) {
137
- return Boolean(options.capturedError) && !options.hasLocalBoundary;
138
- }
139
169
  //#endregion
140
- export { createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, isAppSsrRenderResult, renderAppPageHtmlResponse, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery, shouldRerenderAppPageWithGlobalError };
170
+ export { buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, deferUntilStreamConsumed, isAppSsrRenderResult, renderAppPageHtmlResponse, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery };
@@ -10,6 +10,22 @@ type RenderPagesFallbackDependencies = {
10
10
  buildRequestHeaders: (requestHeaders: Headers, middlewareRequestHeaders: Headers) => Headers | null;
11
11
  decodePathParams: (pathname: string) => string;
12
12
  applyRouteHandlerMiddlewareContext: (response: Response, middlewareContext: AppMiddlewareContext) => Response;
13
+ /**
14
+ * Returns the `__prerender_bypass` Set-Cookie header emitted by a
15
+ * `draftMode().enable()`/`disable()` call inside middleware, if any. Reading
16
+ * it clears it. Mirrors how App Router route handlers and page renders surface
17
+ * the middleware-enabled draft cookie so the same flow works when the request
18
+ * falls through to a Pages Router route.
19
+ *
20
+ * Note: this closes the draft-mode flow for production (Cloudflare Workers /
21
+ * Node), where middleware runs inline in the same RSC handler context that
22
+ * builds this fallback. In hybrid *dev*, middleware runs in a separate Vite
23
+ * Pages SSR runner and `draftMode()` inside middleware is not yet permitted
24
+ * there (it throws a scope error before any cookie is set), so this getter
25
+ * returns `null` and no cookie is appended. That dev limitation is pre-existing
26
+ * and tracked separately from #1520.
27
+ */
28
+ getDraftModeCookieHeader: () => string | null | undefined;
13
29
  };
14
30
  type RenderPagesFallbackOptions = {
15
31
  isRscRequest: boolean;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  async function renderPagesFallback(options, dependencies) {
6
6
  const { isRscRequest, middlewareContext, request, url } = options;
7
- const { loadPagesEntry, buildRequestHeaders, decodePathParams, applyRouteHandlerMiddlewareContext } = dependencies;
7
+ const { loadPagesEntry, buildRequestHeaders, decodePathParams, applyRouteHandlerMiddlewareContext, getDraftModeCookieHeader } = dependencies;
8
8
  if (isRscRequest) return null;
9
9
  const pagesEntry = await loadPagesEntry();
10
10
  const pagesRequestHeaders = middlewareContext.requestHeaders ? buildRequestHeaders(request.headers, middlewareContext.requestHeaders) : null;
@@ -24,11 +24,31 @@ async function renderPagesFallback(options, dependencies) {
24
24
  const pagesPathname = url.pathname;
25
25
  if (pagesPathname.startsWith("/api/") || pagesPathname === "/api") {
26
26
  if (typeof pagesEntry.handleApiRoute !== "function") return null;
27
- return applyRouteHandlerMiddlewareContext(await pagesEntry.handleApiRoute(pagesRequest, pagesUrl), middlewareContext);
27
+ const pagesApiResponse = await pagesEntry.handleApiRoute(pagesRequest, pagesUrl);
28
+ const draftCookie = getDraftModeCookieHeader();
29
+ return applyDraftModeCookie(applyRouteHandlerMiddlewareContext(pagesApiResponse, middlewareContext), draftCookie);
28
30
  }
29
31
  if (typeof pagesEntry.renderPage !== "function") return null;
30
32
  const pagesRes = await pagesEntry.renderPage(pagesRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders);
31
- return pagesRes.status !== 404 ? pagesRes : null;
33
+ if (pagesRes.status === 404) return null;
34
+ return applyDraftModeCookie(pagesRes, getDraftModeCookieHeader());
35
+ }
36
+ /**
37
+ * Append a middleware-emitted `__prerender_bypass` Set-Cookie header to a Pages
38
+ * Router fallback response. Returns the response unchanged when there is no
39
+ * draft cookie to add. App Router route handlers/page renders surface this same
40
+ * cookie via `finalizeRouteHandlerResponse`/the page response builder; this
41
+ * keeps draft-mode parity for requests that fall through to the Pages Router.
42
+ */
43
+ function applyDraftModeCookie(response, draftCookie) {
44
+ if (!draftCookie) return response;
45
+ const headers = new Headers(response.headers);
46
+ headers.append("Set-Cookie", draftCookie);
47
+ return new Response(response.body, {
48
+ status: response.status,
49
+ statusText: response.statusText,
50
+ headers
51
+ });
32
52
  }
33
53
  //#endregion
34
54
  export { renderPagesFallback };
@@ -18,6 +18,7 @@ type ReadAppRouteHandlerCacheOptions = {
18
18
  getCollectedFetchTags: () => string[];
19
19
  handlerFn: AppRouteHandlerFunction;
20
20
  i18n?: NextI18nConfig | null;
21
+ trailingSlash?: boolean;
21
22
  isAutoHead: boolean;
22
23
  isrDebug?: AppRouteDebugLogger;
23
24
  isrGet: RouteHandlerCacheGetter;
@@ -39,6 +39,7 @@ async function readAppRouteHandlerCacheResponse(options) {
39
39
  dynamicConfig: options.dynamicConfig,
40
40
  handlerFn: options.handlerFn,
41
41
  i18n: options.i18n,
42
+ trailingSlash: options.trailingSlash,
42
43
  markDynamicUsage: options.markDynamicUsage,
43
44
  params: options.params === null ? null : makeThenableParams(options.params),
44
45
  request: new Request(options.requestUrl, { method: "GET" }),
@@ -30,6 +30,7 @@ type DispatchAppRouteHandlerOptions = {
30
30
  isrGet: RouteHandlerCacheGetter;
31
31
  isrRouteKey: (pathname: string) => string;
32
32
  isrSet: RouteHandlerCacheSetter;
33
+ trailingSlash?: boolean;
33
34
  middlewareContext: RouteHandlerMiddlewareContext;
34
35
  middlewareRequestHeaders?: Headers | null;
35
36
  /**
@@ -77,6 +77,7 @@ async function dispatchAppRouteHandler(options) {
77
77
  getCollectedFetchTags,
78
78
  handlerFn: resolvedHandlerFn,
79
79
  i18n: options.i18n,
80
+ trailingSlash: options.trailingSlash,
80
81
  isAutoHead,
81
82
  isrDebug: options.isrDebug,
82
83
  isrGet: options.isrGet,
@@ -127,6 +128,7 @@ async function dispatchAppRouteHandler(options) {
127
128
  handler,
128
129
  handlerFn: resolvedHandlerFn,
129
130
  i18n: options.i18n,
131
+ trailingSlash: options.trailingSlash,
130
132
  isAutoHead,
131
133
  isProduction,
132
134
  isrDebug: options.isrDebug,
@@ -42,6 +42,7 @@ type RunAppRouteHandlerOptions = {
42
42
  dynamicConfig?: string;
43
43
  handlerFn: AppRouteHandlerFunction;
44
44
  i18n?: NextI18nConfig | null;
45
+ trailingSlash?: boolean;
45
46
  markDynamicUsage: MarkAppRouteDynamicUsageFn;
46
47
  middlewareRequestHeaders?: Headers | null;
47
48
  /**
@@ -21,6 +21,7 @@ async function runAppRouteHandler(options) {
21
21
  const trackedRequest = createTrackedAppRouteRequest(options.request, {
22
22
  basePath: options.basePath,
23
23
  i18n: options.i18n,
24
+ trailingSlash: options.trailingSlash,
24
25
  middlewareHeaders: options.middlewareRequestHeaders,
25
26
  onDynamicAccess() {
26
27
  options.markDynamicUsage();
@@ -1,7 +1,8 @@
1
1
  import "./headers.js";
2
2
  import { processMiddlewareHeaders } from "./request-pipeline.js";
3
3
  import { setCacheStateHeaders } from "./cache-headers.js";
4
- import { NEVER_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, buildCachedRevalidateCacheControl } from "./cache-control.js";
4
+ import { applyCdnResponseHeaders } from "./cache-control.js";
5
+ import { buildAppRouteMissIsrCacheControl, decideIsr } from "./isr-decision.js";
5
6
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
6
7
  import { getSetCookieName } from "./cookie-utils.js";
7
8
  //#region src/server/app-route-handler-response.ts
@@ -11,11 +12,6 @@ function hasMiddlewareHeader(headers) {
11
12
  for (const key of headers.keys()) if (key.startsWith("x-middleware-")) return true;
12
13
  return false;
13
14
  }
14
- function buildRouteHandlerCacheControl(cacheState, revalidateSeconds, expireSeconds) {
15
- if (revalidateSeconds === 0) return NEVER_CACHE_CONTROL;
16
- if (revalidateSeconds === Infinity) return STATIC_CACHE_CONTROL;
17
- return buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expireSeconds);
18
- }
19
15
  function applyRouteHandlerMiddlewareContext(response, middlewareContext) {
20
16
  if (!middlewareContext.headers && middlewareContext.status == null) return response;
21
17
  const responseHeaders = new Headers(response.headers);
@@ -35,9 +31,14 @@ function buildRouteHandlerCachedResponse(cachedValue, options) {
35
31
  for (const [key, value] of Object.entries(cachedValue.headers)) if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
36
32
  else headers.set(key, value);
37
33
  setCacheStateHeaders(headers, options.cacheState);
38
- const revalidateSeconds = options.cacheControl?.revalidate ?? options.revalidateSeconds;
39
- const expireSeconds = options.cacheControl === void 0 ? void 0 : options.cacheControl.expire ?? options.expireSeconds;
40
- applyCdnResponseHeaders(headers, { cacheControl: buildRouteHandlerCacheControl(options.cacheState, revalidateSeconds, expireSeconds) });
34
+ const { cacheControl } = decideIsr({
35
+ cacheState: options.cacheState,
36
+ kind: "app-route",
37
+ revalidateSeconds: options.revalidateSeconds,
38
+ expireSeconds: options.expireSeconds,
39
+ cacheControlMeta: options.cacheControl
40
+ });
41
+ applyCdnResponseHeaders(headers, { cacheControl });
41
42
  return new Response(options.isHead ? null : cachedValue.body, {
42
43
  status: cachedValue.status,
43
44
  headers
@@ -45,7 +46,7 @@ function buildRouteHandlerCachedResponse(cachedValue, options) {
45
46
  }
46
47
  function applyRouteHandlerRevalidateHeader(response, revalidateSeconds, expireSeconds, tags) {
47
48
  applyCdnResponseHeaders(response.headers, {
48
- cacheControl: buildRouteHandlerCacheControl("HIT", revalidateSeconds, expireSeconds),
49
+ cacheControl: buildAppRouteMissIsrCacheControl(revalidateSeconds, expireSeconds),
49
50
  tags
50
51
  });
51
52
  }
@@ -24,6 +24,7 @@ type AppRouteRequestMode = "auto" | "force-static" | "error";
24
24
  type TrackedAppRouteRequestOptions = {
25
25
  basePath?: string;
26
26
  i18n?: NextI18nConfig | null;
27
+ trailingSlash?: boolean;
27
28
  middlewareHeaders?: Headers | null;
28
29
  onDynamicAccess?: (access: AppRouteDynamicRequestAccess) => void;
29
30
  requestMode?: AppRouteRequestMode;
@@ -1,3 +1,4 @@
1
+ import { addBasePathToPathname } from "../utils/base-path.js";
1
2
  import { buildRequestHeadersFromMiddlewareResponse } from "./middleware-request-headers.js";
2
3
  import { NextRequest, RequestCookies, sealRequestCookies, sealRequestHeaders } from "../shims/server.js";
3
4
  //#region src/server/app-route-handler-runtime.ts
@@ -43,10 +44,11 @@ function bindMethodIfNeeded(value, target) {
43
44
  return typeof value === "function" ? value.bind(target) : value;
44
45
  }
45
46
  function buildNextConfig(options) {
46
- if (!options.basePath && !options.i18n) return null;
47
+ if (!options.basePath && !options.i18n && !options.trailingSlash) return null;
47
48
  return {
48
49
  basePath: options.basePath,
49
- i18n: options.i18n ?? void 0
50
+ i18n: options.i18n ?? void 0,
51
+ trailingSlash: options.trailingSlash
50
52
  };
51
53
  }
52
54
  function rebuildRequestWithHeaders(input, headers) {
@@ -155,7 +157,17 @@ function createTrackedAppRouteRequest(request, options = {}) {
155
157
  }
156
158
  } });
157
159
  };
158
- const wrapRequest = (input) => {
160
+ const wrapRequest = (rawInput) => {
161
+ let input = rawInput;
162
+ if (options.basePath) {
163
+ const inputUrl = new URL(rawInput.url);
164
+ const prefixedPathname = addBasePathToPathname(inputUrl.pathname, options.basePath);
165
+ if (prefixedPathname !== inputUrl.pathname) {
166
+ inputUrl.pathname = prefixedPathname;
167
+ const bodySource = rawInput.body && !rawInput.bodyUsed ? rawInput.clone() : rawInput;
168
+ input = new Request(inputUrl, bodySource);
169
+ }
170
+ }
159
171
  const requestHeaders = options.middlewareHeaders ? buildRequestHeadersFromMiddlewareResponse(input.headers, options.middlewareHeaders) : null;
160
172
  const requestWithOverrides = requestHeaders ? rebuildRequestWithHeaders(input, requestHeaders) : input;
161
173
  const nextRequest = requestWithOverrides instanceof NextRequest ? requestWithOverrides : new NextRequest(requestWithOverrides, { nextConfig: nextConfig ?? void 0 });
@@ -30,6 +30,7 @@ type AppRscRouteMatch<TRoute> = {
30
30
  type DispatchMatchedPageOptions<TRoute> = {
31
31
  clientReuseManifest: ClientReuseManifestParseResult;
32
32
  cleanPathname: string;
33
+ displayPathname: string;
33
34
  formState: ReactFormState | null;
34
35
  actionError?: unknown;
35
36
  actionFailed?: boolean;