vinext 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +25 -22
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +4 -3
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +4 -7
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  10. package/dist/client/dev-error-overlay.d.ts +2 -0
  11. package/dist/client/dev-error-overlay.js +20 -4
  12. package/dist/client/navigation-runtime.d.ts +13 -1
  13. package/dist/client/navigation-runtime.js +1 -1
  14. package/dist/config/config-matchers.js +2 -0
  15. package/dist/config/destination-params.d.ts +2 -1
  16. package/dist/config/destination-params.js +52 -2
  17. package/dist/config/next-config.d.ts +25 -0
  18. package/dist/config/next-config.js +82 -4
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  35. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  36. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  37. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  38. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  39. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  40. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  41. package/dist/entries/app-rsc-entry.js +17 -49
  42. package/dist/entries/app-rsc-manifest.js +3 -4
  43. package/dist/entries/app-ssr-entry.js +1 -0
  44. package/dist/entries/pages-server-entry.js +36 -9
  45. package/dist/index.js +112 -90
  46. package/dist/init-cloudflare.d.ts +1 -3
  47. package/dist/init-cloudflare.js +10 -48
  48. package/dist/init-platform.d.ts +2 -0
  49. package/dist/init-platform.js +4 -0
  50. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  51. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  52. package/dist/plugins/next-intl-catalog.js +85 -0
  53. package/dist/plugins/pages-node-externals.d.ts +1 -0
  54. package/dist/plugins/pages-node-externals.js +1 -1
  55. package/dist/plugins/strip-server-exports.js +2 -1
  56. package/dist/plugins/use-cache-callable.d.ts +1 -1
  57. package/dist/plugins/use-cache-callable.js +136 -94
  58. package/dist/routing/app-route-graph.d.ts +6 -0
  59. package/dist/routing/app-route-graph.js +9 -1
  60. package/dist/server/api-handler.js +21 -5
  61. package/dist/server/app-bfcache-identity.js +1 -1
  62. package/dist/server/app-browser-entry.js +86 -66
  63. package/dist/server/app-browser-history-controller.d.ts +5 -4
  64. package/dist/server/app-browser-history-controller.js +93 -15
  65. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  66. package/dist/server/app-browser-navigation-controller.js +1 -2
  67. package/dist/server/app-browser-rsc-redirect.js +7 -7
  68. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  69. package/dist/server/app-browser-server-action-client.js +5 -4
  70. package/dist/server/app-browser-state.d.ts +2 -4
  71. package/dist/server/app-browser-state.js +9 -4
  72. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  73. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  74. package/dist/server/app-browser-visible-commit.js +4 -14
  75. package/dist/server/app-elements-wire.d.ts +6 -38
  76. package/dist/server/app-elements-wire.js +4 -41
  77. package/dist/server/app-elements.d.ts +2 -2
  78. package/dist/server/app-elements.js +2 -2
  79. package/dist/server/app-history-state.d.ts +2 -2
  80. package/dist/server/app-history-state.js +5 -2
  81. package/dist/server/app-mounted-layouts.d.ts +21 -0
  82. package/dist/server/app-mounted-layouts.js +67 -0
  83. package/dist/server/app-optimistic-routing.d.ts +19 -1
  84. package/dist/server/app-optimistic-routing.js +43 -4
  85. package/dist/server/app-page-boundary-render.js +10 -14
  86. package/dist/server/app-page-boundary.js +2 -2
  87. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  88. package/dist/server/app-page-cache-finalizer.js +70 -29
  89. package/dist/server/app-page-cache.d.ts +13 -1
  90. package/dist/server/app-page-cache.js +31 -9
  91. package/dist/server/app-page-dispatch.d.ts +5 -11
  92. package/dist/server/app-page-dispatch.js +84 -155
  93. package/dist/server/app-page-element-builder.d.ts +2 -2
  94. package/dist/server/app-page-element-builder.js +13 -12
  95. package/dist/server/app-page-execution.d.ts +17 -52
  96. package/dist/server/app-page-execution.js +102 -95
  97. package/dist/server/app-page-head.d.ts +13 -0
  98. package/dist/server/app-page-head.js +44 -16
  99. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  100. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  101. package/dist/server/app-page-layout-gate.d.ts +35 -0
  102. package/dist/server/app-page-layout-gate.js +50 -0
  103. package/dist/server/app-page-params.d.ts +0 -1
  104. package/dist/server/app-page-params.js +1 -14
  105. package/dist/server/app-page-probe.d.ts +3 -21
  106. package/dist/server/app-page-probe.js +4 -37
  107. package/dist/server/app-page-render.d.ts +2 -13
  108. package/dist/server/app-page-render.js +103 -234
  109. package/dist/server/app-page-request.d.ts +1 -3
  110. package/dist/server/app-page-request.js +1 -1
  111. package/dist/server/app-page-response.js +3 -3
  112. package/dist/server/app-page-route-wiring.d.ts +13 -6
  113. package/dist/server/app-page-route-wiring.js +86 -63
  114. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  115. package/dist/server/app-page-rsc-render-status.js +121 -13
  116. package/dist/server/app-page-stream.d.ts +12 -8
  117. package/dist/server/app-page-stream.js +4 -3
  118. package/dist/server/app-pages-bridge.d.ts +4 -0
  119. package/dist/server/app-pages-bridge.js +7 -3
  120. package/dist/server/app-render-dependency.d.ts +11 -4
  121. package/dist/server/app-render-dependency.js +86 -38
  122. package/dist/server/app-request-stage-dispatch.js +1 -1
  123. package/dist/server/app-request-stage-independent-entry.js +1 -1
  124. package/dist/server/app-route-handler-cache.js +2 -2
  125. package/dist/server/app-route-handler-dispatch.js +14 -9
  126. package/dist/server/app-route-handler-execution.js +11 -5
  127. package/dist/server/app-route-handler-policy.d.ts +1 -1
  128. package/dist/server/app-route-handler-policy.js +3 -3
  129. package/dist/server/app-route-handler-response.js +1 -1
  130. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  131. package/dist/server/app-route-handler-runtime.js +28 -11
  132. package/dist/server/app-router-entry.js +1 -1
  133. package/dist/server/app-rsc-cache-busting.d.ts +12 -12
  134. package/dist/server/app-rsc-cache-busting.js +31 -72
  135. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  136. package/dist/server/app-rsc-error-handler.js +10 -2
  137. package/dist/server/app-rsc-errors.d.ts +8 -0
  138. package/dist/server/app-rsc-errors.js +11 -1
  139. package/dist/server/app-rsc-handler.d.ts +5 -5
  140. package/dist/server/app-rsc-handler.js +80 -83
  141. package/dist/server/app-rsc-request-normalization.d.ts +10 -16
  142. package/dist/server/app-rsc-request-normalization.js +12 -18
  143. package/dist/server/app-rsc-response-finalizer.js +2 -2
  144. package/dist/server/app-rsc-response-stage.js +9 -15
  145. package/dist/server/app-server-action-execution.d.ts +3 -1
  146. package/dist/server/app-server-action-execution.js +15 -8
  147. package/dist/server/app-ssr-entry.d.ts +8 -0
  148. package/dist/server/app-ssr-entry.js +22 -16
  149. package/dist/server/app-ssr-stream.js +1 -1
  150. package/dist/server/app-visited-response-cache.d.ts +36 -1
  151. package/dist/server/app-visited-response-cache.js +46 -5
  152. package/dist/server/app-worker-stages.d.ts +37 -6
  153. package/dist/server/app-worker-stages.js +81 -9
  154. package/dist/server/artifact-compatibility.d.ts +0 -1
  155. package/dist/server/artifact-compatibility.js +1 -10
  156. package/dist/server/bfcache-identity.d.ts +7 -0
  157. package/dist/server/bfcache-identity.js +10 -1
  158. package/dist/server/cache-proof.d.ts +1 -115
  159. package/dist/server/cache-proof.js +1 -544
  160. package/dist/server/cacheability-manifest.js +1 -2
  161. package/dist/server/cacheability-request.js +13 -8
  162. package/dist/server/dev-origin-check.d.ts +2 -1
  163. package/dist/server/dev-origin-check.js +2 -1
  164. package/dist/server/dev-server.js +6 -4
  165. package/dist/server/headers.d.ts +6 -3
  166. package/dist/server/headers.js +9 -5
  167. package/dist/server/image-optimization.d.ts +5 -0
  168. package/dist/server/image-optimization.js +6 -1
  169. package/dist/server/isr-cache.d.ts +8 -2
  170. package/dist/server/isr-cache.js +30 -2
  171. package/dist/server/metadata-route-response.js +32 -15
  172. package/dist/server/middleware-matcher.js +19 -6
  173. package/dist/server/middleware-runtime.js +6 -2
  174. package/dist/server/multi-stage.d.ts +57 -12
  175. package/dist/server/multi-stage.js +13 -1
  176. package/dist/server/navigation-planner.js +1 -1
  177. package/dist/server/pages-api-route.js +28 -19
  178. package/dist/server/pages-media-type.d.ts +21 -1
  179. package/dist/server/pages-media-type.js +69 -5
  180. package/dist/server/pages-node-compat.d.ts +1 -1
  181. package/dist/server/pages-node-compat.js +54 -27
  182. package/dist/server/pages-page-data.js +2 -1
  183. package/dist/server/pages-page-handler.js +28 -26
  184. package/dist/server/pages-page-response.d.ts +9 -0
  185. package/dist/server/pages-page-response.js +18 -4
  186. package/dist/server/pages-request-pipeline.d.ts +8 -0
  187. package/dist/server/pages-request-pipeline.js +2 -2
  188. package/dist/server/pages-request-stage-entry.js +36 -14
  189. package/dist/server/pages-response-stage-entry.js +9 -3
  190. package/dist/server/pages-router-entry.js +3 -2
  191. package/dist/server/prod-server.d.ts +2 -2
  192. package/dist/server/prod-server.js +47 -9
  193. package/dist/server/request-error-log.d.ts +9 -0
  194. package/dist/server/request-error-log.js +13 -0
  195. package/dist/server/request-pipeline.d.ts +15 -4
  196. package/dist/server/request-pipeline.js +43 -15
  197. package/dist/server/rsc-completion-metadata.js +3 -0
  198. package/dist/server/shared-cache-identity.d.ts +23 -0
  199. package/dist/server/shared-cache-identity.js +36 -0
  200. package/dist/server/static-file-cache.d.ts +9 -0
  201. package/dist/server/static-file-cache.js +13 -2
  202. package/dist/server/worker-stages.d.ts +16 -3
  203. package/dist/server/worker-stages.js +23 -4
  204. package/dist/shims/cache-handler.js +2 -2
  205. package/dist/shims/cache-request-state.d.ts +23 -26
  206. package/dist/shims/cache-request-state.js +22 -57
  207. package/dist/shims/cache-runtime.d.ts +3 -1
  208. package/dist/shims/cache-runtime.js +94 -48
  209. package/dist/shims/cache.d.ts +16 -10
  210. package/dist/shims/cache.js +47 -36
  211. package/dist/shims/cacheability-classification.d.ts +2 -0
  212. package/dist/shims/cdn-cache.d.ts +22 -5
  213. package/dist/shims/client-hook-error.d.ts +2 -0
  214. package/dist/shims/client-hook-error.js +16 -1
  215. package/dist/shims/client-page-root.js +2 -2
  216. package/dist/shims/dynamic.js +9 -6
  217. package/dist/shims/error-boundary.js +2 -2
  218. package/dist/shims/fetch-cache-state.d.ts +5 -1
  219. package/dist/shims/fetch-cache-state.js +1 -3
  220. package/dist/shims/fetch-cache.d.ts +2 -1
  221. package/dist/shims/fetch-cache.js +39 -25
  222. package/dist/shims/form.js +2 -2
  223. package/dist/shims/headers.d.ts +10 -24
  224. package/dist/shims/headers.js +22 -70
  225. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  226. package/dist/shims/internal/navigation-untracked.js +1 -1
  227. package/dist/shims/link.js +8 -11
  228. package/dist/shims/metadata.d.ts +23 -21
  229. package/dist/shims/metadata.js +62 -24
  230. package/dist/shims/navigation-server.js +1 -1
  231. package/dist/shims/navigation.d.ts +74 -7
  232. package/dist/shims/navigation.js +184 -31
  233. package/dist/shims/navigation.react-server.js +2 -2
  234. package/dist/shims/request-state-types.d.ts +3 -2
  235. package/dist/shims/slot.d.ts +16 -0
  236. package/dist/shims/slot.js +44 -9
  237. package/dist/shims/unified-request-context.d.ts +7 -1
  238. package/dist/shims/unified-request-context.js +3 -3
  239. package/dist/utils/cache-control-metadata.d.ts +5 -0
  240. package/dist/utils/cache-control-metadata.js +9 -1
  241. package/dist/utils/cache-life-profiles.d.ts +33 -0
  242. package/dist/utils/cache-life-profiles.js +111 -0
  243. package/dist/utils/middleware-request-headers.d.ts +14 -3
  244. package/dist/utils/middleware-request-headers.js +54 -7
  245. package/dist/utils/project.d.ts +2 -2
  246. package/dist/utils/project.js +3 -16
  247. package/dist/utils/regex.d.ts +5 -0
  248. package/dist/utils/regex.js +11 -1
  249. package/dist/utils/text-stream.d.ts +10 -5
  250. package/dist/utils/text-stream.js +6 -6
  251. package/package.json +2 -1
  252. package/dist/build/layout-classification-types.d.ts +0 -60
  253. package/dist/build/layout-classification-types.js +0 -1
  254. package/dist/build/layout-classification.d.ts +0 -62
  255. package/dist/build/layout-classification.js +0 -98
  256. package/dist/build/route-classification-injector.d.ts +0 -33
  257. package/dist/build/route-classification-injector.js +0 -59
  258. package/dist/build/route-classification-manifest.d.ts +0 -54
  259. package/dist/build/route-classification-manifest.js +0 -143
  260. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  261. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  262. package/dist/server/app-layout-param-observation.d.ts +0 -42
  263. package/dist/server/app-layout-param-observation.js +0 -168
  264. package/dist/server/client-reuse-manifest.d.ts +0 -103
  265. package/dist/server/client-reuse-manifest.js +0 -209
  266. package/dist/server/skip-cache-proof.d.ts +0 -60
  267. package/dist/server/skip-cache-proof.js +0 -168
  268. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  269. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  270. package/dist/utils/sorted-array.d.ts +0 -8
  271. package/dist/utils/sorted-array.js +0 -20
