vinext 1.4.2 → 1.4.4

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 (116) hide show
  1. package/README.md +4 -1
  2. package/dist/build/prerender-paths.d.ts +11 -1
  3. package/dist/build/prerender-paths.js +41 -11
  4. package/dist/cli.js +1 -1
  5. package/dist/client/dev-error-overlay.d.ts +2 -1
  6. package/dist/client/dev-error-overlay.js +8 -3
  7. package/dist/config/config-matchers.js +6 -2
  8. package/dist/config/next-config.d.ts +9 -0
  9. package/dist/entries/app-rsc-entry.js +24 -12
  10. package/dist/entries/pages-server-entry.js +17 -4
  11. package/dist/index.js +48 -39
  12. package/dist/init-platform.js +2 -2
  13. package/dist/init.js +42 -5
  14. package/dist/server/api-handler.js +8 -5
  15. package/dist/server/app-browser-entry.js +26 -47
  16. package/dist/server/app-browser-error.d.ts +1 -1
  17. package/dist/server/app-browser-error.js +13 -4
  18. package/dist/server/app-browser-navigation-controller.js +2 -2
  19. package/dist/server/app-browser-server-action-client.js +2 -2
  20. package/dist/server/app-browser-state.js +2 -2
  21. package/dist/server/app-browser-supplemental-refresh.d.ts +13 -0
  22. package/dist/server/app-browser-supplemental-refresh.js +36 -4
  23. package/dist/server/app-browser-visible-commit.js +1 -1
  24. package/dist/server/app-history-state.js +1 -1
  25. package/dist/server/app-middleware.d.ts +2 -0
  26. package/dist/server/app-middleware.js +1 -0
  27. package/dist/server/app-page-boundary-render.js +4 -2
  28. package/dist/server/app-page-dispatch.d.ts +1 -0
  29. package/dist/server/app-page-dispatch.js +5 -1
  30. package/dist/server/app-page-element-builder.d.ts +2 -0
  31. package/dist/server/app-page-element-builder.js +6 -5
  32. package/dist/server/app-page-render.js +1 -1
  33. package/dist/server/app-page-route-wiring.d.ts +7 -5
  34. package/dist/server/app-page-route-wiring.js +11 -9
  35. package/dist/server/app-pages-bridge.d.ts +6 -0
  36. package/dist/server/app-pages-bridge.js +8 -3
  37. package/dist/server/app-request-context.js +1 -1
  38. package/dist/server/app-request-stage-independent-entry.js +2 -3
  39. package/dist/server/app-route-handler-dispatch.js +1 -1
  40. package/dist/server/app-route-handler-execution.js +1 -1
  41. package/dist/server/app-route-handler-response.d.ts +1 -0
  42. package/dist/server/app-route-handler-response.js +27 -2
  43. package/dist/server/app-router-entry.js +2 -3
  44. package/dist/server/app-rsc-cache-busting.d.ts +8 -0
  45. package/dist/server/app-rsc-cache-busting.js +13 -1
  46. package/dist/server/app-rsc-combined-handler.js +2 -1
  47. package/dist/server/app-rsc-handler.d.ts +4 -0
  48. package/dist/server/app-rsc-handler.js +41 -24
  49. package/dist/server/app-rsc-response-finalizer.d.ts +15 -3
  50. package/dist/server/app-rsc-response-finalizer.js +51 -7
  51. package/dist/server/app-rsc-response-stage.js +1 -1
  52. package/dist/server/app-server-action-execution.js +1 -1
  53. package/dist/server/app-ssr-entry.js +18 -10
  54. package/dist/server/config-headers.d.ts +7 -0
  55. package/dist/server/config-headers.js +20 -1
  56. package/dist/server/edge-api-runtime.d.ts +6 -2
  57. package/dist/server/edge-api-runtime.js +21 -4
  58. package/dist/server/image-animation.d.ts +9 -0
  59. package/dist/server/image-animation.js +118 -0
  60. package/dist/server/image-optimization.d.ts +25 -5
  61. package/dist/server/image-optimization.js +305 -21
  62. package/dist/server/image-source-reader.d.ts +34 -0
  63. package/dist/server/image-source-reader.js +126 -0
  64. package/dist/server/metadata-route-response.d.ts +6 -1
  65. package/dist/server/metadata-route-response.js +24 -6
  66. package/dist/server/middleware-matcher.d.ts +1 -0
  67. package/dist/server/middleware-matcher.js +1 -1
  68. package/dist/server/middleware-runtime.d.ts +5 -0
  69. package/dist/server/middleware-runtime.js +48 -15
  70. package/dist/server/middleware.d.ts +1 -1
  71. package/dist/server/middleware.js +2 -1
  72. package/dist/server/navigation-planner.js +14 -6
  73. package/dist/server/nitro-request-target-plugin.d.ts +17 -0
  74. package/dist/server/nitro-request-target-plugin.js +22 -0
  75. package/dist/server/open-redirect.d.ts +2 -1
  76. package/dist/server/open-redirect.js +2 -1
  77. package/dist/server/pages-client-assets.d.ts +1 -0
  78. package/dist/server/pages-i18n.d.ts +7 -0
  79. package/dist/server/pages-i18n.js +14 -1
  80. package/dist/server/pages-node-compat.js +7 -3
  81. package/dist/server/pages-page-handler.js +4 -4
  82. package/dist/server/pages-request-pipeline.d.ts +9 -0
  83. package/dist/server/pages-request-pipeline.js +29 -10
  84. package/dist/server/pages-request-stage-entry.js +7 -2
  85. package/dist/server/pages-router-entry.js +3 -0
  86. package/dist/server/prod-server.d.ts +1 -1
  87. package/dist/server/prod-server.js +48 -54
  88. package/dist/server/remove-request-headers.d.ts +16 -0
  89. package/dist/server/remove-request-headers.js +23 -0
  90. package/dist/server/srvx-middleware-origin.d.ts +13 -0
  91. package/dist/server/srvx-middleware-origin.js +19 -0
  92. package/dist/server/srvx-request-target.d.ts +12 -0
  93. package/dist/server/srvx-request-target.js +20 -0
  94. package/dist/server/static-file-cache.js +10 -22
  95. package/dist/server/worker-image-optimization.d.ts +8 -1
  96. package/dist/server/worker-image-optimization.js +25 -6
  97. package/dist/server/worker-revalidation-context.js +1 -0
  98. package/dist/shims/cache-runtime.js +1 -1
  99. package/dist/shims/cache.js +6 -3
  100. package/dist/shims/client-page-root.js +2 -2
  101. package/dist/shims/error-boundary.d.ts +6 -2
  102. package/dist/shims/error-boundary.js +6 -4
  103. package/dist/shims/internal/navigation-untracked.js +1 -1
  104. package/dist/shims/metadata.js +1 -1
  105. package/dist/shims/navigation-server.js +1 -1
  106. package/dist/shims/navigation.js +4 -4
  107. package/dist/shims/navigation.react-server.js +3 -3
  108. package/dist/shims/request-context.d.ts +3 -1
  109. package/dist/shims/root-params.js +16 -2
  110. package/dist/shims/slot.js +3 -3
  111. package/dist/shims/url-utils.js +6 -1
  112. package/dist/utils/client-entry-manifest.d.ts +5 -0
  113. package/dist/utils/client-entry-manifest.js +1 -0
  114. package/dist/utils/client-runtime-metadata.d.ts +5 -0
  115. package/dist/utils/client-runtime-metadata.js +33 -7
  116. package/package.json +1 -1
