vinext 1.4.1 → 1.4.3

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 (143) hide show
  1. package/README.md +5 -2
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/build/prerender-paths.d.ts +11 -1
  4. package/dist/build/prerender-paths.js +36 -10
  5. package/dist/check.js +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/client/dev-error-overlay.d.ts +2 -1
  8. package/dist/client/dev-error-overlay.js +8 -3
  9. package/dist/config/config-matchers.js +8 -4
  10. package/dist/config/destination-params.js +5 -2
  11. package/dist/config/next-config.d.ts +25 -1
  12. package/dist/config/next-config.js +31 -1
  13. package/dist/entries/app-rsc-entry.js +65 -30
  14. package/dist/entries/app-rsc-manifest.js +4 -0
  15. package/dist/entries/pages-server-entry.js +19 -4
  16. package/dist/index.js +37 -37
  17. package/dist/init-cloudflare.js +18 -2
  18. package/dist/init-platform.js +2 -2
  19. package/dist/init.js +42 -5
  20. package/dist/plugins/css-target.d.ts +18 -0
  21. package/dist/plugins/css-target.js +93 -0
  22. package/dist/routing/app-route-graph.d.ts +10 -0
  23. package/dist/routing/app-route-graph.js +15 -1
  24. package/dist/server/api-handler.d.ts +2 -0
  25. package/dist/server/api-handler.js +10 -8
  26. package/dist/server/app-action-forwarding.js +3 -1
  27. package/dist/server/app-browser-entry.js +37 -41
  28. package/dist/server/app-browser-error.d.ts +1 -1
  29. package/dist/server/app-browser-error.js +13 -4
  30. package/dist/server/app-browser-supplemental-refresh.d.ts +13 -0
  31. package/dist/server/app-browser-supplemental-refresh.js +36 -4
  32. package/dist/server/app-elements.d.ts +3 -0
  33. package/dist/server/app-elements.js +14 -1
  34. package/dist/server/app-fallback-renderer.js +2 -2
  35. package/dist/server/app-middleware.d.ts +2 -0
  36. package/dist/server/app-middleware.js +1 -0
  37. package/dist/server/app-optimistic-routing.d.ts +12 -1
  38. package/dist/server/app-optimistic-routing.js +38 -16
  39. package/dist/server/app-page-boundary-render.js +4 -2
  40. package/dist/server/app-page-cache-render.js +1 -1
  41. package/dist/server/app-page-dispatch.d.ts +11 -1
  42. package/dist/server/app-page-dispatch.js +78 -31
  43. package/dist/server/app-page-element-builder.d.ts +11 -20
  44. package/dist/server/app-page-element-builder.js +39 -50
  45. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  46. package/dist/server/app-page-intercept-segment-config.js +90 -0
  47. package/dist/server/app-page-navigation-params.d.ts +23 -0
  48. package/dist/server/app-page-navigation-params.js +49 -0
  49. package/dist/server/app-page-render.d.ts +5 -2
  50. package/dist/server/app-page-render.js +17 -3
  51. package/dist/server/app-page-request.d.ts +24 -2
  52. package/dist/server/app-page-request.js +10 -10
  53. package/dist/server/app-page-route-wiring.d.ts +17 -5
  54. package/dist/server/app-page-route-wiring.js +19 -13
  55. package/dist/server/app-page-segment-plan.js +9 -1
  56. package/dist/server/app-page-segment-state.d.ts +9 -0
  57. package/dist/server/app-page-segment-state.js +54 -1
  58. package/dist/server/app-pages-bridge.d.ts +6 -0
  59. package/dist/server/app-pages-bridge.js +8 -3
  60. package/dist/server/app-request-stage-independent-entry.js +2 -3
  61. package/dist/server/app-response-stage-entry.js +1 -0
  62. package/dist/server/app-route-module-loader.d.ts +2 -0
  63. package/dist/server/app-route-module-loader.js +1 -0
  64. package/dist/server/app-router-entry.js +2 -3
  65. package/dist/server/app-rsc-cache-busting.d.ts +8 -0
  66. package/dist/server/app-rsc-cache-busting.js +13 -1
  67. package/dist/server/app-rsc-combined-handler.js +2 -1
  68. package/dist/server/app-rsc-handler.d.ts +14 -1
  69. package/dist/server/app-rsc-handler.js +111 -62
  70. package/dist/server/app-rsc-response-finalizer.d.ts +18 -3
  71. package/dist/server/app-rsc-response-finalizer.js +61 -7
  72. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  73. package/dist/server/app-rsc-route-matching.js +42 -18
  74. package/dist/server/app-server-action-execution.js +16 -10
  75. package/dist/server/app-ssr-entry.js +8 -0
  76. package/dist/server/cache-control.d.ts +2 -0
  77. package/dist/server/cache-control.js +3 -1
  78. package/dist/server/config-headers.d.ts +7 -0
  79. package/dist/server/config-headers.js +20 -1
  80. package/dist/server/image-animation.d.ts +9 -0
  81. package/dist/server/image-animation.js +118 -0
  82. package/dist/server/image-local-patterns.d.ts +21 -0
  83. package/dist/server/image-local-patterns.js +59 -0
  84. package/dist/server/image-optimization.d.ts +48 -27
  85. package/dist/server/image-optimization.js +316 -22
  86. package/dist/server/image-source-reader.d.ts +34 -0
  87. package/dist/server/image-source-reader.js +126 -0
  88. package/dist/server/metadata-route-response.d.ts +6 -1
  89. package/dist/server/metadata-route-response.js +46 -10
  90. package/dist/server/middleware-matcher.d.ts +1 -0
  91. package/dist/server/middleware-matcher.js +1 -1
  92. package/dist/server/middleware-runtime.d.ts +5 -0
  93. package/dist/server/middleware-runtime.js +48 -15
  94. package/dist/server/middleware.d.ts +1 -1
  95. package/dist/server/middleware.js +2 -1
  96. package/dist/server/nitro-public-files-plugin.js +20 -8
  97. package/dist/server/nitro-request-target-plugin.d.ts +17 -0
  98. package/dist/server/nitro-request-target-plugin.js +22 -0
  99. package/dist/server/open-redirect.d.ts +2 -1
  100. package/dist/server/open-redirect.js +2 -1
  101. package/dist/server/pages-client-assets.d.ts +1 -0
  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-i18n.d.ts +7 -0
  105. package/dist/server/pages-i18n.js +14 -1
  106. package/dist/server/pages-node-compat.js +7 -3
  107. package/dist/server/pages-page-handler.js +4 -4
  108. package/dist/server/pages-request-pipeline.d.ts +9 -0
  109. package/dist/server/pages-request-pipeline.js +35 -15
  110. package/dist/server/pages-request-stage-entry.js +8 -2
  111. package/dist/server/pages-router-entry.js +3 -0
  112. package/dist/server/prod-server.d.ts +1 -1
  113. package/dist/server/prod-server.js +64 -62
  114. package/dist/server/request-pipeline.d.ts +5 -0
  115. package/dist/server/request-pipeline.js +11 -1
  116. package/dist/server/response-stage-cacheability.d.ts +7 -0
  117. package/dist/server/response-stage-cacheability.js +6 -5
  118. package/dist/server/srvx-middleware-origin.d.ts +13 -0
  119. package/dist/server/srvx-middleware-origin.js +19 -0
  120. package/dist/server/srvx-request-target.d.ts +12 -0
  121. package/dist/server/srvx-request-target.js +20 -0
  122. package/dist/server/static-file-cache.js +10 -22
  123. package/dist/server/worker-image-optimization.d.ts +10 -3
  124. package/dist/server/worker-image-optimization.js +43 -9
  125. package/dist/server/worker-revalidation-context.js +1 -0
  126. package/dist/server/worker-utils.d.ts +8 -0
  127. package/dist/server/worker-utils.js +19 -2
  128. package/dist/shims/error-boundary.d.ts +6 -2
  129. package/dist/shims/error-boundary.js +4 -2
  130. package/dist/shims/image-props.d.ts +6 -0
  131. package/dist/shims/image-props.js +39 -3
  132. package/dist/shims/image.js +3 -3
  133. package/dist/shims/request-context.d.ts +3 -1
  134. package/dist/shims/slot.d.ts +3 -0
  135. package/dist/shims/slot.js +67 -9
  136. package/dist/shims/url-utils.js +6 -1
  137. package/dist/utils/base-path.d.ts +7 -0
  138. package/dist/utils/base-path.js +11 -1
  139. package/dist/utils/client-entry-manifest.d.ts +5 -0
  140. package/dist/utils/client-entry-manifest.js +1 -0
  141. package/dist/utils/client-runtime-metadata.d.ts +5 -0
  142. package/dist/utils/client-runtime-metadata.js +33 -7
  143. package/package.json +1 -1