@@ -9,11 +9,12 @@ import { matchRoute } from "../routing/pages-router.js";
9
9
  import { isDangerousScheme } from "../shims/url-safety.js";
10
10
  import { extractLocaleFromUrl as extractLocaleFromUrl$1, resolvePagesI18nRequest } from "./pages-i18n.js";
11
11
  import { importModule, reportRequestError } from "./instrumentation.js";
12
+ import { logRequestError } from "./request-error-log.js";
12
13
  import { NEVER_CACHE_CONTROL } from "./cache-control.js";
13
14
  import { buildMissIsrCacheControl } from "./isr-decision.js";
14
15
  import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
15
16
  import "./isr-cache.js";
16
- import { ensureFetchPatch } from "../shims/fetch-cache.js";
17
+ import { ensureFetchPatch, setCurrentFetchDraftMode } from "../shims/fetch-cache.js";
17
18
  import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
18
19
  import "../shims/router-state.js";
19
20
  import { withScriptNonce } from "../shims/script-nonce-context.js";
@@ -405,7 +406,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
405
406
  res.end("{}");
406
407
  return;
407
408
  }
408
- const requestContext = createRequestContext();
409
+ const requestContext = createRequestContext({ isOnDemandRevalidate: isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]) });
409
410
  const closeRequest = () => void closeAfterResponse(requestContext);