@@ -19,6 +19,7 @@ import { setFrameworkRequestRoute } from "./request-tracing.js";
19
19
  import { withExternalRewriteResponseHeaders } from "./middleware-response-headers.js";
20
20
  import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
21
21
  import { finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders } from "./worker-utils.js";
22
+ import { publicFileRouteKey } from "../utils/public-file-path.js";
22
23
  //#region src/server/pages-request-pipeline.ts
23
24
  function ruleUsesUnkeyedRequestCondition(rule) {
24
25
  return [...rule.has ?? [], ...rule.missing ?? []].some((condition) => condition.type === "header" || condition.type === "cookie" || condition.type === "host");
@@ -35,24 +36,41 @@ function headersFromRecord(record) {
35
36
  else headers.set(name, value);
36
37
  return headers;
37
38
  }
38
- async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset, publicFiles, basePath = "", assetPathPrefix = "", pathnameHasBasePath = true) {
39
- const isRetrievalMethod = request.method === "GET" || request.method === "HEAD";
40
- 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");
41
48
  assetUrl.pathname = requestPathname;
42
- assetUrl.search = "";
43
49
  const decodedAssetUrl = new URL(assetUrl);
44
50
  let publicFileKey;
51
+ let servedPathname;
52
+ let servedFileKey;
45
53
  try {
46
54
  decodedAssetUrl.pathname = decodeURIComponent(assetUrl.pathname);
47
55
  publicFileKey = normalizePathnameForRouteMatchStrict(assetUrl.pathname);
56
+ servedPathname = decodeURIComponent(requestPathname);
57
+ servedFileKey = publicFileRouteKey(servedPathname);
48
58
  } catch {
49
59
  return false;
50
60
  }
51
- if (!pathnameHasBasePath && !isAssetPrefixBuildAsset(decodedAssetUrl.pathname, assetPathPrefix)) return false;
52
- 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);
53
64
  if (!isPublicFile && (requestPathname === "/api" || requestPathname.startsWith("/api/"))) return false;
