vinext 1.4.0 → 1.4.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 (156) hide show
  1. package/README.md +21 -21
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/build/prerender.js +3 -2
  4. package/dist/check.js +4 -7
  5. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  6. package/dist/client/dev-error-overlay.d.ts +2 -0
  7. package/dist/client/dev-error-overlay.js +19 -3
  8. package/dist/config/config-matchers.js +4 -2
  9. package/dist/config/destination-params.d.ts +2 -1
  10. package/dist/config/destination-params.js +55 -2
  11. package/dist/config/next-config.d.ts +22 -1
  12. package/dist/config/next-config.js +49 -4
  13. package/dist/entries/app-rsc-entry.js +41 -19
  14. package/dist/entries/app-rsc-manifest.js +4 -0
  15. package/dist/entries/pages-server-entry.js +5 -1
  16. package/dist/index.js +44 -32
  17. package/dist/init-cloudflare.js +18 -2
  18. package/dist/plugins/css-target.d.ts +18 -0
  19. package/dist/plugins/css-target.js +93 -0
  20. package/dist/plugins/strip-server-exports.js +2 -1
  21. package/dist/routing/app-route-graph.d.ts +10 -0
  22. package/dist/routing/app-route-graph.js +15 -1
  23. package/dist/server/api-handler.d.ts +2 -0
  24. package/dist/server/api-handler.js +5 -6
  25. package/dist/server/app-action-forwarding.js +3 -1
  26. package/dist/server/app-browser-entry.js +28 -7
  27. package/dist/server/app-browser-rsc-redirect.js +3 -3
  28. package/dist/server/app-browser-server-action-client.js +2 -2
  29. package/dist/server/app-browser-visible-commit.js +2 -2
  30. package/dist/server/app-elements.d.ts +3 -0
  31. package/dist/server/app-elements.js +14 -1
  32. package/dist/server/app-fallback-renderer.js +2 -2
  33. package/dist/server/app-optimistic-routing.d.ts +12 -1
  34. package/dist/server/app-optimistic-routing.js +40 -19
  35. package/dist/server/app-page-boundary-render.js +7 -12
  36. package/dist/server/app-page-cache-render.js +1 -1
  37. package/dist/server/app-page-dispatch.d.ts +10 -2
  38. package/dist/server/app-page-dispatch.js +73 -31
  39. package/dist/server/app-page-element-builder.d.ts +9 -20
  40. package/dist/server/app-page-element-builder.js +40 -52
  41. package/dist/server/app-page-execution.js +9 -6
  42. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  43. package/dist/server/app-page-intercept-segment-config.js +90 -0
  44. package/dist/server/app-page-navigation-params.d.ts +23 -0
  45. package/dist/server/app-page-navigation-params.js +49 -0
  46. package/dist/server/app-page-render.d.ts +5 -3
  47. package/dist/server/app-page-render.js +31 -17
  48. package/dist/server/app-page-request.d.ts +24 -2
  49. package/dist/server/app-page-request.js +10 -10
  50. package/dist/server/app-page-response.js +1 -1
  51. package/dist/server/app-page-route-wiring.d.ts +10 -0
  52. package/dist/server/app-page-route-wiring.js +34 -13
  53. package/dist/server/app-page-segment-plan.js +9 -1
  54. package/dist/server/app-page-segment-state.d.ts +9 -0
  55. package/dist/server/app-page-segment-state.js +54 -1
  56. package/dist/server/app-page-stream.d.ts +10 -8
  57. package/dist/server/app-page-stream.js +2 -1
  58. package/dist/server/app-request-stage-independent-entry.js +1 -1
  59. package/dist/server/app-response-stage-entry.js +1 -0
  60. package/dist/server/app-route-handler-execution.js +3 -2
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  62. package/dist/server/app-route-handler-runtime.js +11 -7
  63. package/dist/server/app-route-module-loader.d.ts +2 -0
  64. package/dist/server/app-route-module-loader.js +1 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  67. package/dist/server/app-rsc-cache-busting.js +27 -68
  68. package/dist/server/app-rsc-error-handler.js +1 -1
  69. package/dist/server/app-rsc-errors.js +4 -1
  70. package/dist/server/app-rsc-handler.d.ts +10 -1
  71. package/dist/server/app-rsc-handler.js +126 -103
  72. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  73. package/dist/server/app-rsc-request-normalization.js +11 -14
  74. package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
  75. package/dist/server/app-rsc-response-finalizer.js +11 -1
  76. package/dist/server/app-rsc-response-stage.js +4 -12
  77. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  78. package/dist/server/app-rsc-route-matching.js +42 -18
  79. package/dist/server/app-server-action-execution.js +24 -14
  80. package/dist/server/app-ssr-entry.d.ts +6 -0
  81. package/dist/server/app-ssr-entry.js +13 -8
  82. package/dist/server/app-worker-stages.d.ts +12 -3
  83. package/dist/server/app-worker-stages.js +37 -4
  84. package/dist/server/bfcache-identity.d.ts +7 -0
  85. package/dist/server/bfcache-identity.js +10 -1
  86. package/dist/server/cache-control.d.ts +2 -0
  87. package/dist/server/cache-control.js +3 -1
  88. package/dist/server/cacheability-manifest.js +1 -2
  89. package/dist/server/dev-origin-check.d.ts +2 -1
  90. package/dist/server/dev-origin-check.js +2 -1
  91. package/dist/server/dev-server.js +2 -1
  92. package/dist/server/image-local-patterns.d.ts +21 -0
  93. package/dist/server/image-local-patterns.js +59 -0
  94. package/dist/server/image-optimization.d.ts +28 -22
  95. package/dist/server/image-optimization.js +17 -2
  96. package/dist/server/isr-cache.js +4 -1
  97. package/dist/server/metadata-route-response.js +25 -7
  98. package/dist/server/middleware-matcher.js +19 -6
  99. package/dist/server/middleware-runtime.js +6 -2
  100. package/dist/server/multi-stage.d.ts +7 -3
  101. package/dist/server/nitro-public-files-plugin.js +20 -8
  102. package/dist/server/pages-data-route.d.ts +5 -1
  103. package/dist/server/pages-data-route.js +14 -6
  104. package/dist/server/pages-node-compat.d.ts +1 -1
  105. package/dist/server/pages-node-compat.js +2 -0
  106. package/dist/server/pages-page-data.js +2 -1
  107. package/dist/server/pages-page-handler.js +17 -6
  108. package/dist/server/pages-page-response.d.ts +9 -0
  109. package/dist/server/pages-page-response.js +18 -4
  110. package/dist/server/pages-request-pipeline.js +6 -5
  111. package/dist/server/pages-request-stage-entry.js +25 -4
  112. package/dist/server/pages-router-entry.js +3 -2
  113. package/dist/server/prod-server.d.ts +2 -2
  114. package/dist/server/prod-server.js +59 -17
  115. package/dist/server/request-error-log.d.ts +9 -0
  116. package/dist/server/request-error-log.js +13 -0
  117. package/dist/server/request-pipeline.d.ts +14 -4
  118. package/dist/server/request-pipeline.js +33 -15
  119. package/dist/server/response-stage-cacheability.d.ts +7 -0
  120. package/dist/server/response-stage-cacheability.js +6 -5
  121. package/dist/server/rsc-completion-metadata.js +3 -0
  122. package/dist/server/static-file-cache.d.ts +9 -0
  123. package/dist/server/static-file-cache.js +13 -2
  124. package/dist/server/worker-image-optimization.d.ts +2 -2
  125. package/dist/server/worker-image-optimization.js +18 -3
  126. package/dist/server/worker-utils.d.ts +8 -0
  127. package/dist/server/worker-utils.js +19 -2
  128. package/dist/shims/cache-request-state.d.ts +6 -6
  129. package/dist/shims/cache-request-state.js +6 -36
  130. package/dist/shims/cache-runtime.d.ts +3 -1
  131. package/dist/shims/cache-runtime.js +77 -47
  132. package/dist/shims/cache.d.ts +10 -5
  133. package/dist/shims/cache.js +35 -22
  134. package/dist/shims/client-hook-error.d.ts +2 -0
  135. package/dist/shims/client-hook-error.js +16 -1
  136. package/dist/shims/fetch-cache.js +6 -2
  137. package/dist/shims/headers.d.ts +2 -1
  138. package/dist/shims/headers.js +5 -3
  139. package/dist/shims/image-props.d.ts +6 -0
  140. package/dist/shims/image-props.js +39 -3
  141. package/dist/shims/image.js +3 -3
  142. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  143. package/dist/shims/link.js +5 -10
  144. package/dist/shims/navigation.js +2 -3
  145. package/dist/shims/request-state-types.d.ts +2 -1
  146. package/dist/shims/slot.d.ts +9 -2
  147. package/dist/shims/slot.js +76 -11
  148. package/dist/shims/unified-request-context.d.ts +6 -0
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/utils/base-path.d.ts +7 -0
  151. package/dist/utils/base-path.js +11 -1
  152. package/dist/utils/cache-life-profiles.d.ts +33 -0
  153. package/dist/utils/cache-life-profiles.js +111 -0
  154. package/dist/utils/middleware-request-headers.d.ts +14 -3
  155. package/dist/utils/middleware-request-headers.js +54 -7
  156. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