410
411
  res.on("finish", closeRequest);
411
412
  res.on("close", closeRequest);
@@ -423,7 +424,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
423
424
  let requestAsPath = isDataReq ? gsspResolvedUrl : i18nConfig ? extractLocaleFromUrl$1(originalUrl, i18nConfig, locale).url : originalUrl;
424
425
  const userFacingParams = route.isDynamic ? params : null;
425
426
  let query = mergeRouteParamsIntoQuery(parseQueryString(url), params);
426
- const requestContext = createRequestContext();
427
+ const requestContext = createRequestContext({ isOnDemandRevalidate: isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]) });
427
428
  let isStaticPropsRender = false;
428
429
  const closeRequest = () => void closeAfterResponse(requestContext);
429
430
  res.on("finish", closeRequest);
@@ -468,6 +469,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
468
469
  shouldClear: false
469
470
  };
470
471
  const requestPreviewData = requestPreview.data;
472
+ setCurrentFetchDraftMode(requestPreviewData !== false);
471
473
  const pagesNextData = {
472
474
  ...buildPagesReadinessNextData({
473
475
  pageModule,
@@ -917,7 +919,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
917
919
  _renderEnd = now();
918
920
  if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext(null);
919
921
  } catch (e) {
920
- console.error(e);
922
+ logRequestError(e);
921
923
  await reportRequestError(e, {
922
924
  path: url,
923
925
  method: req.method ?? "GET",
@@ -17,7 +17,7 @@ export declare const VINEXT_CACHE_HEADER = "X-Vinext-Cache";
17
17
  export declare const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
18
18
  /** Next.js cache-tag metadata carried by prerendered App Router responses. */
19
19
  export declare const NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
20
- /** Timing metrics: `handlerStart,compileMs,renderMs`. */
20
+ /** Timing metrics: `handlerStart,compileMs,renderMs` (handlerStart is wall-clock ms). */
21
21
  export declare const VINEXT_TIMING_HEADER = "x-vinext-timing";
22
22
  /** Matched route returned from an independently hosted response stage. */
23
23
  export declare const VINEXT_TRACE_ROUTE_HEADER = "X-Vinext-Trace-Route";
@@ -85,8 +85,11 @@ export declare const VINEXT_PRERENDER_RENDER_ERROR_HEADER = "x-vinext-prerender-
85
85
  export declare const VINEXT_PRERENDER_SPECIAL_ERROR_HEADER = "x-vinext-prerender-special-error";
86
86
  /** Internal marker persisted only inside metadata-route APP_ROUTE cache values. */
87
87
  export declare const VINEXT_METADATA_ROUTE_CACHE_HEADER = "x-vinext-metadata-route-cache";
88
- /** Disabled-by-default client hint describing already-held App Router payload entries. */
89
- export declare const VINEXT_CLIENT_REUSE_MANIFEST_HEADER = "X-Vinext-Client-Reuse-Manifest";
88
+ /**
89
+ * Hashed identities of the layouts a client navigation already holds, so a
90
+ * render that's never stored can leave them out. Not part of the RSC cache key.
91
+ */
92
+ export declare const VINEXT_MOUNTED_LAYOUTS_HEADER = "X-Vinext-Mounted-Layouts";
90
93
  /**
91
94
  * Side-channel signal that an RSC response (HTTP 200) encodes a `redirect()`
92
95
  * thrown during render. The header value is the redirect target (path-only
@@ -17,7 +17,7 @@ const VINEXT_CACHE_HEADER = "X-Vinext-Cache";
17
17
  const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
18
18
  /** Next.js cache-tag metadata carried by prerendered App Router responses. */
19
19
  const NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
20
- /** Timing metrics: `handlerStart,compileMs,renderMs`. */
20
+ /** Timing metrics: `handlerStart,compileMs,renderMs` (handlerStart is wall-clock ms). */
21
21
  const VINEXT_TIMING_HEADER = "x-vinext-timing";
22
22
  /** Matched route returned from an independently hosted response stage. */
23
23
  const VINEXT_TRACE_ROUTE_HEADER = "X-Vinext-Trace-Route";
@@ -85,8 +85,11 @@ const VINEXT_PRERENDER_RENDER_ERROR_HEADER = "x-vinext-prerender-render-error";
85
85
  const VINEXT_PRERENDER_SPECIAL_ERROR_HEADER = "x-vinext-prerender-special-error";
86
86
  /** Internal marker persisted only inside metadata-route APP_ROUTE cache values. */
87
87
  const VINEXT_METADATA_ROUTE_CACHE_HEADER = "x-vinext-metadata-route-cache";
88
- /** Disabled-by-default client hint describing already-held App Router payload entries. */
89
- const VINEXT_CLIENT_REUSE_MANIFEST_HEADER = "X-Vinext-Client-Reuse-Manifest";
88
+ /**
89
+ * Hashed identities of the layouts a client navigation already holds, so a
90
+ * render that's never stored can leave them out. Not part of the RSC cache key.
91
+ */
92
+ const VINEXT_MOUNTED_LAYOUTS_HEADER = "X-Vinext-Mounted-Layouts";
90
93
  /**
91
94
  * Side-channel signal that an RSC response (HTTP 200) encodes a `redirect()`
92
95
  * thrown during render. The header value is the redirect target (path-only
@@ -152,7 +155,8 @@ const FLIGHT_HEADERS = [
152
155
  "next-router-prefetch",
153
156
  "next-hmr-refresh",
154
157
  "next-router-segment-prefetch",
155
- "x-vinext-rsc-state-fingerprint"
158
+ "x-vinext-rsc-state-fingerprint",
159
+ "x-vinext-mounted-layouts"
156
160
  ];
157
161
  /**
158
162
  * Headers that must be stripped from external requests before any handler
@@ -192,4 +196,4 @@ const VINEXT_INTERNAL_HEADERS = [
192
196
  VINEXT_TRACE_ROUTE_HEADER.toLowerCase()
193
197
  ];
194
198
  //#endregion
195
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_EXPECTED_WORKER_VERSION_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERCEPTION_ID_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_STATE_FINGERPRINT_HEADER, VINEXT_RSC_VARY_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER, VINEXT_TRACE_BUFFERED_BODY_HEADER, VINEXT_TRACE_ERROR_HEADER, VINEXT_TRACE_ROUTE_HEADER, isVinextRscVaryField };
199
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, VINEXT_CACHE_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_EXPECTED_WORKER_VERSION_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERCEPTION_ID_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_MOUNTED_LAYOUTS_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_STATE_FINGERPRINT_HEADER, VINEXT_RSC_VARY_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER, VINEXT_TRACE_BUFFERED_BODY_HEADER, VINEXT_TRACE_ERROR_HEADER, VINEXT_TRACE_ROUTE_HEADER, isVinextRscVaryField };
@@ -143,6 +143,11 @@ export declare function negotiateImageFormat(acceptHeader: string | null): strin
143
143
  * Optimized images are immutable because the URL encodes the transform params.
144
144
  */
145
145
  export declare const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
146
+ /**
147
+ * Next.js's Cache-Control for an image whose source can change at its URL
148
+ * (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
149
+ */
150
+ export declare const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
146
151
  /**
147
152
  * Content-Security-Policy for image optimization responses.
148
153
  * Blocks script execution and framing to prevent XSS via SVG or other
@@ -229,6 +229,11 @@ function negotiateImageFormat(acceptHeader) {
229
229
  */
230
230
  const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
231
231
  /**
232
+ * Next.js's Cache-Control for an image whose source can change at its URL
233
+ * (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
234
+ */
235
+ const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
236
+ /**
232
237
  * Content-Security-Policy for image optimization responses.
233
238
  * Blocks script execution and framing to prevent XSS via SVG or other
234
239
  * active content that might be served through the image endpoint.
@@ -397,4 +402,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
397
402
  }, allowedWidths, imageConfig);
398
403
  }
399
404
  //#endregion
400
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
405
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
@@ -1,7 +1,7 @@
1
- import { OnRequestErrorContext } from "./instrumentation.js";
2
- import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
3
1
  import { RenderObservation } from "./cache-proof.js";
4
2
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
+ import { OnRequestErrorContext } from "./instrumentation.js";
4
+ import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
5
5
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
6
6
  import { CacheControlMetadata, CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
7
7
  import { getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret } from "./revalidation-request.js";
@@ -68,6 +68,12 @@ export declare function isrSetPrerenderedAppPage(key: string, data: CachedAppPag
68
68
  }): Promise<void>;
69
69
  /** Coalesce same-key synchronous on-demand revalidations. */
70
70
  export declare function coalesceOnDemandRevalidation<T>(key: string, renderFn: () => Promise<T>): Promise<T>;
71
+ /**
72
+ * Coalesce same-key on-demand revalidations whose render answers with a
73
+ * Response, or null for no match. Every caller gets its own copy of the one
74
+ * render's response. `method` is the request method the response answers.
75
+ */
76
+ export declare function coalesceOnDemandResponse<R extends Response | null>(key: string, method: string, renderFn: () => Promise<R>): Promise<R>;
71
77
  /**
72
78
  * Trigger a background regeneration for a cache key.
73
79
  *
@@ -3,6 +3,7 @@ import { normalizePathnameForRouteMatch } from "../routing/utils.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
5
5
  import { reportRequestError } from "./instrumentation.js";
6
+ import { logRequestError } from "./request-error-log.js";
6
7
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
7
8
  import { shareIsolatePromise } from "./isolate-shared-promise.js";
8
9
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
@@ -101,6 +102,31 @@ function coalesceOnDemandRevalidation(key, renderFn) {
101
102
  return promise;
102
103
  }
103
104
  /**
105
+ * Coalesce same-key on-demand revalidations whose render answers with a
106
+ * Response, or null for no match. Every caller gets its own copy of the one
107
+ * render's response. `method` is the request method the response answers.
108
+ */
109
+ async function coalesceOnDemandResponse(key, method, renderFn) {
110
+ const snapshot = await coalesceOnDemandRevalidation(key, async () => {
111
+ const response = await renderFn();
112
+ if (!response) return null;
113
+ const hasBody = method !== "HEAD" && response.status !== 204 && response.status !== 205 && response.status !== 304;
114
+ if (!hasBody) await response.body?.cancel();
115
+ return {
116
+ body: hasBody ? new Uint8Array(await response.arrayBuffer()) : null,
117
+ headers: [...response.headers.entries()],
118
+ status: response.status,
119
+ statusText: response.statusText
120
+ };
121
+ });
122
+ if (!snapshot) return null;
123
+ return new Response(snapshot.body?.slice() ?? null, {
124
+ headers: snapshot.headers,
125
+ status: snapshot.status,
126
+ statusText: snapshot.statusText
127
+ });
128
+ }
129
+ /**
104
130
  * Trigger a background regeneration for a cache key.
105
131
  *
106
132
  * If a regeneration for this key is already in progress, this is a no-op.
@@ -119,7 +145,9 @@ function triggerBackgroundRegeneration(key, renderFn, errorContext) {
119
145
  if (!getCdnCacheAdapter().ownsBackgroundRevalidation) return;
120
146
  if (pendingRegenerations.has(key)) return;
121
147
  const promise = renderFn().catch(async (err) => {
122
- console.error(`[vinext] ISR background regeneration failed for ${key}:`, err);
148
+ const message = `[vinext] ISR background regeneration failed for ${key}:`;
149
+ if (errorContext) logRequestError(message, err);
150
+ else console.error(message, err);
123
151
  if (errorContext && (errorContext.shouldReport?.(err) ?? true)) await reportRequestError(err, {
124
152
  path: key,
125
153
  method: "GET",
@@ -247,4 +275,4 @@ function appIsrRouteKey(pathname) {
247
275
  return appIsrCacheKey(pathname, pathname !== "/" && pathname.endsWith("/") ? "route:trailing-slash" : "route");
248
276
  }
249
277
  //#endregion
250
- export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrFallbackShellHtmlKey, appIsrFallbackShellKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
278
+ export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrFallbackShellHtmlKey, appIsrFallbackShellKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandResponse, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
@@ -8,16 +8,16 @@ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
8
8
  import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
9
9
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy } from "./cache-control.js";
10
10
  import { buildAppRouteMissIsrCacheControl } from "./isr-decision.js";
11
- import { isrCacheControl } from "./isr-cache.js";
11
+ import { coalesceOnDemandResponse, isrCacheControl } from "./isr-cache.js";
12
12
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
13
13
  import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
14
14
  import { getActiveDraftModeState, getHeadersContext, hasDraftModeCookieHeader, replaceHeadersContext } from "../shims/headers.js";
15
- import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
15
+ import { _consumeRequestScopedCacheLife, _isOnDemandRevalidate } from "../shims/cache-request-state.js";
16
16
  import { ensureFetchPatch, getCollectedFetchTags, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
17
- import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
18
17
  import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
19
- import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
18
+ import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
20
19
  import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse } from "./app-route-handler-response.js";
20
+ import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
21
21
  import { buildPageCacheTags } from "./implicit-tags.js";
22
22
  import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
23
23
  import { completeAppRouteHandlerResponse } from "./app-route-handler-execution.js";
@@ -165,7 +165,7 @@ async function captureRenderedMetadataRoute(response, route, cleanPathname) {
165
165
  ...observedCacheLife,
166
166
  revalidate: frameworkRevalidate
167
167
  };
168
- const cacheable = (response.status < 400 || response.status === 404) && completed && !isRenderDynamicLatched() && !getActiveDraftModeState() && !hasDraftModeCookieHeader() && route.module?.dynamic !== "force-dynamic" && frameworkRevalidate > 0 && !response.headers.has("set-cookie");
168
+ const cacheable = (_isOnDemandRevalidate() || response.status < 400 || response.status === 404) && completed && !isRenderDynamicLatched() && !getActiveDraftModeState() && !hasDraftModeCookieHeader() && route.module?.dynamic !== "force-dynamic" && frameworkRevalidate > 0 && !response.headers.has("set-cookie");
169
169
  const cacheControl = cacheable ? buildAppRouteMissIsrCacheControl(frameworkRevalidate, cacheLife?.expire) : NEVER_CACHE_CONTROL;
170
170
  if (isUserResponse && !hasUserPolicy) {
171
171
  response = new Response(response.body, {
@@ -231,12 +231,34 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
231
231
  await closeAfterResponse(requestContext);
232
232
  }
233
233
  }
234
+ /** The outer ISR key for a matched metadata route, or null if it's never cached. */
235
+ function getOuterMetadataCacheKey(options, route) {
236
+ if (!isOuterMetadataCacheEnabled() || !options.isrRouteKey || getActiveDraftModeState() || hasDraftModeCookieHeader() || route.module?.dynamic === "force-dynamic" || route.module?.revalidate === 0) return null;
237
+ return options.isrRouteKey(options.cleanPathname);
238
+ }
239
+ /**
240
+ * Renders a matched metadata route that wasn't served from the cache, and
241
+ * stores it. Concurrent on-demand revalidations of one entry share a single
242
+ * render, as Next.js batches ResponseCache work by its key and
243
+ * isOnDemandRevalidate.
244
+ */
245
+ async function renderMetadataRouteMiss(options, route, render, cachedEntry) {
246
+ const renderAndStore = async () => {
247
+ const rendered = await render();
248
+ if (!rendered) return null;
249
+ await writeMetadataRouteMiss(options, route, rendered, cachedEntry);
250
+ return rendered.response;
251
+ };
252
+ const routeKey = _isOnDemandRevalidate() ? getOuterMetadataCacheKey(options, route) : null;
253
+ if (routeKey === null || !options.isrSet) return renderAndStore();
254
+ return coalesceOnDemandResponse(routeKey, "GET", renderAndStore);
255
+ }
234
256
  async function readMatchedPrerenderedMetadataRouteResponse(options, route, render) {
235
- if (!isOuterMetadataCacheEnabled() || !options.isrGet || !options.isrRouteKey || getActiveDraftModeState() || hasDraftModeCookieHeader() || route.module?.dynamic === "force-dynamic" || route.module?.revalidate === 0) return {
257
+ const routeKey = getOuterMetadataCacheKey(options, route);
258
+ if (routeKey === null || !options.isrGet || _isOnDemandRevalidate()) return {
236
259
  cachedEntry: null,
237
260
  response: null
238
261
  };
239
- const routeKey = options.isrRouteKey(options.cleanPathname);
240
262
  let cached;
241
263
  try {
242
264
  cached = await options.isrGet(routeKey);
@@ -495,11 +517,8 @@ async function handleMetadataRouteRequest(options) {
495
517
  };
496
518
  const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
497
519
  if (cached.response) return cached.response;
498
- const rendered = await render();
499
- if (rendered) {
500
- await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
501
- return rendered.response;
502
- }
520
+ const response = await renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
521
+ if (response) return response;
503
522
  }
504
523
  continue;
505
524
  }
@@ -533,9 +552,7 @@ async function handleMetadataRouteRequest(options) {
533
552
  });
534
553
  if (dynamicParamsResponse) return dynamicParamsResponse;
535
554
  }
536
- const rendered = await render();
537
- await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
538
- return rendered.response;
555
+ return renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
539
556
  }
540
557
  return null;
541
558
  }
@@ -50,17 +50,30 @@ function matchesMiddlewarePathname(pathname, matcher, i18nConfig, localeContext)
50
50
  }
51
51
  function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
52
52
  if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, pattern, i18nConfig, true);