54
- if (phase === "direct" && isRetrievalMethod && !isPublicFile) return false;
55
- 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;
56
74
  const response = await fetchAsset(isRetrievalMethod ? new Request(assetUrl, request) : new Request(assetUrl, {
57
75
  method: "HEAD",
58
76
  headers: request.headers
@@ -179,7 +197,8 @@ async function runPagesRequest(request, deps) {
179
197
  if (!isOnDemandRevalidate && typeof deps.runMiddleware === "function") {
180
198
  const result = await deps.runMiddleware(deps.middlewareRequest ?? request, deps.ctx ?? null, {
181
199
  isDataRequest,
182
- isStaleDataRequest: isDataRequest && !!deps.dataNotFoundResponse
200
+ isStaleDataRequest: isDataRequest && !!deps.dataNotFoundResponse,
201
+ restoreLocaleRootTrailingSlash: deps.restoreLocaleRootTrailingSlash
183
202
  });
184
203
  if (deps.recordCacheability !== false && result.pathnameEligible) markRouteCacheabilityDynamic("middleware can match this pathname");
185
204
  if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
@@ -465,4 +484,4 @@ async function runPagesRequest(request, deps) {
465
484
  };
466
485
  }
467
486
  //#endregion
468
- export { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
487
+ export { fetchWorkerFilesystemRoute, isServableFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
@@ -73,7 +73,9 @@ const imageConfig = vinextConfig?.images ? {
73
73
  dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
74
74
  dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
75
75
  contentDispositionType: vinextConfig.images.contentDispositionType,
76
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
76
+ contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
77
+ minimumCacheTTL: vinextConfig.images.minimumCacheTTL,
78
+ maximumResponseBody: vinextConfig.images.maximumResponseBody
77
79
  } : void 0;
78
80
  /**
79
81
  * Whether the request stage serves this request as a client build file
@@ -384,7 +386,10 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
384
386
  });
385
387
  if (sourceResult.type !== "response") return null;
386
388
  await sourceResult.response.body?.cancel().catch(() => {});
387
- 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
+ }
388
393
  await sourceAsset?.body?.cancel().catch(() => {});
389
394
  return null;
390
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";
@@ -339,6 +339,7 @@ function installClientBuildManifestGlobals(clientDir, assetBase, assetPrefix, cr
339
339
  });
340
340
  setPagesClientAssets({
341
341
  appBootstrapPreinitModules: metadata.appBootstrapPreinitModules,
342
+ appBootstrapPreloadModules: metadata.appBootstrapPreloadModules,
342
343
  appRouteManifestModule: metadata.appRouteManifestModule,
343
344
  lazyChunks: metadata.lazyChunks,
344
345
  dynamicPreloads: metadata.dynamicPreloads,
@@ -444,7 +445,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
444
445
  if (encoding !== "identity") {
445
446
  writeHead(mergeVaryHeader({
446
447
  ...headersWithoutBodyHeaders,
447
- "Content-Type": contentType,
448
+ ...contentType !== void 0 && { "Content-Type": contentType },
448
449
  "Content-Encoding": encoding
449
450
  }, "Accept-Encoding"));
450
451
  const compressor = createCompressor(encoding);
@@ -453,7 +454,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
453
454
  } else {
454
455
  const identityHeaders = {
455
456
  ...headersWithoutBodyHeaders,
456
- "Content-Type": contentType,
457
+ ...contentType !== void 0 && { "Content-Type": contentType },
457
458
  "Content-Length": String(buf.length)
458
459
  };
459
460
  writeHead(varyByEncoding ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
@@ -477,20 +478,18 @@ function staticCacheLookupPath(pathname) {
477
478
  }
478
479
  /**
479
480
  * Give a Node image passthrough Next.js's image Cache-Control instead of the
480
- * public-file default: a hashed build asset keeps its immutable default, and
481
- * 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.
482
485
  */
483
- function withImageSourceCacheControl(headers, sourcePath) {
484
- if (sourcePath.includes(`/_next/static/`)) return headers;
486
+ function withImageSourceCacheControl(headers, publicFileCacheControl, config) {
487
+ if (publicFileCacheControl === void 0) return headers;
485
488
  return {
486
489
  ...headers,
487
- "Cache-Control": MUTABLE_IMAGE_SOURCE_CACHE_CONTROL
490
+ "Cache-Control": sourceImageCacheControl(false, publicFileCacheControl ?? staticFileCacheControl(false), config)
488
491
  };
489
492
  }
490
- /** Pathnames the Pages Router serves from routes rather than the client directory. */
491
- function isPagesFilesystemExcludedPath(pathname, phase) {
492
- return pathname === "/" || pathname === "/api" || pathname.startsWith("/api/") || phase === "direct" && pathname.startsWith(`/_next/static/`);
493
- }
494
493
  /**
495
494
  * Try to serve a static file from the client build directory.
496
495
  *
@@ -783,32 +782,14 @@ function pipeStaticFileRange(filePath, range, res) {
783
782
  }
784
783
  });
785
784
  }
786
- /**
787
- * Resolve the actual file to serve, trying extension-less HTML fallbacks.
788
- * Returns the resolved path + size + mtime, or null if not found.
789
- */
785
+ /** Returns the file's path + size + mtime, or null if it is not a regular file. */
790
786
  async function resolveStaticFile(staticFile) {
791
787
  const stat = await statIfFile(staticFile);
792
- if (stat) return {
788
+ return stat ? {
793
789
  path: staticFile,
794
790
  size: stat.size,
795
791
  mtimeMs: stat.mtimeMs
796
- };
797
- const htmlFallback = staticFile + ".html";
798
- const htmlStat = await statIfFile(htmlFallback);
799
- if (htmlStat) return {
800
- path: htmlFallback,
801
- size: htmlStat.size,
802
- mtimeMs: htmlStat.mtimeMs
803
- };
804
- const indexFallback = path.join(staticFile, "index.html");
805
- const indexStat = await statIfFile(indexFallback);
806
- if (indexStat) return {
807
- path: indexFallback,
808
- size: indexStat.size,
809
- mtimeMs: indexStat.mtimeMs
810
- };
811
- return null;
792
+ } : null;
812
793
  }
813
794
  async function statIfFile(filePath) {
814
795
  try {
@@ -1129,6 +1110,7 @@ function installPagesClientAssets(options) {
1129
1110
  setPagesClientAssets({
1130
1111
  clientEntry: metadata.clientEntryFile,
1131
1112
  appBootstrapPreinitModules: metadata.appBootstrapPreinitModules,
1113
+ appBootstrapPreloadModules: metadata.appBootstrapPreloadModules,
1132
1114
  appRouteManifestModule: metadata.appRouteManifestModule,
1133
1115
  ssrManifest: Object.keys(ssrManifest).length > 0 ? ssrManifest : void 0,
1134
1116
  sharedChunks: metadata.sharedChunks,
@@ -1214,7 +1196,7 @@ async function startAppRouterServer(options) {
1214
1196
  return;
1215
1197
  }
1216
1198
  const sendUnsafeContentType = () => {
1217
- sendBadRequest("The requested resource is not an allowed image type");
1199
+ sendBadRequest(IMAGE_TYPE_NOT_ALLOWED_MESSAGE);
1218
1200
  };
1219
1201
  const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), imageConfig?.dangerouslyAllowSVG);
1220
1202
  const imageSecurityHeaders = {
@@ -1222,7 +1204,7 @@ async function startAppRouterServer(options) {
1222
1204
  "X-Content-Type-Options": "nosniff",
1223
1205
  "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1224
1206
  };
1225
- 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);
1226
1208
  const sourceUrl = new URL(params.imageUrl, request.url);
1227
1209
  let sourcePathname = null;
1228
1210
  try {
@@ -1248,7 +1230,7 @@ async function startAppRouterServer(options) {
1248
1230
  sendUnsafeContentType();
1249
1231
  return;
1250
1232
  }
1251
- if (await serveSource(sourcePath)) return;
1233
+ if (await serveSource(sourcePath, sourceResponse.headers.get("Cache-Control"))) return;
1252
1234
  }
1253
1235
  }
1254
1236
  sendBadRequest(INVALID_IMAGE_SOURCE_MESSAGE);
@@ -1440,6 +1422,8 @@ async function createPagesRouterRequestHandler(options) {
1440
1422
  const ensureInstrumentation = typeof serverEntry.__ensureInstrumentation === "function" ? () => serverEntry.__ensureInstrumentation() : () => void 0;
1441
1423
  const hasMiddleware = serverEntry.hasMiddleware === true;
1442
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();
1443
1427
  const prerenderSecret = readPrerenderSecret(path.dirname(serverEntryPath));
1444
1428
  const compress = compressOption && readServerCompress(serverDir);
1445
1429
  const basePath = vinextConfig?.basePath ?? "";
@@ -1464,7 +1448,9 @@ async function createPagesRouterRequestHandler(options) {
1464
1448
  qualities: vinextConfig.images.qualities,
1465
1449
  localPatterns: vinextConfig.images.localPatterns,
1466
1450
  contentDispositionType: vinextConfig.images.contentDispositionType,
1467
- contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
1451
+ contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
1452
+ minimumCacheTTL: vinextConfig.images.minimumCacheTTL,
1453
+ maximumResponseBody: vinextConfig.images.maximumResponseBody
1468
1454
  } : void 0;
1469
1455
  const ssrManifest = installPagesClientAssets({
1470
1456
  clientDir,
@@ -1554,6 +1540,10 @@ async function createPagesRouterRequestHandler(options) {
1554
1540
  if (pagesAssetLookup) {
1555
1541
  if (await tryServeStatic(req, res, clientDir, pagesAssetLookup, compress, staticCache)) return;
1556
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
+ }
1557
1547
  try {
1558
1548
  const hadBasePath = !basePath || hasBasePath(requestPathname, basePath);
1559
1549
  const restoreLocaleRootTrailingSlash = shouldRestoreLocaleRootTrailingSlash(requestPathname, {
@@ -1624,6 +1614,7 @@ async function createPagesRouterRequestHandler(options) {
1624
1614
  const middlewareRequest = isDataReq && skipProxyUrlNormalize ? new Request(new URL(originalRenderUrl, webRequest.url), webRequest) : void 0;
1625
1615
  const middlewareCtx = createNodeExecutionContext(void 0, { middlewareOrigin: resolveNodeMiddlewareOrigin(req, webRequest.url, host, port) });
1626
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. */
1627
1618
  const resolvePublicImageSource = async (sourceUrl) => {
1628
1619
  const rawSourcePathname = sourceUrl.pathname;
1629
1620
  if (getRepeatedSlashRedirect(rawSourcePathname) || isOpenRedirectShaped(rawSourcePathname)) return null;
@@ -1631,7 +1622,7 @@ async function createPagesRouterRequestHandler(options) {
1631
1622
  const sourceHadBasePath = !basePath || hasBasePath(sourceRequestPathname, basePath);
1632
1623
  const strippedSourcePathname = stripBasePath(sourceRequestPathname, basePath);
1633
1624
  if (isNextDataPathname(strippedSourcePathname)) return null;
1634
- let sourcePath = null;
1625
+ let source = null;
1635
1626
  const result = await runPagesRequest(new Request(new URL(strippedSourcePathname + sourceUrl.search, sourceUrl), { signal: webRequest.signal }), {
1636
1627
  ...deps,
1637
1628
  hadBasePath: sourceHadBasePath,
@@ -1649,17 +1640,20 @@ async function createPagesRouterRequestHandler(options) {
1649
1640
  runMiddleware: runNodeMiddleware ? wrapMiddlewareWithBasePath(runNodeMiddleware, basePath, sourceHadBasePath) : null,
1650
1641
  renderPage: null,
1651
1642
  handleApi: null,
1652
- serveFilesystemRoute: async (candidate, _stagedHeaders, phase, _resolvedUrl, _request, candidateHasBasePath) => {
1653
- 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;
1654
1645
  const lookupPath = staticCacheLookupPath(candidate);
1655
1646
  if (lookupPath === null || lookupPath === "/" || !staticCache.lookup(lookupPath)) return false;
1656
- sourcePath = candidate;
1647
+ source = {
1648
+ path: candidate,
1649
+ cacheControl: readHeaderCaseInsensitive(stagedHeaders, "cache-control") ?? null
1650
+ };
1657
1651
  return new Response(null);
1658
1652
  }
1659
1653
  });
1660
1654
  if (result.type !== "response") return null;
1661
1655
  cancelResponseBody(result.response);
1662
- return isImageSourceBodyStatus(result.response.status) ? sourcePath : null;
1656
+ return isImageSourceBodyStatus(result.response.status) ? source : null;
1663
1657
  };
1664
1658
  const deps = {
1665
1659
  assetPrefix,
@@ -1693,7 +1687,7 @@ async function createPagesRouterRequestHandler(options) {
1693
1687
  return handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node", stagedHeaders);
1694
1688
  } : null,
1695
1689
  handleApiAppliesStagedHeaders: true,
1696
- serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl, _request, resolvedPathnameHasBasePath) => {
1690
+ serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl, filesystemRequest, resolvedPathnameHasBasePath) => {
1697
1691
  if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
1698
1692
  for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
1699
1693
  const parsedUrl = new URL(resolvedUrl, "http://localhost");
@@ -1705,7 +1699,7 @@ async function createPagesRouterRequestHandler(options) {
1705
1699
  }
1706
1700
  const sendUnsafeContentType = () => {
1707
1701
  res.writeHead(400);
1708
- res.end("The requested resource is not an allowed image type");
1702
+ res.end(IMAGE_TYPE_NOT_ALLOWED_MESSAGE);
1709
1703
  };
1710
1704
  const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), pagesImageConfig?.dangerouslyAllowSVG);
1711
1705
  const imageSecurityHeaders = {
@@ -1713,7 +1707,7 @@ async function createPagesRouterRequestHandler(options) {
1713
1707
  "X-Content-Type-Options": "nosniff",
1714
1708
  "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1715
1709
  };
1716
- 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);
1717
1711
  const sourceUrl = new URL(params.imageUrl, webRequest.url);
1718
1712
  let sourcePathname = null;
1719
1713
  try {
@@ -1723,22 +1717,22 @@ async function createPagesRouterRequestHandler(options) {
1723
1717
  sendUnsafeContentType();
1724
1718
  return true;
1725
1719
  }
1726
- const assetLookupPath = sourcePathname !== null ? resolveAppRouterAssetPath(sourcePathname, pagesAssetPathPrefix, assetPrefix) : null;
1720
+ const assetLookupPath = sourcePathname !== null ? resolveAppRouterAssetPath(sourcePathname, pagesAssetPathPrefix, assetPrefix) ?? resolveClientBuildFile(sourceUrl.pathname, basePath, clientBuildFiles) : null;
1727
1721
  if (assetLookupPath && await serveSource(assetLookupPath)) return true;
1728
- const sourcePath = sourcePathname !== null ? await resolvePublicImageSource(sourceUrl) : null;
1722
+ const source = sourcePathname !== null ? await resolvePublicImageSource(sourceUrl) : null;
1729
1723
  if (webRequest.signal.aborted) return true;
1730
- if (sourcePath !== null) {
1731
- if (!isSafeSourcePath(staticCacheLookupPath(sourcePath) ?? sourcePath)) {
1724
+ if (source !== null) {
1725
+ if (!isSafeSourcePath(staticCacheLookupPath(source.path) ?? source.path)) {
1732
1726
  sendUnsafeContentType();
1733
1727
  return true;
1734
1728
  }
1735
- if (await serveSource(sourcePath)) return true;
1729
+ if (await serveSource(source.path, source.cacheControl)) return true;
1736
1730
  }
1737
1731
  res.writeHead(400);
1738
1732
  res.end(INVALID_IMAGE_SOURCE_MESSAGE);
1739
1733
  return true;
1740
1734
  }
1741
- if (!resolvedPathnameHasBasePath && !isAssetPrefixBuildAsset(requestPathname, pagesAssetPathPrefix) || isPagesFilesystemExcludedPath(requestPathname, phase)) return false;
1735
+ if (!isServableFilesystemRoute(requestPathname, phase, filesystemRequest.method, publicFiles, basePath, pagesAssetPathPrefix, resolvedPathnameHasBasePath)) return false;
1742
1736
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
1743
1737
  }
1744
1738
  };
@@ -1758,7 +1752,7 @@ async function createPagesRouterRequestHandler(options) {
1758
1752
  }
1759
1753
  const responseBody = await bufferResponseBodyUntilAborted(response, webRequest.signal);
1760
1754
  if (!responseBody) return;
1761
- const ct = response.headers.get("content-type") ?? result.defaultContentType;
1755
+ const ct = response.headers.get("content-type") ?? (responseBody.length > 0 ? result.defaultContentType : void 0);
1762
1756
  const responseHeaders = {};
1763
1757
  response.headers.forEach((v, k) => {
1764
1758
  if (k === "set-cookie") return;
@@ -0,0 +1,16 @@
1
+ import { IncomingMessage } from "node:http";
2
+ import { Http2ServerRequest } from "node:http2";
3
+ //#region src/server/remove-request-headers.d.ts
4
+ /**
5
+ * Remove headers from every view of a Node request.
6
+ *
7
+ * Node keeps each client header in req.rawHeaders as well as req.headers, and
8
+ * srvx (plugin-rsc's dev request adapter) answers Headers reads from rawHeaders
9
+ * once anything iterates them, so later handlers must not find a removed
10
+ * header in either. On HTTP/1, Node builds req.headers and req.headersDistinct
11
+ * lazily from rawHeaders and the header count it parsed, so both must exist
12
+ * before rawHeaders gets shorter. HTTP/2 compatibility requests (Vite with
13
+ * `server.https`) have no headersDistinct.
14
+ */
15
+ export declare function removeRequestHeaders(req: IncomingMessage | Http2ServerRequest, names: ReadonlySet<string>): void;
16
+ //#endregion
@@ -0,0 +1,23 @@
1
+ //#region src/server/remove-request-headers.ts
2
+ /**
3
+ * Remove headers from every view of a Node request.
4
+ *
5
+ * Node keeps each client header in req.rawHeaders as well as req.headers, and
6
+ * srvx (plugin-rsc's dev request adapter) answers Headers reads from rawHeaders
7
+ * once anything iterates them, so later handlers must not find a removed
8
+ * header in either. On HTTP/1, Node builds req.headers and req.headersDistinct
9
+ * lazily from rawHeaders and the header count it parsed, so both must exist
10
+ * before rawHeaders gets shorter. HTTP/2 compatibility requests (Vite with
11
+ * `server.https`) have no headersDistinct.
12
+ */
13
+ function removeRequestHeaders(req, names) {
14
+ const headersDistinct = "headersDistinct" in req ? req.headersDistinct : void 0;
15
+ for (const name of names) {
16
+ delete req.headers[name];
17
+ if (headersDistinct) delete headersDistinct[name];
18
+ }
19
+ const rawHeaders = req.rawHeaders;
20
+ for (let i = rawHeaders.length - 2; i >= 0; i -= 2) if (names.has(rawHeaders[i].toLowerCase())) rawHeaders.splice(i, 2);
21
+ }
22
+ //#endregion
23
+ export { removeRequestHeaders };
@@ -0,0 +1,13 @@
1
+ //#region src/server/srvx-middleware-origin.d.ts
2
+ /**
3
+ * The server-owned origin middleware runs against when srvx serves the
4
+ * request on Node. srvx builds `request.url` from the client's Host and
5
+ * `X-Forwarded-Proto` headers, which must not decide whether a rewrite to
6
+ * another host is external, so like the Node production server (and Next.js,
7
+ * which uses its hostname and port) this uses the socket's TLS state and
8
+ * local port, or a trusted `X-Forwarded-Proto` and `X-Forwarded-Host`.
9
+ * Undefined on other hosts, and on Vercel, which routes by hostname like
10
+ * Workers; they keep the request URL's origin.
11
+ */
12
+ export declare function resolveSrvxMiddlewareOrigin(request: Request): string | undefined;
13
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { resolveRequestProtocol, resolveTrustedForwardedHost } from "./proxy-trust.js";
2
+ //#region src/server/srvx-middleware-origin.ts
3
+ /**
4
+ * The server-owned origin middleware runs against when srvx serves the
5
+ * request on Node. srvx builds `request.url` from the client's Host and
6
+ * `X-Forwarded-Proto` headers, which must not decide whether a rewrite to
7
+ * another host is external, so like the Node production server (and Next.js,
8
+ * which uses its hostname and port) this uses the socket's TLS state and
9
+ * local port, or a trusted `X-Forwarded-Proto` and `X-Forwarded-Host`.
10
+ * Undefined on other hosts, and on Vercel, which routes by hostname like
11
+ * Workers; they keep the request URL's origin.
12
+ */
13
+ function resolveSrvxMiddlewareOrigin(request) {
14
+ const socket = request.runtime?.node?.req?.socket;
15
+ if (!socket || process.env.VERCEL) return void 0;
16
+ return `${socket.encrypted === true || resolveRequestProtocol(request.headers) === "https" ? "https:" : "http:"}//${resolveTrustedForwardedHost(request.headers) ?? (socket.localPort === void 0 ? "localhost" : `localhost:${socket.localPort}`)}`;
17
+ }
18
+ //#endregion
19
+ export { resolveSrvxMiddlewareOrigin };
@@ -0,0 +1,12 @@
1
+ //#region src/server/srvx-request-target.d.ts
2
+ /**
3
+ * Whether srvx, serving the request on Node, got a request target that is not
4
+ * origin-form (`GET https://other.example/x`). srvx then takes the origin of
5
+ * `request.url` from the target, as RFC 9112 §3.2.2 asks of an origin server.
6
+ * `vinext start` 404s every such target (see `getRepeatedSlashRedirect`) and
7
+ * Next.js never routes one, so the request-based entries Nitro calls refuse
8
+ * them too. The raw target is the one `nitroRequestTargetPlugin` saved before
9
+ * h3 could rewrite `req.url`.
10
+ */
11
+ export declare function isSrvxAbsoluteFormTarget(request: Request): boolean;
12
+ //#endregion
@@ -0,0 +1,20 @@
1
+ import { NITRO_REQUEST_TARGET_KEY } from "./nitro-request-target-plugin.js";
2
+ //#region src/server/srvx-request-target.ts
3
+ /**
4
+ * Whether srvx, serving the request on Node, got a request target that is not
5
+ * origin-form (`GET https://other.example/x`). srvx then takes the origin of
6
+ * `request.url` from the target, as RFC 9112 §3.2.2 asks of an origin server.
7
+ * `vinext start` 404s every such target (see `getRepeatedSlashRedirect`) and
8
+ * Next.js never routes one, so the request-based entries Nitro calls refuse
9
+ * them too. The raw target is the one `nitroRequestTargetPlugin` saved before
10
+ * h3 could rewrite `req.url`.
11
+ */
12
+ function isSrvxAbsoluteFormTarget(request) {
13
+ const req = request.runtime?.node?.req;
14
+ if (!req) return false;
15
+ const saved = Reflect.get(req, Symbol.for(NITRO_REQUEST_TARGET_KEY));
16
+ const target = typeof saved === "string" ? saved : req.url;
17
+ return !!target && !target.startsWith("/");
18
+ }
19
+ //#endregion
20
+ export { isSrvxAbsoluteFormTarget };
@@ -29,9 +29,11 @@ const CONTENT_TYPES = {
29
29
  ".css": "text/css; charset=utf-8",
30
30
  ".html": "text/html; charset=utf-8",
31
31
  ".ico": "image/x-icon",
32
+ ".icns": "image/x-icns",
32
33
  ".jpeg": "image/jpeg",
33
34
  ".jpg": "image/jpeg",
34
35
  ".json": "application/json; charset=utf-8",
36
+ ".jxl": "image/jxl",
35
37
  ".map": "application/json; charset=utf-8",
36
38
  ".mp3": "audio/mpeg",
37
39
  ".mp4": "video/mp4",
@@ -148,30 +150,16 @@ var StaticFileCache = class StaticFileCache {
148
150
  }
149
151
  const pathname = "/" + relativePath;
150
152
  entries.set(pathname, entry);
151
- if (ext === ".html") {
152
- if (relativePath.endsWith("/index.html")) {
153
- const dirPath = "/" + relativePath.slice(0, -11);
154
- if (dirPath !== "/") entries.set(dirPath, entry);
155
- } else {
156
- const withoutExt = "/" + relativePath.slice(0, -ext.length);
157
- entries.set(withoutExt, entry);
158
- }
159
- }
160
153
  }
161
154
  const toBuffer = [];
162
- const seenEntries = /* @__PURE__ */ new Set();
163
- for (const entry of entries.values()) {
164
- if (seenEntries.has(entry)) continue;
165
- seenEntries.add(entry);
166
- for (const variant of [
167
- entry.original,
168
- entry.br,
169
- entry.gz,
170
- entry.zst
171
- ]) {
172
- if (!variant || variant.size > BUFFER_THRESHOLD) continue;
173
- toBuffer.push(variant);
174
- }
155
+ for (const entry of entries.values()) for (const variant of [
156
+ entry.original,
157
+ entry.br,
158
+ entry.gz,
159
+ entry.zst
160
+ ]) {
161
+ if (!variant || variant.size > BUFFER_THRESHOLD) continue;
162
+ toBuffer.push(variant);
175
163
  }
176
164
  for (let i = 0; i < toBuffer.length; i += 64) await Promise.all(toBuffer.slice(i, i + 64).map(async (v) => {
177
165
  v.buffer = await fsp.readFile(v.path);
@@ -19,10 +19,17 @@ export type WorkerImageSourceOptions = {
19
19
  * served only when the application pipeline selects a public file for it.
20
20
  */
21
21
  export declare function fetchWorkerImageSource(imageUrl: string, request: Request, options: WorkerImageSourceOptions): Promise<Response>;
22
+ /**
23
+ * Give a public-file source the Cache-Control middleware or `headers()` staged
24
+ * for it, which a direct request receives in place of the asset default and
25
+ * Next.js' internal source fetch reads.
26
+ */
27
+ export declare function withStagedCacheControl(asset: Response, cacheControl: string | null): Response;
22
28
  /**
23
29
  * Return the public file a framework static-file signal selects, provided the
24
30
  * pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
25
- * request header overrides, as resolveStaticAssetSignal fetches it.
31
+ * request header overrides, as resolveStaticAssetSignal fetches it, and keeps
32
+ * the Cache-Control the pipeline staged.
26
33
  */
27
34
  export declare function fetchSignalledPublicFile(response: Response, sourceRequest: Request, assets: VinextAssetFetcher): Promise<Response> | null;
28
35
  /**