@@ -8,6 +8,19 @@ function readHeader(headers, name) {
8
8
  return direct;
9
9
  }
10
10
  const normalizeHostname = normalizeDomainHostname;
11
+ const LOCALE_REDIRECT = Symbol.for("vinext.pages-locale-redirect");
12
+ /**
13
+ * Mark a locale-detection redirect. Next.js answers it before next.config
14
+ * headers are staged, so it goes out without them. The global symbol crosses
15
+ * the App Router / Pages Router module graph boundary.
16
+ */
17
+ function markLocaleRedirect(response) {
18
+ if (!isLocaleRedirect(response)) Object.defineProperty(response, LOCALE_REDIRECT, { value: true });
19
+ return response;
20
+ }
21
+ function isLocaleRedirect(response) {
22
+ return LOCALE_REDIRECT in response;
23
+ }
11
24
  /**
12
25
  * Return the configured locale that the first pathname segment names, compared
13
26
  * case-insensitively (`/EN/about` -> `"en"`), or `undefined` when there is none.
@@ -259,4 +272,4 @@ function resolvePagesI18nRequest(url, i18nConfig, headers, hostname, basePath =
259
272
  };
260
273
  }
261
274
  //#endregion
262
- export { detectDomainLocale, detectLocaleFromAcceptLanguage, detectPathnameLocale, extractLocaleFromUrl, getLocaleRedirect, normalizeDefaultLocalePathname, parseCookieLocaleFromHeader, resolvePagesI18nRequest, shouldRestoreLocaleRootTrailingSlash, stripI18nLocaleForApiRoute };
275
+ export { detectDomainLocale, detectLocaleFromAcceptLanguage, detectPathnameLocale, extractLocaleFromUrl, getLocaleRedirect, isLocaleRedirect, markLocaleRedirect, normalizeDefaultLocalePathname, parseCookieLocaleFromHeader, resolvePagesI18nRequest, shouldRestoreLocaleRootTrailingSlash, stripI18nLocaleForApiRoute };
@@ -201,10 +201,14 @@ var PagesResponseStream = class extends Writable {
201
201
  return this;
202
202
  }
203
203
  json(data) {
204
- this.resHeaders["content-type"] = "application/json";
204
+ this.resHeaders["content-type"] = "application/json; charset=utf-8";
205
205
  this.end(JSON.stringify(data));
206
206
  }
207
207
  send(data) {
208
+ if (data === null || data === void 0) {
209
+ this.end();
210
+ return;
211
+ }
208
212
  if (NO_BODY_RESPONSE_STATUSES.has(this.resStatusCode)) {
209
213
  this.end();
210
214
  return;
@@ -220,8 +224,8 @@ var PagesResponseStream = class extends Writable {
220
224
  this.end(data);
221
225
  return;
222
226
  }
223
- if (typeof data === "object" && data !== null) {
224
- this.resHeaders["content-type"] = "application/json";
227
+ if (typeof data === "object" || typeof data === "number" || typeof data === "boolean") {
228
+ this.resHeaders["content-type"] = "application/json; charset=utf-8";
225
229
  this.end(JSON.stringify(data));
226
230
  return;
227
231
  }
@@ -5,7 +5,7 @@ import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_CACHE_HEADER,
5
5
  import { normalizePathnameForRouteMatch } from "../routing/utils.js";
6
6
  import { patternToNextFormat } from "../routing/route-validation.js";
7
7
  import { getRequestExecutionContext } from "../shims/request-context.js";
8
- import { extractLocaleFromUrl, resolvePagesI18nRequest } from "./pages-i18n.js";
8
+ import { extractLocaleFromUrl, markLocaleRedirect, resolvePagesI18nRequest } from "./pages-i18n.js";
9
9
  import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
10
10
  import { reportRequestError } from "./instrumentation.js";
11
11
  import { logRequestError } from "./request-error-log.js";
@@ -154,7 +154,7 @@ function createPagesPageHandler(opts) {
154
154
  const originalRequestPathAndSearch = originalRequestUrl.pathname + originalRequestUrl.search;
155
155
  let dataRequestPathname = null;
156
156
  let dataRequestSearch = "";
157
- const initialDataNorm = normalizePagesDataRequest(request, buildId, vinextConfig.basePath, hasMiddleware && vinextConfig.trailingSlash);
157
+ const initialDataNorm = normalizePagesDataRequest(request, buildId, "", hasMiddleware && vinextConfig.trailingSlash);
158
158
  if (!isDataReq) {
159
159
  if (initialDataNorm.notFoundResponse) return initialDataNorm.notFoundResponse;
160
160
  if (initialDataNorm.isDataReq) {
@@ -195,10 +195,10 @@ function createPagesPageHandler(opts) {
195
195
  const currentDefaultLocale = i18nConfig ? localeInfo.domainLocale ? localeInfo.domainLocale.defaultLocale : i18nConfig.defaultLocale : void 0;
196
196
  const domainLocales = i18nConfig ? i18nConfig.domains : void 0;
197
197
  const pageIsrCacheKey = isrCacheKeyForRequest(i18nConfig ? localeInfo.domainLocale ? JSON.stringify([String(localeInfo.domainLocale.domain).toLowerCase(), locale]) : "locale:" + String(locale) : null);
198
- if (localeInfo.redirectUrl) return new Response(null, {
198
+ if (localeInfo.redirectUrl) return markLocaleRedirect(new Response(null, {
199
199
  status: 307,
200
200
  headers: { Location: localeInfo.redirectUrl }
201
- });
201
+ }));
202
202
  let match = options && options.__forcedRoute ? {
203
203
  route: options.__forcedRoute,
204
204
  params: {}
@@ -15,6 +15,14 @@ type PageRouteMatch = {
15
15
  dataKind?: PagesRouteDataKind;
16
16
  };
17
17
  };
18
+ /**
19
+ * Whether a filesystem route may serve `requestPathname` from the client
20
+ * build directory. Like Next.js, only a public file or a build asset resolves
21
+ * there: the directory also holds build metadata and private artifacts. A
22
+ * direct GET/HEAD reaches this route only for a public file, since servers
23
+ * answer existing build assets before middleware.
24
+ */
25
+ export declare function isServableFilesystemRoute(requestPathname: string, phase: FilesystemRoutePhase, method: string, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string, pathnameHasBasePath?: boolean): boolean;
18
26
  export declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string, pathnameHasBasePath?: boolean): Promise<Response | false>;