53
- if (!i18nConfig) return matchPattern(pathname, pattern);
53
+ if (!i18nConfig) return matchRequestPattern(pathname, pattern);
54
54
  if (localeContext) {
55
55
  if (localeContext.kind === "internal") return false;
56
- return matchPattern(localeContext.kind === "literal" ? stripFirstPathSegment(pathname) : pathname, pattern);
56
+ return matchRequestPattern(localeContext.kind === "literal" ? stripFirstPathSegment(pathname) : pathname, pattern);
57
57
  }
58
- return matchPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
58
+ return matchRequestPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
59
59
  }
60
60
  function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
61
61
  if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, matcher.source, i18nConfig, matcher.locale !== false);
62
62
  if (matcher.locale !== false) return matchMatcherPattern(pathname, matcher.source, i18nConfig, localeContext);
63
- return matchPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
63
+ return matchRequestPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
64
+ }
65
+ const APP_ROUTE_TRANSPORT_SUFFIX = "\\.rsc|\\.segments/.+\\.segment\\.rsc";
66
+ const APP_ROUTE_TRANSPORT_SUFFIX_RE = /(?:\.rsc|\.segments\/.+\.segment\.rsc)$/;
67
+ /**
68
+ * Next.js compiles every matcher with optional App Router transport suffixes,
69
+ * so a matcher for `/about` runs middleware for `/about.rsc` too. Middleware
70
+ * still sees the path as requested.
71
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
72
+ */
73
+ function matchRequestPattern(pathname, source) {
74
+ if (matchPattern(pathname, source)) return true;
75
+ if (!APP_ROUTE_TRANSPORT_SUFFIX_RE.test(pathname)) return false;
76
+ return matchPattern(pathname, source === "/" ? `/(/?index(?:${APP_ROUTE_TRANSPORT_SUFFIX}))?` : `${source}{(${APP_ROUTE_TRANSPORT_SUFFIX})}?`);
64
77
  }