2
+ import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
2
3
  import { cacheabilityManifestPageState, cacheabilityRequestIdentity, cacheabilityRoutePathname } from "./cacheability-manifest.js";
3
4
  import { keepOnlyValidatedRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
4
5
  import { isTrustedPrerenderState } from "./prerender-route-params.js";
@@ -32,9 +33,8 @@ function createSharedAppCacheIdentity(request, props) {
32
33
  }
33
34
  /**
34
35
  * Build the query-free cache identity of a shared App page dispatch. The user
35
- * query leaves both the URL and `resolvedUrl`; the `.rsc` suffix, an RSC
36
- * request's validated `_rsc`, and the render mode stay because they select the
37
- * representation. HTML identities also drop the RSC selector headers.
36
+ * query leaves both the URL and `resolvedUrl`; an RSC request's validated
37
+ * `_rsc` and the render mode stay because they select the representation. HTML identities also drop the RSC selector headers.
38
38
  */
39
39
  function createSharedAppPageCacheIdentity(request, props) {
40
40
  const queryFree = withoutAppPageDispatchQuery(request.url, props);
@@ -49,6 +49,39 @@ function createSharedAppPageCacheIdentity(request, props) {
49
49
  };
50
50
  }
51
51
  /**
52
+ * Build the query-free cache identity of a shared Route Handler or metadata
53
+ * route dispatch. Next.js keys a route handler by its pathname: the query and
54
+ * the RSC selectors pick no representation of a handler's response, and a
55
+ * handler that reads the request's URL or headers is dynamic, so admission
56
+ * refuses its response. Keeping them in the identity would only let a client
57
+ * store one entry per query or selector value. The method, path and route
58
+ * props stay; the selector headers and the props derived from them leave.
59
+ */
60
+ function createSharedAppRouteCacheIdentity(request, props) {
61
+ const url = new URL(request.url);
62
+ url.search = "";
63
+ const headers = new Headers(request.headers);
64
+ for (const name of VINEXT_RSC_VARY_HEADER.split(",")) headers.delete(name.trim());
65
+ const searchIndex = props.resolvedUrl.indexOf("?");
66
+ return {
67
+ ...sharedAppCacheIdentity(url.toString(), {
68
+ headers,
69
+ method: request.method
70
+ }, {
71
+ ...props,
72
+ ...props.kind === "app-metadata" ? {} : {
73
+ interceptionContext: null,
74
+ interceptionId: null
75
+ },
76
+ isRscRequest: false,
77
+ mountedSlotsHeader: null,
78
+ renderMode: APP_RSC_RENDER_MODE_NAVIGATION,
79
+ resolvedUrl: searchIndex === -1 ? props.resolvedUrl : props.resolvedUrl.slice(0, searchIndex)
80
+ }),
81
+ queryFree: true
82
+ };
83
+ }
84
+ /**
52
85
  * Build the cache identity of a shared hybrid Pages page dispatch: the
53
86
  * dispatched request and props under `origin`, so every host that names it
54
87
  * shares one entry.
@@ -141,4 +174,4 @@ function isResponseStageCacheability(value) {
141
174
  return (cacheability.probeMode === null || cacheability.probeMode === "probe" || cacheability.probeMode === "identity") && (cacheability.policyHeaders === null || Array.isArray(cacheability.policyHeaders) && cacheability.policyHeaders.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string")) && (cacheability.representation === void 0 || cacheability.representation === "app-route" || cacheability.representation === "html" || cacheability.representation === "pages-data" || cacheability.representation === "rsc-full" || cacheability.representation === "rsc-loading-shell") && typeof cacheability.resolvedRoutePathname === "string" && cacheability.resolvedRoutePathname.startsWith("/");
142
175
  }
143
176
  //#endregion
144
- export { APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER, APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION, createSharedAppCacheIdentity, createSharedAppPageCacheIdentity, createSharedHybridPagesCacheIdentity, isAppWorkerResponseStageProps, isStaticCandidateAppPageDispatch, prepareSharedAppPageDispatch, withoutAppPageDispatchQuery };
177
+ export { APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER, APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION, createSharedAppCacheIdentity, createSharedAppPageCacheIdentity, createSharedAppRouteCacheIdentity, createSharedHybridPagesCacheIdentity, isAppWorkerResponseStageProps, isStaticCandidateAppPageDispatch, prepareSharedAppPageDispatch, withoutAppPageDispatchQuery };
@@ -3,6 +3,13 @@ type ParallelSlotBindingState = "active" | "default" | "unmatched";
3
3
  export declare function createNestedBfcacheSlotSegmentId(parentSlotId: string, level: number): string;
4
4
  export declare function isNestedBfcacheSlotSegmentId(id: string): boolean;
5
5
  export declare function isNestedBfcacheSlotSegmentIdFor(id: string, parentSlotId: string): boolean;
6
+ /**
7
+ * Activity key for a route segment whose directory has no layout of its own.
8
+ * Next.js keeps a layout-router Activity list at every segment level, implicit
9
+ * layouts included, keyed by the child segment. The tag keeps these keys apart
10
+ * from identity strings, whose kinds never use it, and from BFCache ids.
11
+ */
12
+ export declare function createBfcacheChildSegmentStateKey(segmentPathKey: string): string;
6
13
  export type BfcacheSegmentIdentity = string;
7
14
  export type BfcacheSegmentDescriptor = {
8
15
  kind: "page";
@@ -13,6 +13,15 @@ function isNestedBfcacheSlotSegmentId(id) {
13
13
  function isNestedBfcacheSlotSegmentIdFor(id, parentSlotId) {
14
14
  return isNestedBfcacheSlotSegmentId(id) && id.startsWith(createNestedBfcacheSlotParentPrefix(parentSlotId));
15
15
  }
16
+ /**
17
+ * Activity key for a route segment whose directory has no layout of its own.
18
+ * Next.js keeps a layout-router Activity list at every segment level, implicit
19
+ * layouts included, keyed by the child segment. The tag keeps these keys apart
20
+ * from identity strings, whose kinds never use it, and from BFCache ids.
21
+ */
22
+ function createBfcacheChildSegmentStateKey(segmentPathKey) {
23
+ return JSON.stringify(["segment", segmentPathKey]);
24
+ }
16
25
  function deriveBfcacheSegmentIdentity(descriptor) {
17
26
  switch (descriptor.kind) {
18
27
  case "page":
@@ -49,4 +58,4 @@ function deriveBfcacheSegmentIdentity(descriptor) {
49
58
  }
50
59
  }
51
60
  //#endregion
52
- export { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId, isNestedBfcacheSlotSegmentIdFor };
61
+ export { createBfcacheChildSegmentStateKey, createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId, isNestedBfcacheSlotSegmentIdFor };
@@ -1,6 +1,8 @@
1
1
  import { CdnCacheableHeaderInput, isNonCacheableCacheControl } from "../shims/cdn-cache.js";
2
2
  //#region src/server/cache-control.d.ts
3
3
  export declare const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
4
+ /** The policy Next.js sends on every Server Action response. */
5
+ export declare const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
4
6
  export declare const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
5
7
  export declare const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
6
8
  export declare const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
@@ -2,6 +2,8 @@ import { getCdnCacheAdapter, isNonCacheableCacheControl } from "../shims/cdn-cac
2
2
  import { recordRouteCacheabilityCdnTags } from "../shims/cacheability-classification.js";
3
3
  //#region src/server/cache-control.ts
4
4
  const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
5
+ /** The policy Next.js sends on every Server Action response. */
6
+ const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
5
7
  const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
6
8
  const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
7
9
  const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
@@ -167,4 +169,4 @@ function buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expire
167
169
  return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
168
170
  }
169
171
  //#endregion
170
- export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
172
+ export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, SERVER_ACTION_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
@@ -118,7 +118,7 @@ function cacheabilityRequestIdentity(request, trustedRepresentation) {
118
118
  representation: "pages-data",
119
119
  requestKey
120
120
  };
121
- if (!(request.headers.get("RSC") === "1" || url.pathname.endsWith(".rsc"))) return (request.headers.get("Accept")?.toLowerCase() ?? "").includes("text/html") ? {
121
+ if (!(request.headers.get("RSC") === "1")) return (request.headers.get("Accept")?.toLowerCase() ?? "").includes("text/html") ? {
122
122
  representation: "html",
123
123
  requestKey
124
124
  } : {
@@ -146,7 +146,6 @@ function normalizeCacheabilityRoutePathname(pathname) {
146
146
  }
147
147
  function cacheabilityRoutePathname(pathname, representation) {
148
148
  const normalized = new URL(pathname, "http://vinext.local").pathname;
149
- if (representation === "rsc-full" || representation === "rsc-loading-shell") return normalizeCacheabilityRoutePathname(normalized.endsWith(".rsc") ? normalized.slice(0, -4) : normalized);
150
149
  if (representation !== "pages-data") return normalizeCacheabilityRoutePathname(normalized);
151
150
  const marker = normalized.indexOf("/_next/data/");
152
151
  const restWithBuildId = marker === -1 ? "" : normalized.slice(marker + 12);
@@ -41,7 +41,8 @@ export declare function isCrossSiteNoCorsRequest(secFetchSite: string | null | u
41
41
  * Validate a dev server request from a Node.js IncomingMessage.
42
42
  *
43
43
  * Returns null if the request is allowed, or a reason string if it should be blocked.
44
- * This is used by the Pages Router connect middleware.
44
+ * This is used by the vinext:dev-origin-check connect middleware and the Pages
45
+ * Router handler.
45
46
  */
46
47
  export declare function validateDevRequest(headers: {
47
48
  origin?: string;
@@ -74,7 +74,8 @@ function isCrossSiteNoCorsRequest(secFetchSite, secFetchMode) {
74
74
  * Validate a dev server request from a Node.js IncomingMessage.
75
75
  *
76
76
  * Returns null if the request is allowed, or a reason string if it should be blocked.
77
- * This is used by the Pages Router connect middleware.
77
+ * This is used by the vinext:dev-origin-check connect middleware and the Pages
78
+ * Router handler.
78
79
  */
79
80
  function validateDevRequest(headers, allowedDevOrigins) {
80
81
  if (isCrossSiteNoCorsRequest(headers["sec-fetch-site"], headers["sec-fetch-mode"])) return `cross-site no-cors request blocked`;
@@ -9,6 +9,7 @@ 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";
@@ -918,7 +919,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
918
919
  _renderEnd = now();
919
920
  if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext(null);
920
921
  } catch (e) {
921
- console.error(e);
922
+ logRequestError(e);
922
923
  await reportRequestError(e, {
923
924
  path: url,
924
925
  method: req.method ?? "GET",
@@ -0,0 +1,21 @@
1
+ //#region src/server/image-local-patterns.d.ts
2
+ /**
3
+ * `images.localPatterns` matching for the image optimization endpoint, kept
4
+ * apart from the remote-pattern shim so the request path doesn't load its IP
5
+ * parsing.
6
+ */
7
+ export type LocalPattern = {
8
+ pathname?: string;
9
+ search?: string;
10
+ };
11
+ /**
12
+ * Check whether a local URL matches a single local pattern.
13
+ * Follows the same semantics as Next.js's matchLocalPattern().
14
+ */
15
+ export declare function matchLocalPattern(pattern: LocalPattern, url: URL): boolean;
16
+ /**
17
+ * Check whether a local URL matches any configured local pattern. Like
18
+ * Next.js's hasLocalMatch(), an unset list allows every local URL.
19
+ */
20
+ export declare function hasLocalMatch(localPatterns: LocalPattern[] | undefined, url: URL): boolean;
21
+ //#endregion
@@ -0,0 +1,59 @@
1
+ //#region src/server/image-local-patterns.ts
2
+ const localPathnameRegexCache = /* @__PURE__ */ new Map();
3
+ /**
4
+ * Convert a local pathname glob to a RegExp, reading `*` and `**` like Next.js's
5
+ * picomatch (`makeRe(pathname, { dot: true })`):
6
+ * - a `**` segment matches zero or more whole segments, but needs its own
7
+ * separator right after a leading `/`, and at the end after a segment
8
+ * that ends in `*`;
9
+ * - any other `*` run matches characters within one segment;
10
+ * - a last segment ending in `*` also matches one trailing slash, and a
11
+ * whole last `*` matches one character or more (that slash included).
12
+ * Other picomatch syntax (braces, `?`, classes) is matched literally.
13
+ */
14
+ function localPathnameGlobToRegex(pattern) {
15
+ const cached = localPathnameRegexCache.get(pattern);
16
+ if (cached !== void 0) return cached;
17
+ const segments = pattern.split("/").filter((segment, i, all) => !(segment === "**" && all[i - 1] === "**"));
18
+ let regexStr = "^";
19
+ for (let i = 0; i < segments.length; i++) {
20
+ const segment = segments[i];
21
+ const previous = segments[i - 1];
22
+ const isLast = i === segments.length - 1;
23
+ if (segment === "**") {
24
+ if (i === 0) regexStr += isLast ? ".*" : "(?:.*/)?";
25
+ else if (previous === "" || isLast && previous.endsWith("*")) regexStr += "/.*";
26
+ else regexStr += "(?:/.*)?";
27
+ continue;
28
+ }
29
+ if (i > 0 && !(i === 1 && previous === "**")) regexStr += "/";
30
+ if (segment === "*" && isLast) {
31
+ regexStr += "(?:[^/]+/?|/)";
32
+ continue;
33
+ }
34
+ regexStr += segment.split(/\*+/).map((literal) => literal.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
35
+ if (isLast && segment.endsWith("*")) regexStr += "/?";
36
+ }
37
+ regexStr += "$";
38
+ const re = new RegExp(regexStr);
39
+ localPathnameRegexCache.set(pattern, re);
40
+ return re;
41
+ }
42
+ /**
43
+ * Check whether a local URL matches a single local pattern.
44
+ * Follows the same semantics as Next.js's matchLocalPattern().
45
+ */
46
+ function matchLocalPattern(pattern, url) {
47
+ if (pattern.search !== void 0 && pattern.search !== url.search) return false;
48
+ return localPathnameGlobToRegex(pattern.pathname ?? "**").test(url.pathname);
49
+ }
50
+ /**
51
+ * Check whether a local URL matches any configured local pattern. Like
52
+ * Next.js's hasLocalMatch(), an unset list allows every local URL.
53
+ */
54
+ function hasLocalMatch(localPatterns, url) {
55
+ if (!localPatterns) return true;
56
+ return localPatterns.some((p) => matchLocalPattern(p, url));
57
+ }
58
+ //#endregion
59
+ export { hasLocalMatch, matchLocalPattern };
@@ -1,22 +1,5 @@
1
+ import { LocalPattern } from "./image-local-patterns.js";
1
2
  //#region src/server/image-optimization.d.ts
2
- /**
3
- * Image optimization request handler.
4
- *
5
- * Handles `/_next/image?url=...&w=...&q=...` requests. In production
6
- * on Cloudflare Workers, uses the Images binding (`env.IMAGES`) to
7
- * resize and transcode on the fly. On other runtimes (Node.js dev/prod
8
- * server), serves the original file as a passthrough with appropriate
9
- * Cache-Control headers.
10
- *
11
- * Format negotiation: inspects the `Accept` header and serves AVIF, WebP,
12
- * or JPEG depending on client support.
13
- *
14
- * Security: All image responses include Content-Security-Policy and
15
- * X-Content-Type-Options headers to prevent XSS via SVG or Content-Type
16
- * spoofing. SVG content is blocked by default (following Next.js behavior).
17
- * When `dangerouslyAllowSVG` is enabled in next.config.js, SVGs are served
18
- * as-is (no transformation) with security headers applied.
19
- */
20
3
  /** The pathname that triggers image optimization (matches Next.js). */
21
4
  export declare const IMAGE_OPTIMIZATION_PATH = "/_next/image";
22
5
  /**
@@ -49,11 +32,22 @@ export type ImageConfig = {
49
32
  /** Allowed fixed-image widths. Defaults to Next.js image sizes. */
50
33
  imageSizes?: number[];
51
34
  /**
52
- * Allowed output qualities. When unset, any quality from 1-100 is permitted
53
- * (matches Next.js: an unset `images.qualities` is not restricted to a single
54
- * value). When set, only the listed qualities are accepted.
35
+ * Allowed output qualities. When set, only the listed qualities are
36
+ * accepted, as in Next.js. When unset, any quality from 1-100 is accepted.
37
+ *
38
+ * Intentional divergence from Next.js 16, which defaults this to `[75]`:
39
+ * vinext keeps the Next.js 14/15 behaviour for an unset list so apps that
40
+ * adopt vinext from those versions and pass `quality={N}` keep working
41
+ * instead of getting 400s. Apps opt into Next.js 16's strict list by
42
+ * configuring `images.qualities`.
55
43
  */
56
44
  qualities?: number[];
45
+ /**
46
+ * Local sources the endpoint may serve, resolved like Next.js: an app list
47
+ * gains the static-import pattern, and an unset list becomes
48
+ * DEFAULT_LOCAL_PATTERNS when checked.
49
+ */
50
+ localPatterns?: LocalPattern[];
57
51
  /** Allow SVG through the image optimization endpoint. Default: false. */
58
52
  dangerouslyAllowSVG?: boolean;
59
53
  /**
@@ -80,10 +74,12 @@ export declare const DEFAULT_DEVICE_SIZES: number[];
80
74
  export declare const DEFAULT_IMAGE_SIZES: number[];
81
75
  export type ParseImageParamsOptions = {
82
76
  isDev?: boolean;
77
+ /** `images.localPatterns`; DEFAULT_LOCAL_PATTERNS when unset. */
78
+ localPatterns?: LocalPattern[];
83
79
  };
84
80
  export declare function resolveDevImageRedirect(requestUrl: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): string | null;
85
81
  export type ImageOptimizationSignal = {
86
- /** Middleware response headers to send with the image. */
82
+ /** next.config and middleware response headers to send with the image. */
87
83
  middlewareHeaders: Headers;
88
84
  /** The request headers after middleware's overrides, when it set any. */
89
85
  requestHeaders: Headers | null;
@@ -120,6 +116,11 @@ export declare function isImageSourceBodyStatus(status: number): boolean;
120
116
  */
121
117
  export declare const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
122
118
  export declare function invalidImageSourceResponse(): Response;
119
+ /**
120
+ * Next.js applies this when `images.localPatterns` is unset: every local path,
121
+ * but no query string.
122
+ */
123
+ export declare const DEFAULT_LOCAL_PATTERNS: LocalPattern[];
123
124
  /**
124
125
  * Parse and validate image optimization query parameters.
125
126
  * Returns null if the request is malformed.
@@ -143,6 +144,11 @@ export declare function negotiateImageFormat(acceptHeader: string | null): strin
143
144
  * Optimized images are immutable because the URL encodes the transform params.
144
145
  */
145
146
  export declare const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
147
+ /**
148
+ * Next.js's Cache-Control for an image whose source can change at its URL
149
+ * (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
150
+ */
151
+ export declare const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
146
152
  /**
147
153
  * Content-Security-Policy for image optimization responses.
148
154
  * Blocks script execution and framing to prevent XSS via SVG or other
@@ -1,5 +1,6 @@
1
1
  import { badRequestResponse } from "./http-error-responses.js";
2
2
  import { setCacheStateHeaders } from "./cache-headers.js";
3
+ import { hasLocalMatch } from "./image-local-patterns.js";
3
4
  //#region src/server/image-optimization.ts
4
5
  /**
5
6
  * Image optimization request handler.
@@ -166,6 +167,14 @@ function invalidImageSourceResponse() {
166
167
  }
167
168
  const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
168
169
  /**
170
+ * Next.js applies this when `images.localPatterns` is unset: every local path,
171
+ * but no query string.
172
+ */
173
+ const DEFAULT_LOCAL_PATTERNS = [{
174
+ pathname: "**",
175
+ search: ""
176
+ }];
177
+ /**
169
178
  * Parse and validate image optimization query parameters.
170
179
  * Returns null if the request is malformed.
171
180
  *
@@ -204,6 +213,7 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
204
213
  if (resolved.origin !== base) return null;
205
214
  if (resolved.pathname.startsWith("//")) return null;
206
215
  if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
216
+ if (!options.isDev && !hasLocalMatch(options.localPatterns ?? DEFAULT_LOCAL_PATTERNS, resolved)) return null;
207
217
  } catch {
208
218
  return null;
209
219
  }
@@ -229,6 +239,11 @@ function negotiateImageFormat(acceptHeader) {
229
239
  */
230
240
  const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
231
241
  /**
242
+ * Next.js's Cache-Control for an image whose source can change at its URL
243
+ * (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
244
+ */
245
+ const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
246
+ /**
232
247
  * Content-Security-Policy for image optimization responses.
233
248
  * Blocks script execution and framing to prevent XSS via SVG or other
234
249
  * active content that might be served through the image endpoint.
@@ -312,7 +327,7 @@ function createPassthroughImageResponse(source, config) {
312
327
  * cache headers.
313
328
  */
314
329
  async function handleImageOptimization(request, handlers, allowedWidths, imageConfig) {
315
- const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities);
330
+ const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
316
331
  if (!params) return badRequestResponse();
317
332
  const { imageUrl, width, quality } = params;
318
333
  const source = await handlers.fetchAsset(imageUrl, request);
@@ -397,4 +412,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
397
412
  }, allowedWidths, imageConfig);
398
413
  }
399
414
  //#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 };
415
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, DEFAULT_LOCAL_PATTERNS, 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 };
@@ -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";
@@ -144,7 +145,9 @@ function triggerBackgroundRegeneration(key, renderFn, errorContext) {
144
145
  if (!getCdnCacheAdapter().ownsBackgroundRevalidation) return;
145
146
  if (pendingRegenerations.has(key)) return;
146
147
  const promise = renderFn().catch(async (err) => {
147
- 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);
148
151
  if (errorContext && (errorContext.shouldReport?.(err) ?? true)) await reportRequestError(err, {
149
152
  path: key,
150
153
  method: "GET",
@@ -14,10 +14,10 @@ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
14
14
  import { getActiveDraftModeState, getHeadersContext, hasDraftModeCookieHeader, replaceHeadersContext } from "../shims/headers.js";
15
15
  import { _consumeRequestScopedCacheLife, _isOnDemandRevalidate } from "../shims/cache-request-state.js";
16
16
  import { ensureFetchPatch, getCollectedFetchTags, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
17
+ import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
17
18
  import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
18
19
  import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse } from "./app-route-handler-response.js";
19
20
  import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
20
- import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.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";
@@ -504,20 +504,34 @@ async function writeMetadataRouteMiss(options, route, rendered, cachedEntry) {
504
504
  async function handleMetadataRouteRequest(options) {
505
505
  let urlParts;
506
506
  const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
507
+ const routePathname = options.routePathname ?? options.cleanPathname;
508
+ let routeUrlParts;
509
+ const getRouteUrlParts = () => routeUrlParts ??= routePathname.split("/").filter(Boolean);
510
+ let cacheOptions = options;
511
+ const skipDecodedOwner = () => {
512
+ cacheOptions = {
513
+ ...options,
514
+ isrGet: void 0,
515
+ isrRouteKey: void 0,
516
+ isrSet: void 0
517
+ };
518
+ };
507
519
  for (const route of options.metadataRoutes) {
508
520
  const functions = getMetadataRouteFunctions(route);
509
521
  if (route.type === "sitemap" && route.isDynamic) {
510
522
  if (functions.generateSitemaps) {
511
- if (isGeneratedSitemapPath(route, options.cleanPathname)) {
523
+ if (!isGeneratedSitemapPath(route, options.cleanPathname)) continue;
524
+ if (!isGeneratedSitemapPath(route, routePathname)) skipDecodedOwner();
525
+ else {
512
526
  beginMetadataRouteCacheability(route);
513
527
  const render = async () => {
514
528
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
515
529
  const response = await handleGeneratedSitemap(route, options.cleanPathname, functions);
516
530
  return response ? captureRenderedMetadataRoute(response, route, options.cleanPathname) : null;
517
531
  };
518
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
532
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
519
533
  if (cached.response) return cached.response;
520
- const response = await renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
534
+ const response = await renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
521
535
  if (response) return response;
522
536
  }
523
537
  continue;
@@ -525,8 +539,12 @@ async function handleMetadataRouteRequest(options) {
525
539
  }
526
540
  const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
527
541
  if (!match) continue;
542
+ if (!matchMetadataRoute(route, routePathname, functions, getRouteUrlParts)) {
543
+ skipDecodedOwner();
544
+ continue;
545
+ }
528
546
  beginMetadataRouteCacheability(route);
529
- const rawParams = route.patternParts ? metadataRouteRawParams(route, match, options.routePathname ?? options.cleanPathname) : Object.create(null);
547
+ const rawParams = route.patternParts ? metadataRouteRawParams(route, match, routePathname) : Object.create(null);
530
548
  if (route.patternParts) {
531
549
  const runtimeParams = { ...rawParams };
532
550
  decodeMatchedParams(runtimeParams);
@@ -536,7 +554,7 @@ async function handleMetadataRouteRequest(options) {
536
554
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
537
555
  return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
538
556
  };
539
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
557
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
540
558
  if (cached.response) return cached.response;
541
559
  if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
542
560
  const validationParams = { ...rawParams };
@@ -552,7 +570,7 @@ async function handleMetadataRouteRequest(options) {
552
570
  });
553
571
  if (dynamicParamsResponse) return dynamicParamsResponse;
554
572
  }
555
- return renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
573
+ return renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
556
574
  }
557
575
  return null;
558
576
  }
@@ -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";
@@ -77,14 +77,18 @@ export type VinextResponseStageDispatchOptions = {
77
77
  *
78
78
  * Under `responseStageCacheIdentity: "query-free"`, an App page identity
79
79
  * also drops the user query from the URL and `resolvedUrl`, keeping
80
- * framework representation selectors: the `.rsc` suffix, the render mode,
81
- * and, for RSC requests only, the one validated `_rsc` value. That is safe
80
+ * framework representation selectors: the render mode and, for RSC
81
+ * requests only, the one validated `_rsc` value. That is safe
82
82
  * only because completed-response admission refuses a cacheable App page
83
83
  * response unless its render proved it never read `searchParams`. The
84
84
  * dispatched request still carries the real query for the render itself. A
85
85
  * page identity keeps the query when a `next.config` public cache policy
86
86
  * applies (cached per full URL, as Next.js CDN caching is), and for
87
- * interception and mounted-slot dispatches.
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.
88
92
  */
89
93
  cacheIdentity?: {
90
94
  props: unknown;