19
27
  export type MiddlewareResult = {
20
28
  continue: boolean;
@@ -77,6 +85,7 @@ export type PagesPipelineDeps = {
77
85
  runMiddleware?: ((request: Request, ctx: unknown, opts: {
78
86
  isDataRequest: boolean;
79
87
  isStaleDataRequest?: boolean;
88
+ restoreLocaleRootTrailingSlash?: boolean;
80
89
  }) => Promise<MiddlewareResult>) | null;
81
90
  /** Original URL presented to middleware when URL normalization is disabled. */
82
91
  middlewareRequest?: Request;
@@ -1,7 +1,7 @@
1
1
  import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
2
2
  import { requestContextFromRequest } from "../config/request-context.js";
3
3
  import { isExternalUrl } from "../utils/external-url.js";
4
- import { addBasePathToPathname } from "../utils/base-path.js";
4
+ import { restoreBasePath } from "../utils/base-path.js";
5
5
  import { VINEXT_PRERENDER_REWRITTEN_HEADER } from "./headers.js";
6
6
  import { methodNotAllowedResponse, notFoundStaticAssetResponse, sanitizeMethodNotAllowedHeaders } from "./http-error-responses.js";
7
7
  import { cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
@@ -14,10 +14,12 @@ import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
14
14
  import { markRouteCacheabilityDynamic } from "../shims/cacheability-classification.js";
15
15
  import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
16
16
  import { mergeRewriteQuery } from "../utils/query.js";
17
+ import { isImageOptimizationPath } from "./image-optimization.js";
17
18
  import { setFrameworkRequestRoute } from "./request-tracing.js";
18
19
  import { withExternalRewriteResponseHeaders } from "./middleware-response-headers.js";
19
20
  import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
20
- import { finalizeMissingStaticAssetResponse, mergeHeaders } from "./worker-utils.js";
21
+ import { finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders } from "./worker-utils.js";
22
+ import { publicFileRouteKey } from "../utils/public-file-path.js";
21
23
  //#region src/server/pages-request-pipeline.ts
22
24
  function ruleUsesUnkeyedRequestCondition(rule) {
23
25
  return [...rule.has ?? [], ...rule.missing ?? []].some((condition) => condition.type === "header" || condition.type === "cookie" || condition.type === "host");
@@ -34,24 +36,41 @@ function headersFromRecord(record) {
34
36
  else headers.set(name, value);
35
37
  return headers;
36
38
  }
37
- async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset, publicFiles, basePath = "", assetPathPrefix = "", pathnameHasBasePath = true) {
38
- const isRetrievalMethod = request.method === "GET" || request.method === "HEAD";
39
- const assetUrl = new URL(request.url);
39
+ /**
40
+ * Whether a filesystem route may serve `requestPathname` from the client
41
+ * build directory. Like Next.js, only a public file or a build asset resolves
42
+ * there: the directory also holds build metadata and private artifacts. A
43
+ * direct GET/HEAD reaches this route only for a public file, since servers
44
+ * answer existing build assets before middleware.
45
+ */
46
+ function isServableFilesystemRoute(requestPathname, phase, method, publicFiles, basePath = "", assetPathPrefix = "", pathnameHasBasePath = true) {
47
+ const assetUrl = new URL("http://localhost");
40
48
  assetUrl.pathname = requestPathname;
41
- assetUrl.search = "";
42
49
  const decodedAssetUrl = new URL(assetUrl);
43
50
  let publicFileKey;
51
+ let servedPathname;
52
+ let servedFileKey;
44
53
  try {
45
54
  decodedAssetUrl.pathname = decodeURIComponent(assetUrl.pathname);
46
55
  publicFileKey = normalizePathnameForRouteMatchStrict(assetUrl.pathname);
56
+ servedPathname = decodeURIComponent(requestPathname);
57
+ servedFileKey = publicFileRouteKey(servedPathname);
47
58
  } catch {
48
59
  return false;
49
60
  }
50
- if (!pathnameHasBasePath && !isAssetPrefixBuildAsset(decodedAssetUrl.pathname, assetPathPrefix)) return false;
51
- const isPublicFile = pathnameHasBasePath && publicFiles.has(publicFileKey);
61
+ const decodedPathname = decodedAssetUrl.pathname;
62
+ if (!pathnameHasBasePath && !isAssetPrefixBuildAsset(decodedPathname, assetPathPrefix)) return false;
63
+ const isPublicFile = pathnameHasBasePath && publicFiles.has(publicFileKey) && publicFiles.has(servedFileKey);
52
64
  if (!isPublicFile && (requestPathname === "/api" || requestPathname.startsWith("/api/"))) return false;
53
- if (phase === "direct" && isRetrievalMethod && !isPublicFile) return false;
54
- if (!isPublicFile && !isNextStaticPath(decodedAssetUrl.pathname, basePath, assetPathPrefix)) return false;
65
+ if (phase === "direct" && (method === "GET" || method === "HEAD") && !isPublicFile) return false;
66
+ return isPublicFile || isNextStaticPath(decodedPathname, basePath, assetPathPrefix) && isNextStaticPath(servedPathname, basePath, assetPathPrefix);
67
+ }
68
+ async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset, publicFiles, basePath = "", assetPathPrefix = "", pathnameHasBasePath = true) {
69
+ const isRetrievalMethod = request.method === "GET" || request.method === "HEAD";
70
+ const assetUrl = new URL(request.url);
71
+ assetUrl.pathname = requestPathname;
72
+ assetUrl.search = "";
73
+ if (!isServableFilesystemRoute(requestPathname, phase, request.method, publicFiles, basePath, assetPathPrefix, pathnameHasBasePath)) return false;
55
74
  const response = await fetchAsset(isRetrievalMethod ? new Request(assetUrl, request) : new Request(assetUrl, {
56
75
  method: "HEAD",
57
76
  headers: request.headers
@@ -80,7 +99,7 @@ function wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) {
80
99
  if (!hadBasePath || !basePath) return runMiddleware;
81
100
  return (request, ctx, opts) => {
82
101
  const mwUrl = new URL(request.url);
83
- mwUrl.pathname = addBasePathToPathname(mwUrl.pathname, basePath);
102
+ mwUrl.pathname = restoreBasePath(mwUrl.pathname, basePath);
84
103
  return runMiddleware(cloneRequestWithUrl(request, mwUrl.toString()), ctx, opts);
85
104
  };
86
105
  }
@@ -166,7 +185,7 @@ async function runPagesRequest(request, deps) {
166
185
  const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase, resolvedUrl, request, resolvedPathnameHasBasePath);
167
186
  if (served instanceof Response) {
168
187
  const isStaticMethodNotAllowed = served.status === 405 && served.headers.get("allow") === "GET, HEAD";
169
- const response = mergeHeaders(served, middlewareHeaders, isStaticMethodNotAllowed || !served.ok || served.status === 206 ? void 0 : middlewareStatus);
188
+ const response = (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname) ? mergeHeaders : mergeStaticFileHeaders)(served, middlewareHeaders, isStaticMethodNotAllowed || !served.ok || served.status === 206 ? void 0 : middlewareStatus);
170
189
  if (isStaticMethodNotAllowed) sanitizeMethodNotAllowedHeaders(response.headers, "GET, HEAD");
171
190
  return {
172
191
  type: "response",
@@ -178,7 +197,8 @@ async function runPagesRequest(request, deps) {
178
197
  if (!isOnDemandRevalidate && typeof deps.runMiddleware === "function") {
179
198
  const result = await deps.runMiddleware(deps.middlewareRequest ?? request, deps.ctx ?? null, {
180
199
  isDataRequest,
181
- isStaleDataRequest: isDataRequest && !!deps.dataNotFoundResponse
200
+ isStaleDataRequest: isDataRequest && !!deps.dataNotFoundResponse,
201
+ restoreLocaleRootTrailingSlash: deps.restoreLocaleRootTrailingSlash
182
202
  });
183
203
  if (deps.recordCacheability !== false && result.pathnameEligible) markRouteCacheabilityDynamic("middleware can match this pathname");
184
204
  if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
@@ -306,7 +326,7 @@ async function runPagesRequest(request, deps) {
306
326
  let apiRequest = request;
307
327
  if (basePath && hadBasePath) {
308
328
  const apiRequestUrl = new URL(request.url);
309
- apiRequestUrl.pathname = addBasePathToPathname(apiRequestUrl.pathname, basePath);
329
+ apiRequestUrl.pathname = restoreBasePath(apiRequestUrl.pathname, basePath);
310
330
  apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
311
331
  }
312
332
  const response = await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null, headersFromRecord(middlewareHeaders));
@@ -464,4 +484,4 @@ async function runPagesRequest(request, deps) {
464
484
  };
465
485
  }
466
486
  //#endregion
467
- export { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
487
+ export { fetchWorkerFilesystemRoute, isServableFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
@@ -69,10 +69,13 @@ const configRewrites = vinextConfig?.rewrites ?? {
69
69
  const configHeaders = vinextConfig?.headers ?? [];
70
70
  const imageConfig = vinextConfig?.images ? {
71
71
  qualities: vinextConfig.images.qualities,
72
+ localPatterns: vinextConfig.images.localPatterns,
72
73
  dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
73
74
  dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
74
75
  contentDispositionType: vinextConfig.images.contentDispositionType,
75
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
76
+ contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
77
+ minimumCacheTTL: vinextConfig.images.minimumCacheTTL,
78
+ maximumResponseBody: vinextConfig.images.maximumResponseBody
76
79
  } : void 0;
77
80
  /**
78
81
  * Whether the request stage serves this request as a client build file
@@ -383,7 +386,10 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
383
386
  });
384
387
  if (sourceResult.type !== "response") return null;
385
388
  await sourceResult.response.body?.cancel().catch(() => {});
386
- if (isImageSourceBodyStatus(sourceResult.response.status)) return sourceAsset;
389
+ if (isImageSourceBodyStatus(sourceResult.response.status) && sourceAsset !== null) {
390
+ const { withStagedCacheControl } = await import("./worker-image-optimization.js");
391
+ return withStagedCacheControl(sourceAsset, sourceResult.response.headers.get("Cache-Control"));
392
+ }
387
393
  await sourceAsset?.body?.cancel().catch(() => {});
388
394
  return null;
389
395
  };
@@ -1,10 +1,12 @@
1
1
  import { VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM } from "./headers.js";
2
+ import { notFoundResponse } from "./http-error-responses.js";
2
3
  import { cloneRequestWithHeaders, cloneRequestWithUrl, stripRequestUrlFragment } from "./request-pipeline.js";
3
4
  import { runWithExecutionContext } from "../shims/request-context.js";
4
5
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
5
6
  import { validateCdnRequest } from "./cache-control.js";
6
7
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
7
8
  import { createWorkerPrerenderDiscoveryContext, createWorkerPrerenderReadinessResponse } from "./worker-prerender-discovery.js";
9
+ import { isSrvxAbsoluteFormTarget } from "./srvx-request-target.js";
8
10
  import { handleRequestStageLocally, isPagesClientBuildFileRequest, pagesRequestStageBuildId, pagesRequestStagePrerenderSecret } from "./pages-request-stage-entry.js";
9
11
  import { renderPagesResponse } from "./pages-response-stage-entry.js";
10
12
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
@@ -52,6 +54,7 @@ async function handleSingleStageRequest(request, env, platformCtx) {
52
54
  return finalize ? finalize(response, ctx) : response;
53
55
  }
54
56
  var pages_router_entry_default = { fetch(request, env, ctx) {
57
+ if (isSrvxAbsoluteFormTarget(request)) return Promise.resolve(notFoundResponse());
55
58
  const strippedRequest = stripRequestUrlFragment(request);
56
59
  const handle = () => handleSingleStageRequest(strippedRequest, env, ctx);
57
60
  return ctx && typeof ctx.waitUntil === "function" ? runWithExecutionContext(ctx, handle) : handle();
@@ -90,7 +90,7 @@ declare function mergeWebResponse(middlewareHeaders: Record<string, string | str
90
90
  * Send a compressed response if the content type is compressible and the
91
91
  * client supports compression. Otherwise send uncompressed.
92
92
  */
93
- declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body: string | Buffer, contentType: string, statusCode: number, extraHeaders?: Record<string, string | string[]>, compress?: boolean, statusText?: string): void;
93
+ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body: string | Buffer, contentType: string | undefined, statusCode: number, extraHeaders?: Record<string, string | string[]>, compress?: boolean, statusText?: string): void;
94
94
  /**
95
95
  * Try to serve a static file from the client build directory.
96
96
  *
@@ -7,7 +7,7 @@ import { notFoundResponse } from "./http-error-responses.js";
7
7
  import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedirect } from "./open-redirect.js";
8
8
  import { readStaticFileSignal } from "./static-file-signal.js";
9
9
  import { canonicalizeRequestPathname, filterInternalHeaders } from "./request-pipeline.js";
10
- import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix, isAssetPrefixBuildAsset } from "../utils/asset-prefix.js";
10
+ import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from "../utils/asset-prefix.js";
11
11
  import { StaticFileCache, contentTypeForPath, etagFromFilenameHash, staticFileCacheControl } from "./static-file-cache.js";
12
12
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
13
13
  import { resolveRequestHost, resolveRequestProtocol, resolveTrustedForwardedHost, trustProxy, trustedHosts } from "./proxy-trust.js";
@@ -19,13 +19,13 @@ import { parseHttpDate } from "./http-date.js";
19
19
  import { evaluateStaticPreconditions } from "./http-conditional.js";
20
20
  import { setPagesClientAssets } from "./pages-client-assets.js";
21
21
  import { signalFromNodeResponse } from "./node-response-signal.js";
22
- import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, parseImageParams, readImageOptimizationSignal } from "./image-optimization.js";
22
+ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_RESPONSE_CACHE_STATE, IMAGE_TYPE_NOT_ALLOWED_MESSAGE, INVALID_IMAGE_SOURCE_MESSAGE, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, parseImageParams, readImageOptimizationSignal, sourceImageCacheControl } from "./image-optimization.js";
23
23
  import { installSocketErrorBackstop } from "./socket-error-backstop.js";
24
24
  import { collectInlineCssManifest } from "../build/inline-css.js";
25
25
  import { readTrustedRevalidationHostname } from "./revalidation-host.js";
26
26
  import { traceFrameworkRequest } from "./request-tracing.js";
27
27
  import { mergeHeaders } from "./worker-utils.js";
28
- import { runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
28
+ import { isServableFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
29
29
  import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
30
30
  import { readPrerenderSecret, readServerCompress } from "../build/server-manifest.js";
31
31
  import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
@@ -270,6 +270,7 @@ const OMIT_STATIC_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
270
270
  "content-type",
271
271
  VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
272
272
  ]);
273
+ const IMAGE_ERROR_SIGNAL_HEADERS = ["content-type", "content-encoding"];
273
274
  function omitHeadersCaseInsensitive(headersRecord, targets) {
274
275
  const filtered = {};
275
276
  for (const [key, value] of Object.entries(headersRecord)) {
@@ -338,6 +339,7 @@ function installClientBuildManifestGlobals(clientDir, assetBase, assetPrefix, cr
338
339
  });
339
340
  setPagesClientAssets({
340
341
  appBootstrapPreinitModules: metadata.appBootstrapPreinitModules,
342
+ appBootstrapPreloadModules: metadata.appBootstrapPreloadModules,
341
343
  appRouteManifestModule: metadata.appRouteManifestModule,
342
344
  lazyChunks: metadata.lazyChunks,
343
345
  dynamicPreloads: metadata.dynamicPreloads,
@@ -443,7 +445,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
443
445
  if (encoding !== "identity") {
444
446
  writeHead(mergeVaryHeader({
445
447
  ...headersWithoutBodyHeaders,
446
- "Content-Type": contentType,
448
+ ...contentType !== void 0 && { "Content-Type": contentType },
447
449
  "Content-Encoding": encoding
448
450
  }, "Accept-Encoding"));
449
451
  const compressor = createCompressor(encoding);
@@ -452,7 +454,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
452
454
  } else {
453
455
  const identityHeaders = {
454
456
  ...headersWithoutBodyHeaders,
455
- "Content-Type": contentType,
457
+ ...contentType !== void 0 && { "Content-Type": contentType },
456
458
  "Content-Length": String(buf.length)
457
459
  };
458
460
  writeHead(varyByEncoding ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
@@ -476,20 +478,18 @@ function staticCacheLookupPath(pathname) {
476
478
  }
477
479
  /**
478
480
  * Give a Node image passthrough Next.js's image Cache-Control instead of the
479
- * public-file default: a hashed build asset keeps its immutable default, and
480
- * any other source is revalidated after the default `images.minimumCacheTTL`.
481
+ * public-file default: a build asset keeps its immutable default, and a public
482
+ * file, whatever its path, is revalidated after `images.minimumCacheTTL`, or
483
+ * after the max-age a direct request for it gets (from middleware or
484
+ * `headers()`, else the public-file default) when that is longer.
481
485
  */
482
- function withImageSourceCacheControl(headers, sourcePath) {
483
- if (sourcePath.includes(`/_next/static/`)) return headers;
486
+ function withImageSourceCacheControl(headers, publicFileCacheControl, config) {
487
+ if (publicFileCacheControl === void 0) return headers;
484
488
  return {
485
489
  ...headers,
486
- "Cache-Control": MUTABLE_IMAGE_SOURCE_CACHE_CONTROL
490
+ "Cache-Control": sourceImageCacheControl(false, publicFileCacheControl ?? staticFileCacheControl(false), config)
487
491
  };
488
492
  }
489
- /** Pathnames the Pages Router serves from routes rather than the client directory. */
490
- function isPagesFilesystemExcludedPath(pathname, phase) {
491
- return pathname === "/" || pathname === "/api" || pathname.startsWith("/api/") || phase === "direct" && pathname.startsWith(`/_next/static/`);
492
- }
493
493
  /**
494
494
  * Try to serve a static file from the client build directory.
495
495
  *
@@ -782,32 +782,14 @@ function pipeStaticFileRange(filePath, range, res) {
782
782
  }
783
783
  });
784
784
  }
785
- /**
786
- * Resolve the actual file to serve, trying extension-less HTML fallbacks.
787
- * Returns the resolved path + size + mtime, or null if not found.
788
- */
785
+ /** Returns the file's path + size + mtime, or null if it is not a regular file. */
789
786
  async function resolveStaticFile(staticFile) {
790
787
  const stat = await statIfFile(staticFile);
791
- if (stat) return {
788
+ return stat ? {
792
789
  path: staticFile,
793
790
  size: stat.size,
794
791
  mtimeMs: stat.mtimeMs
795
- };
796
- const htmlFallback = staticFile + ".html";
797
- const htmlStat = await statIfFile(htmlFallback);
798
- if (htmlStat) return {
799
- path: htmlFallback,
800
- size: htmlStat.size,
801
- mtimeMs: htmlStat.mtimeMs
802
- };
803
- const indexFallback = path.join(staticFile, "index.html");
804
- const indexStat = await statIfFile(indexFallback);
805
- if (indexStat) return {
806
- path: indexFallback,
807
- size: indexStat.size,
808
- mtimeMs: indexStat.mtimeMs
809
- };
810
- return null;
792
+ } : null;
811
793
  }
812
794
  async function statIfFile(filePath) {
813
795
  try {
@@ -1128,6 +1110,7 @@ function installPagesClientAssets(options) {
1128
1110
  setPagesClientAssets({
1129
1111
  clientEntry: metadata.clientEntryFile,
1130
1112
  appBootstrapPreinitModules: metadata.appBootstrapPreinitModules,
1113
+ appBootstrapPreloadModules: metadata.appBootstrapPreloadModules,
1131
1114
  appRouteManifestModule: metadata.appRouteManifestModule,
1132
1115
  ssrManifest: Object.keys(ssrManifest).length > 0 ? ssrManifest : void 0,
1133
1116
  sharedChunks: metadata.sharedChunks,
@@ -1195,18 +1178,25 @@ async function startAppRouterServer(options) {
1195
1178
  if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
1196
1179
  const staticCache = await StaticFileCache.create(clientDir);
1197
1180
  const serveImageOptimization = async (req, res, request, signal) => {
1198
- const stagedHeaders = omitHeadersCaseInsensitive(mergeResponseHeaders({}, new Response(null, { headers: signal.middlewareHeaders })), OMIT_STATIC_RESPONSE_HEADERS);
1181
+ const signalHeaders = mergeResponseHeaders({}, new Response(null, { headers: signal.middlewareHeaders }));
1182
+ const stagedHeaders = omitHeadersCaseInsensitive(signalHeaders, OMIT_STATIC_RESPONSE_HEADERS);
1199
1183
  for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
1184
+ const sendBadRequest = (body) => {
1185
+ for (const name of IMAGE_ERROR_SIGNAL_HEADERS) {
1186
+ const value = signalHeaders[name];
1187
+ if (value !== void 0) res.setHeader(name, value);
1188
+ }
1189
+ res.writeHead(400);
1190
+ res.end(body);
1191
+ };
1200
1192
  const parsedUrl = new URL(`/${signal.search}`, "http://localhost");
1201
- const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities);
1193
+ const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
1202
1194
  if (!params) {
1203
- res.writeHead(400);
1204
- res.end("Bad Request");
1195
+ sendBadRequest("Bad Request");
1205
1196
  return;
1206
1197
  }
1207
1198
  const sendUnsafeContentType = () => {
1208
- res.writeHead(400);
1209
- res.end("The requested resource is not an allowed image type");
1199
+ sendBadRequest(IMAGE_TYPE_NOT_ALLOWED_MESSAGE);
1210
1200
  };
1211
1201
  const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), imageConfig?.dangerouslyAllowSVG);
1212
1202
  const imageSecurityHeaders = {
@@ -1214,7 +1204,7 @@ async function startAppRouterServer(options) {
1214
1204
  "X-Content-Type-Options": "nosniff",
1215
1205
  "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1216
1206
  };
1217
- const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
1207
+ const serveSource = (sourcePath, publicFileCacheControl) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, publicFileCacheControl, imageConfig), void 0, IMAGE_REPRESENTATION_HEADERS);
1218
1208
  const sourceUrl = new URL(params.imageUrl, request.url);
1219
1209
  let sourcePathname = null;
1220
1210
  try {
@@ -1240,11 +1230,10 @@ async function startAppRouterServer(options) {
1240
1230
  sendUnsafeContentType();
1241
1231
  return;
1242
1232
  }
1243
- if (await serveSource(sourcePath)) return;
1233
+ if (await serveSource(sourcePath, sourceResponse.headers.get("Cache-Control"))) return;
1244
1234
  }
1245
1235
  }
1246
- res.writeHead(400);
1247
- res.end(INVALID_IMAGE_SOURCE_MESSAGE);
1236
+ sendBadRequest(INVALID_IMAGE_SOURCE_MESSAGE);
1248
1237
  };
1249
1238
  const clientBuildFiles = new Set(Array.isArray(rscModule.__clientBuildFiles) ? rscModule.__clientBuildFiles : []);
1250
1239
  const handleRequestImpl = async (req, res) => {
@@ -1433,6 +1422,8 @@ async function createPagesRouterRequestHandler(options) {
1433
1422
  const ensureInstrumentation = typeof serverEntry.__ensureInstrumentation === "function" ? () => serverEntry.__ensureInstrumentation() : () => void 0;
1434
1423
  const hasMiddleware = serverEntry.hasMiddleware === true;
1435
1424
  const pageRoutes = readPagesServerEntryPageRoutes(serverEntry.pageRoutes);
1425
+ const publicFiles = serverEntry.publicFiles instanceof Set ? serverEntry.publicFiles : /* @__PURE__ */ new Set();
1426
+ const clientBuildFiles = serverEntry.clientBuildFiles instanceof Set ? serverEntry.clientBuildFiles : /* @__PURE__ */ new Set();
1436
1427
  const prerenderSecret = readPrerenderSecret(path.dirname(serverEntryPath));
1437
1428
  const compress = compressOption && readServerCompress(serverDir);
1438
1429
  const basePath = vinextConfig?.basePath ?? "";
@@ -1455,8 +1446,11 @@ async function createPagesRouterRequestHandler(options) {
1455
1446
  dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
1456
1447
  dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
1457
1448
  qualities: vinextConfig.images.qualities,
1449
+ localPatterns: vinextConfig.images.localPatterns,
1458
1450
  contentDispositionType: vinextConfig.images.contentDispositionType,
1459
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
1451
+ contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
1452
+ minimumCacheTTL: vinextConfig.images.minimumCacheTTL,
1453
+ maximumResponseBody: vinextConfig.images.maximumResponseBody
1460
1454
  } : void 0;
1461
1455
  const ssrManifest = installPagesClientAssets({
1462
1456
  clientDir,
@@ -1546,6 +1540,10 @@ async function createPagesRouterRequestHandler(options) {
1546
1540
  if (pagesAssetLookup) {
1547
1541
  if (await tryServeStatic(req, res, clientDir, pagesAssetLookup, compress, staticCache)) return;
1548
1542
  }
1543
+ if (req.method === "GET" || req.method === "HEAD") {
1544
+ const clientBuildFile = resolveClientBuildFile(rawPagesPathname, basePath, clientBuildFiles);
1545
+ if (clientBuildFile && await tryServeStatic(req, res, clientDir, clientBuildFile, compress, staticCache)) return;
1546
+ }
1549
1547
  try {
1550
1548
  const hadBasePath = !basePath || hasBasePath(requestPathname, basePath);
1551
1549
  const restoreLocaleRootTrailingSlash = shouldRestoreLocaleRootTrailingSlash(requestPathname, {
@@ -1616,6 +1614,7 @@ async function createPagesRouterRequestHandler(options) {
1616
1614
  const middlewareRequest = isDataReq && skipProxyUrlNormalize ? new Request(new URL(originalRenderUrl, webRequest.url), webRequest) : void 0;
1617
1615
  const middlewareCtx = createNodeExecutionContext(void 0, { middlewareOrigin: resolveNodeMiddlewareOrigin(req, webRequest.url, host, port) });
1618
1616
  const runNodeMiddleware = typeof runMiddleware === "function" ? (request, _ctx, opts) => runMiddleware(request, middlewareCtx, opts) : null;
1617
+ /** The public file a source resolves to, with the Cache-Control staged for it. */
1619
1618
  const resolvePublicImageSource = async (sourceUrl) => {
1620
1619
  const rawSourcePathname = sourceUrl.pathname;
1621
1620
  if (getRepeatedSlashRedirect(rawSourcePathname) || isOpenRedirectShaped(rawSourcePathname)) return null;
@@ -1623,7 +1622,7 @@ async function createPagesRouterRequestHandler(options) {
1623
1622
  const sourceHadBasePath = !basePath || hasBasePath(sourceRequestPathname, basePath);
1624
1623
  const strippedSourcePathname = stripBasePath(sourceRequestPathname, basePath);
1625
1624
  if (isNextDataPathname(strippedSourcePathname)) return null;
1626
- let sourcePath = null;
1625
+ let source = null;
1627
1626
  const result = await runPagesRequest(new Request(new URL(strippedSourcePathname + sourceUrl.search, sourceUrl), { signal: webRequest.signal }), {
1628
1627
  ...deps,
1629
1628
  hadBasePath: sourceHadBasePath,
@@ -1641,17 +1640,20 @@ async function createPagesRouterRequestHandler(options) {
1641
1640
  runMiddleware: runNodeMiddleware ? wrapMiddlewareWithBasePath(runNodeMiddleware, basePath, sourceHadBasePath) : null,
1642
1641
  renderPage: null,
1643
1642
  handleApi: null,
1644
- serveFilesystemRoute: async (candidate, _stagedHeaders, phase, _resolvedUrl, _request, candidateHasBasePath) => {
1645
- if (!candidateHasBasePath && !isAssetPrefixBuildAsset(candidate, pagesAssetPathPrefix) || isImageOptimizationPath(candidate) || isPagesFilesystemExcludedPath(candidate, phase)) return false;
1643
+ serveFilesystemRoute: async (candidate, stagedHeaders, phase, _resolvedUrl, sourceRequest, candidateHasBasePath) => {
1644
+ if (isImageOptimizationPath(candidate) || !isServableFilesystemRoute(candidate, phase, sourceRequest.method, publicFiles, basePath, pagesAssetPathPrefix, candidateHasBasePath)) return false;
1646
1645
  const lookupPath = staticCacheLookupPath(candidate);
1647
1646
  if (lookupPath === null || lookupPath === "/" || !staticCache.lookup(lookupPath)) return false;
1648
- sourcePath = candidate;
1647
+ source = {
1648
+ path: candidate,
1649
+ cacheControl: readHeaderCaseInsensitive(stagedHeaders, "cache-control") ?? null
1650
+ };
1649
1651
  return new Response(null);
1650
1652
  }
1651
1653
  });
1652
1654
  if (result.type !== "response") return null;
1653
1655
  cancelResponseBody(result.response);
1654
- return isImageSourceBodyStatus(result.response.status) ? sourcePath : null;
1656
+ return isImageSourceBodyStatus(result.response.status) ? source : null;
1655
1657
  };
1656
1658
  const deps = {
1657
1659
  assetPrefix,
@@ -1685,11 +1687,11 @@ async function createPagesRouterRequestHandler(options) {
1685
1687
  return handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node", stagedHeaders);
1686
1688
  } : null,
1687
1689
  handleApiAppliesStagedHeaders: true,
1688
- serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl, _request, resolvedPathnameHasBasePath) => {
1690
+ serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl, filesystemRequest, resolvedPathnameHasBasePath) => {
1689
1691
  if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
1690
1692
  for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
1691
1693
  const parsedUrl = new URL(resolvedUrl, "http://localhost");
1692
- const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities);
1694
+ const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities, { localPatterns: pagesImageConfig?.localPatterns });
1693
1695
  if (!params) {
1694
1696
  res.writeHead(400);
1695
1697
  res.end("Bad Request");
@@ -1697,7 +1699,7 @@ async function createPagesRouterRequestHandler(options) {
1697
1699
  }
1698
1700
  const sendUnsafeContentType = () => {
1699
1701
  res.writeHead(400);
1700
- res.end("The requested resource is not an allowed image type");
1702
+ res.end(IMAGE_TYPE_NOT_ALLOWED_MESSAGE);
1701
1703
  };
1702
1704
  const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), pagesImageConfig?.dangerouslyAllowSVG);
1703
1705
  const imageSecurityHeaders = {
@@ -1705,7 +1707,7 @@ async function createPagesRouterRequestHandler(options) {
1705
1707
  "X-Content-Type-Options": "nosniff",
1706
1708
  "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1707
1709
  };
1708
- const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
1710
+ const serveSource = (sourcePath, publicFileCacheControl) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, publicFileCacheControl, pagesImageConfig), void 0, IMAGE_REPRESENTATION_HEADERS);
1709
1711
  const sourceUrl = new URL(params.imageUrl, webRequest.url);
1710
1712
  let sourcePathname = null;
1711
1713
  try {
@@ -1715,22 +1717,22 @@ async function createPagesRouterRequestHandler(options) {
1715
1717
  sendUnsafeContentType();
1716
1718
  return true;
1717
1719
  }
1718
- const assetLookupPath = sourcePathname !== null ? resolveAppRouterAssetPath(sourcePathname, pagesAssetPathPrefix, assetPrefix) : null;
1720
+ const assetLookupPath = sourcePathname !== null ? resolveAppRouterAssetPath(sourcePathname, pagesAssetPathPrefix, assetPrefix) ?? resolveClientBuildFile(sourceUrl.pathname, basePath, clientBuildFiles) : null;
1719
1721
  if (assetLookupPath && await serveSource(assetLookupPath)) return true;
1720
- const sourcePath = sourcePathname !== null ? await resolvePublicImageSource(sourceUrl) : null;
1722
+ const source = sourcePathname !== null ? await resolvePublicImageSource(sourceUrl) : null;
1721
1723
  if (webRequest.signal.aborted) return true;
1722
- if (sourcePath !== null) {
1723
- if (!isSafeSourcePath(staticCacheLookupPath(sourcePath) ?? sourcePath)) {
1724
+ if (source !== null) {
1725
+ if (!isSafeSourcePath(staticCacheLookupPath(source.path) ?? source.path)) {
1724
1726
  sendUnsafeContentType();
1725
1727
  return true;
1726
1728
  }
1727
- if (await serveSource(sourcePath)) return true;
1729
+ if (await serveSource(source.path, source.cacheControl)) return true;
1728
1730
  }
1729
1731
  res.writeHead(400);
1730
1732
  res.end(INVALID_IMAGE_SOURCE_MESSAGE);
1731
1733
  return true;
1732
1734
  }
1733
- if (!resolvedPathnameHasBasePath && !isAssetPrefixBuildAsset(requestPathname, pagesAssetPathPrefix) || isPagesFilesystemExcludedPath(requestPathname, phase)) return false;
1735
+ if (!isServableFilesystemRoute(requestPathname, phase, filesystemRequest.method, publicFiles, basePath, pagesAssetPathPrefix, resolvedPathnameHasBasePath)) return false;
1734
1736
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
1735
1737
  }
1736
1738
  };
@@ -1750,7 +1752,7 @@ async function createPagesRouterRequestHandler(options) {
1750
1752
  }
1751
1753
  const responseBody = await bufferResponseBodyUntilAborted(response, webRequest.signal);
1752
1754
  if (!responseBody) return;
1753
- const ct = response.headers.get("content-type") ?? result.defaultContentType;
1755
+ const ct = response.headers.get("content-type") ?? (responseBody.length > 0 ? result.defaultContentType : void 0);
1754
1756
  const responseHeaders = {};
1755
1757
  response.headers.forEach((v, k) => {
1756
1758
  if (k === "set-cookie") return;
@@ -16,6 +16,11 @@ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedire
16
16
  export declare function canonicalizeRequestPathname(pathname: string): string;
17
17
  /** Canonicalize only the pathname portion while preserving the raw query. */
18
18
  export declare function canonicalizeRequestUrlPathname(url: string): string;
19
+ /**
20
+ * Whether the pathname has a segment the URL parser resolves as `.` or `..`,
21
+ * literally or percent-encoded. Such a pathname changes when it becomes a URL.
22
+ */
23
+ export declare function hasUrlParserDotSegment(pathname: string): boolean;
19
24
  /**
20
25
  * Shared request pipeline utilities.
21
26
  *