65
78
  function matchNextDataPattern(pathname, source, i18nConfig, localized) {
66
79
  if (compiledPattern(source) === INVALID_MATCHER_PATTERN) return true;
@@ -69,8 +82,8 @@ function matchNextDataPattern(pathname, source, i18nConfig, localized) {
69
82
  /**
70
83
  * The source Next.js compiles for a matcher: an optional `_next/data/<buildId>`
71
84
  * prefix, an internal locale segment under i18n, and an optional data suffix.
72
- * Next.js also accepts App Router transport suffixes, which end in `.rsc` and
73
- * so can never match a `.json` data URL.
85
+ * Its App Router transport suffixes end in `.rsc`, so they can never match a
86
+ * `.json` data URL.
74
87
  * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
75
88
  */
76
89
  function nextDataMatcherSource(source, i18nConfig, localized) {
@@ -19,6 +19,7 @@ import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
19
19
  import { reportRequestError } from "./instrumentation.js";
20
20
  import { isValidNavigationSignalError } from "../utils/navigation-signal.js";
21
21
  import { frameworkTracer } from "./tracer.js";
22
+ import { logRequestError } from "./request-error-log.js";
22
23
  //#region src/server/middleware-runtime.ts
23
24
  function reportProxyError(options, error) {
24
25
  const url = new URL(options.request.url);
@@ -252,8 +253,11 @@ async function executeMiddlewareModule(options) {
252
253
  } catch (e) {
253
254
  const isDevelopmentNavigationSignal = process.env.NODE_ENV !== "production" && isValidNavigationSignalError(e);
254
255
  if (isDevelopmentNavigationSignal && e instanceof Error) e.message = `Next.js navigation API is not allowed to be used in ${options.isProxy ? "Proxy" : "Middleware"}.`;
255
- console.error("[vinext] Middleware error:", e);
256
- if (!isDevelopmentNavigationSignal) await reportProxyError(options, e);
256
+ if (isDevelopmentNavigationSignal) console.error("[vinext] Middleware error:", e);
257
+ else {
258
+ logRequestError("[vinext] Middleware error:", e);
259
+ await reportProxyError(options, e);
260
+ }
257
261
  const waitUntilPromises = drainFetchEvent(fetchEvent);
258
262
  releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
259
263
  const message = options.includeErrorDetails ? "Middleware Error: " + (e instanceof Error ? e.message : String(e)) : "Internal Server Error";
@@ -1,5 +1,14 @@
1
1
  import { CacheabilityRepresentation } from "./cacheability-manifest.js";
2
2
  //#region src/server/multi-stage.d.ts
3
+ /**
4
+ * The origin a shared cache identity names in place of the request's. Next.js
5
+ * keys its response cache by pathname, so one stored response answers every
6
+ * host an app serves. `.invalid` never resolves, so no configured domain or
7
+ * host condition can match it when the identity is replayed.
8
+ */
9
+ export declare const VINEXT_CACHE_IDENTITY_ORIGIN = "https://cache-identity.vinext.invalid";
10
+ /** Name `origin` in place of a dispatch URL's origin in a shared cache identity. */
11
+ export declare function withCacheIdentityOrigin(requestUrl: string, origin?: string): string;
3
12
  /** Whether a response-stage cache must reject a field it did not key itself by. */
4
13
  export declare function hasUnsupportedResponseStageVary(headers: Headers, keyedRequestHeaders?: Iterable<string>): boolean;
5
14
  /**
@@ -41,26 +50,62 @@ export type VinextResponseStageDispatchOptions = {
41
50
  */
42
51
  regeneratesStoredEntry?: boolean;
43
52
  /**
44
- * Query-free identity of a shared App page GET/HEAD dispatch, supplied only
45
- * to adapters that declare `responseStageCacheIdentity: "query-free"` and
46
- * require completed-response admission. A transport given one partitions
53
+ * Shared cache identity of an App Router GET/HEAD dispatch, or of a Pages
54
+ * page the build found static (getStaticProps or none), supplied only to
55
+ * adapters that declare a `responseStageCacheIdentity` and require
56
+ * completed-response admission. A transport given one partitions
47
57
  * its baseline lookup and stored entry by this request's method and complete
48
58
  * URL plus these complete serialized props, instead of the dispatched request
49
- * and props, and replays this identity for background regeneration.
59
+ * and props, and replays this identity for background regeneration, or the
60
+ * dispatched request and props when `regenerateFromDispatch` is set.
50
61
  * Everything else in the contract above still applies.
51
62
  *
52
- * The identity drops the user query from the URL and `resolvedUrl`, keeping
53
- * framework representation selectors: the `.rsc` suffix, the render mode,
54
- * and, for RSC requests only, the one validated `_rsc` value. That is safe only because completed-response
55
- * admission refuses a cacheable App page response unless its render proved
56
- * it never read `searchParams`. The dispatched request still carries the real
57
- * query for the render itself. Core omits the identity when a `next.config`
58
- * public cache policy applies (cached per full URL, as Next.js CDN caching
59
- * is), and for bypassed, interception, and mounted-slot dispatches.
63
+ * The identity names `VINEXT_CACHE_IDENTITY_ORIGIN` in place of the
64
+ * request's origin, in its URL and its `requestOrigin`, so one stored
65
+ * response answers every host, as Next.js's pathname-keyed response cache
66
+ * does. That is safe only because completed-response admission refuses a
67
+ * response whose render used a dynamic API, which every App Router read of
68
+ * the request's host is. Core omits the identity for bypassed dispatches.
69
+ *
70
+ * A Pages page identity instead names the configured i18n domain the
71
+ * request's host selects, as vinext's Pages ISR key keeps the domain, and
72
+ * the cache identity origin for every other host. A page without
73
+ * getStaticProps whose page or `_app` getInitialProps turns out to run is
74
+ * refused admission, because its props say the entry is shared across
75
+ * hosts. A getStaticProps page's `_app` getInitialProps shares its entry,
76
+ * as in Next.js, which keys it by path.
77
+ *
78
+ * Under `responseStageCacheIdentity: "query-free"`, an App page identity
79
+ * also drops the user query from the URL and `resolvedUrl`, keeping
80
+ * framework representation selectors: the render mode and, for RSC
81
+ * requests only, the one validated `_rsc` value. That is safe
82
+ * only because completed-response admission refuses a cacheable App page
83
+ * response unless its render proved it never read `searchParams`. The
84
+ * dispatched request still carries the real query for the render itself. A
85
+ * page identity keeps the query when a `next.config` public cache policy
86
+ * applies (cached per full URL, as Next.js CDN caching is), and for
87
+ * interception and mounted-slot dispatches. A Route Handler or metadata
88
+ * route identity keeps only the method, path (without `.rsc`) and route
89
+ * props, as Next.js keys a route handler by its pathname: the RSC selectors
90
+ * pick no representation of its response, and admission refuses a handler
91
+ * that read the request's URL or headers.
60
92
  */
61
93
  cacheIdentity?: {
62
94
  props: unknown;
95
+ /**
96
+ * Set when the identity dropped the user query. A transport stores a
97
+ * page's other part from one render only under such an identity: one
98
+ * that keeps the query is cached per full URL, and stores its own part.
99
+ */
100
+ queryFree?: true;
63
101
  request: Request;
102
+ /**
103
+ * Regenerate from the dispatched request and props. A getStaticProps
104
+ * page's custom `_app` getInitialProps receives the request, host
105
+ * included, so a Pages page regenerates from a real request, as in
106
+ * Next.js, never from the identity's synthetic origin.
107
+ */
108
+ regenerateFromDispatch?: true;
64
109
  };
65
110
  };
66
111
  export type VinextCacheabilityProbeMode = "probe" | "identity";
@@ -1,8 +1,20 @@
1
1
  //#region src/server/multi-stage.ts
2
+ /**
3
+ * The origin a shared cache identity names in place of the request's. Next.js
4
+ * keys its response cache by pathname, so one stored response answers every
5
+ * host an app serves. `.invalid` never resolves, so no configured domain or
6
+ * host condition can match it when the identity is replayed.
7
+ */
8
+ const VINEXT_CACHE_IDENTITY_ORIGIN = "https://cache-identity.vinext.invalid";
9
+ /** Name `origin` in place of a dispatch URL's origin in a shared cache identity. */
10
+ function withCacheIdentityOrigin(requestUrl, origin = VINEXT_CACHE_IDENTITY_ORIGIN) {
11
+ const url = new URL(requestUrl);
12
+ return `${origin}${url.pathname}${url.search}`;
13
+ }
2
14
  /** Whether a response-stage cache must reject a field it did not key itself by. */
3
15
  function hasUnsupportedResponseStageVary(headers, keyedRequestHeaders = []) {
4
16
  const keyedFields = new Set([...keyedRequestHeaders].map((name) => name.trim().toLowerCase()).filter(Boolean));
5
17
  return (headers.get("Vary") ?? "").split(",").map((name) => name.trim().toLowerCase()).filter(Boolean).some((name) => name === "*" || !keyedFields.has(name));
6
18
  }
7
19
  //#endregion
8
- export { hasUnsupportedResponseStageVary };
20
+ export { VINEXT_CACHE_IDENTITY_ORIGIN, hasUnsupportedResponseStageVary, withCacheIdentityOrigin };
@@ -1,9 +1,9 @@
1
1
  import { stripBasePath } from "../utils/base-path.js";
2
2
  import { splitPathnameForRouteMatch } from "../routing/utils.js";
3
3
  import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
4
+ import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
4
5
  import { AppElementsWire, compareAppElementsSlotIds } from "./app-elements-wire.js";
5
6
  import "./app-elements.js";
6
- import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
7
7
  import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
8
8
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
9
9
  import { verifyOperationTokenForCacheReuse } from "./operation-token.js";
@@ -30,6 +30,17 @@ function isEdgeApiRouteModule(module) {
30
30
  function isNodeApiRouteModule(module) {
31
31
  return typeof module.default === "function" && !isEdgeApiRuntime(resolveModuleRuntime(module));
32
32
  }
33
+ function withInitialResponseHeaders(response, initialResponseHeaders) {
34
+ if (!initialResponseHeaders || !initialResponseHeaders.keys().next().value) return response;
35
+ const headers = new Headers(initialResponseHeaders);
36
+ for (const [name, value] of response.headers) if (name !== "set-cookie") headers.set(name, value);
37
+ for (const cookie of response.headers.getSetCookie()) headers.append("Set-Cookie", cookie);
38
+ return new Response(response.body, {
39
+ headers,
40
+ status: response.status,
41
+ statusText: response.statusText
42
+ });
43
+ }
33
44
  async function handlePagesApiRoute(options) {
34
45
  const run = async () => {
35
46
  const response = await _handlePagesApiRoute(options);
@@ -51,22 +62,10 @@ async function _handlePagesApiRoute(options) {
51
62
  skipTrailingSlashRedirect: options.nextConfig.skipTrailingSlashRedirect
52
63
  } } : void 0);
53
64
  const response = await tracePagesApiHandler(route.pattern, () => handler(nextRequest));
54
- if (response instanceof Response) {
55
- const finalized = finalizeEdgeApiResponse(response, options.edgeRuntime ?? "worker");
56
- if (!options.initialResponseHeaders || !options.initialResponseHeaders.keys().next().value) return finalized;
57
- const headers = new Headers(options.initialResponseHeaders);
58
- for (const [name, value] of finalized.headers) if (name.toLowerCase() !== "set-cookie") headers.set(name, value);
59
- const finalizedCookies = finalized.headers.getSetCookie();
60
- for (const cookie of finalizedCookies) headers.append("Set-Cookie", cookie);
61
- return new Response(finalized.body, {
62
- headers,
63
- status: finalized.status,
64
- statusText: finalized.statusText
65
- });
66
- }
65
+ if (response instanceof Response) return withInitialResponseHeaders(finalizeEdgeApiResponse(response, options.edgeRuntime ?? "worker"), options.initialResponseHeaders);
67
66
  throw new Error("Edge API route did not return a Response");
68
67
  }
69
- if (!isNodeApiRouteModule(route.module)) return new Response("API route does not export a default function", { status: 500 });
68
+ if (!isNodeApiRouteModule(route.module)) return withInitialResponseHeaders(new Response("API route does not export a default function", { status: 500 }), options.initialResponseHeaders);
70
69
  const handler = route.module.default;
71
70
  const query = buildPagesApiQuery(options.url, params);
72
71
  const bodyParserConfig = resolveBodyParserConfig(route.module.config);
@@ -92,8 +91,12 @@ async function _handlePagesApiRoute(options) {
92
91
  if (!res.writableEnded) markVinextStreamedApiResponse(response);
93
92
  return response;
94
93
  };
95
- const destroyAfterHandlerError = (error) => {
94
+ const settleHandlerError = (error) => {
96
95
  const normalizedError = error instanceof Error ? error : new Error(String(error));
96
+ if (!res.headersSent) return {
97
+ type: "error",
98
+ error: normalizedError
99
+ };
97
100
  res.destroy(normalizedError);
98
101
  throw normalizedError;
99
102
  };
@@ -101,22 +104,28 @@ async function _handlePagesApiRoute(options) {
101
104
  type: "response",
102
105
  response
103
106
  }));
104
- const handlerCompletion = Promise.resolve().then(() => tracePagesApiHandler(route.pattern, () => handler(req, res), { recordErrors: options.edgeRuntime !== "node" })).then(() => ({ type: "handler" }), destroyAfterHandlerError);
107
+ const handlerCompletion = Promise.resolve().then(() => tracePagesApiHandler(route.pattern, () => handler(req, res), { recordErrors: options.edgeRuntime !== "node" })).then(() => ({ type: "handler" }), settleHandlerError);
105
108
  const firstSettled = await Promise.race([responseReady, handlerCompletion]);
106
109
  if (firstSettled.type === "response") {
107
110
  const handlerLifecycle = handlerCompletion.then(completeHandler, (error) => options.reportRequestError?.(error, route.pattern));
108
111
  getRequestExecutionContext()?.waitUntil(handlerLifecycle);
109
112
  return finalizeResponse(firstSettled.response);
110
113
  }
114
+ if (firstSettled.type === "error") {
115
+ await options.reportRequestError?.(firstSettled.error, route.pattern);
116
+ res.statusCode = 500;
117
+ res.end("Internal Server Error");
118
+ return finalizeResponse(await responsePromise);
119
+ }
111
120
  completeHandler();
112
121
  return finalizeResponse(await responsePromise);
113
122
  } catch (error) {
114
- if (error instanceof PagesBodyParseError) return new Response(error.message, {
123
+ if (error instanceof PagesBodyParseError) return withInitialResponseHeaders(new Response(error.message, {
115
124
  status: error.statusCode,
116
125
  statusText: error.message
117
- });
126
+ }), options.initialResponseHeaders);
118
127
  await options.reportRequestError?.(error instanceof Error ? error : new Error(String(error)), route.pattern);
119
- return internalServerErrorResponse();
128
+ return withInitialResponseHeaders(internalServerErrorResponse(), options.initialResponseHeaders);
120
129
  }
121
130
  }
122
131
  //#endregion