vinext 1.4.2 → 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 (88) 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 +36 -10
  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 +22 -25
  12. package/dist/init-platform.js +2 -2
  13. package/dist/init.js +42 -5
  14. package/dist/server/api-handler.js +7 -3
  15. package/dist/server/app-browser-entry.js +16 -37
  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-supplemental-refresh.d.ts +13 -0
  19. package/dist/server/app-browser-supplemental-refresh.js +36 -4
  20. package/dist/server/app-middleware.d.ts +2 -0
  21. package/dist/server/app-middleware.js +1 -0
  22. package/dist/server/app-page-boundary-render.js +2 -0
  23. package/dist/server/app-page-dispatch.d.ts +1 -0
  24. package/dist/server/app-page-dispatch.js +5 -1
  25. package/dist/server/app-page-element-builder.d.ts +2 -0
  26. package/dist/server/app-page-element-builder.js +2 -1
  27. package/dist/server/app-page-route-wiring.d.ts +7 -5
  28. package/dist/server/app-page-route-wiring.js +3 -1
  29. package/dist/server/app-pages-bridge.d.ts +6 -0
  30. package/dist/server/app-pages-bridge.js +8 -3
  31. package/dist/server/app-request-stage-independent-entry.js +2 -3
  32. package/dist/server/app-router-entry.js +2 -3
  33. package/dist/server/app-rsc-cache-busting.d.ts +8 -0
  34. package/dist/server/app-rsc-cache-busting.js +13 -1
  35. package/dist/server/app-rsc-combined-handler.js +2 -1
  36. package/dist/server/app-rsc-handler.d.ts +4 -0
  37. package/dist/server/app-rsc-handler.js +39 -22
  38. package/dist/server/app-rsc-response-finalizer.d.ts +15 -3
  39. package/dist/server/app-rsc-response-finalizer.js +51 -7
  40. package/dist/server/app-ssr-entry.js +8 -0
  41. package/dist/server/config-headers.d.ts +7 -0
  42. package/dist/server/config-headers.js +20 -1
  43. package/dist/server/image-animation.d.ts +9 -0
  44. package/dist/server/image-animation.js +118 -0
  45. package/dist/server/image-optimization.d.ts +25 -5
  46. package/dist/server/image-optimization.js +305 -21
  47. package/dist/server/image-source-reader.d.ts +34 -0
  48. package/dist/server/image-source-reader.js +126 -0
  49. package/dist/server/metadata-route-response.d.ts +6 -1
  50. package/dist/server/metadata-route-response.js +22 -4
  51. package/dist/server/middleware-matcher.d.ts +1 -0
  52. package/dist/server/middleware-matcher.js +1 -1
  53. package/dist/server/middleware-runtime.d.ts +5 -0
  54. package/dist/server/middleware-runtime.js +48 -15
  55. package/dist/server/middleware.d.ts +1 -1
  56. package/dist/server/middleware.js +2 -1
  57. package/dist/server/nitro-request-target-plugin.d.ts +17 -0
  58. package/dist/server/nitro-request-target-plugin.js +22 -0
  59. package/dist/server/open-redirect.d.ts +2 -1
  60. package/dist/server/open-redirect.js +2 -1
  61. package/dist/server/pages-client-assets.d.ts +1 -0
  62. package/dist/server/pages-i18n.d.ts +7 -0
  63. package/dist/server/pages-i18n.js +14 -1
  64. package/dist/server/pages-node-compat.js +7 -3
  65. package/dist/server/pages-page-handler.js +4 -4
  66. package/dist/server/pages-request-pipeline.d.ts +9 -0
  67. package/dist/server/pages-request-pipeline.js +29 -10
  68. package/dist/server/pages-request-stage-entry.js +7 -2
  69. package/dist/server/pages-router-entry.js +3 -0
  70. package/dist/server/prod-server.d.ts +1 -1
  71. package/dist/server/prod-server.js +48 -54
  72. package/dist/server/srvx-middleware-origin.d.ts +13 -0
  73. package/dist/server/srvx-middleware-origin.js +19 -0
  74. package/dist/server/srvx-request-target.d.ts +12 -0
  75. package/dist/server/srvx-request-target.js +20 -0
  76. package/dist/server/static-file-cache.js +10 -22
  77. package/dist/server/worker-image-optimization.d.ts +8 -1
  78. package/dist/server/worker-image-optimization.js +25 -6
  79. package/dist/server/worker-revalidation-context.js +1 -0
  80. package/dist/shims/error-boundary.d.ts +6 -2
  81. package/dist/shims/error-boundary.js +4 -2
  82. package/dist/shims/request-context.d.ts +3 -1
  83. package/dist/shims/url-utils.js +6 -1
  84. package/dist/utils/client-entry-manifest.d.ts +5 -0
  85. package/dist/utils/client-entry-manifest.js +1 -0
  86. package/dist/utils/client-runtime-metadata.d.ts +5 -0
  87. package/dist/utils/client-runtime-metadata.js +33 -7
  88. package/package.json +1 -1
@@ -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,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
  /**
@@ -2,7 +2,7 @@ import { VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "./headers.js";
2
2
  import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
3
3
  import { attachRequestCfMetadata, cloneRequestWithUrl } from "./request-pipeline.js";
4
4
  import { isNextStaticPath } from "../utils/asset-prefix.js";
5
- import { handleConfiguredImageOptimization, invalidImageSourceResponse, isImageSourceBodyStatus } from "./image-optimization.js";
5
+ import { handleConfiguredImageOptimization, invalidImageSourceResponse, isImageSourceBodyStatus, markImageBuildAsset } from "./image-optimization.js";
6
6
  import { createStaticAssetRequest, mergeHeaders } from "./worker-utils.js";
7
7
  import { resolveClientBuildFile } from "./client-build-files.js";
8
8
  //#region src/server/worker-image-optimization.ts
@@ -53,28 +53,47 @@ async function fetchWorkerImageSource(imageUrl, request, options) {
53
53
  } catch {
54
54
  return invalidImageSourceResponse();
55
55
  }
56
- const buildAssetPath = resolveNextStaticFile(decodedPathname, options) ?? resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
57
- if (buildAssetPath !== null) return options.assets.fetch(new Request(new URL(buildAssetPath, request.url)));
56
+ const nextStaticFile = resolveNextStaticFile(decodedPathname, options);
57
+ if (nextStaticFile !== null) return markImageBuildAsset(await options.assets.fetch(new Request(new URL(nextStaticFile, request.url))));
58
+ const clientBuildFile = resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
59
+ if (clientBuildFile !== null) return options.assets.fetch(new Request(new URL(clientBuildFile, request.url)));
58
60
  const sourceResponse = await options.fetchPublicFile(attachRequestCfMetadata(new Request(sourceUrl, { signal: request.signal }), request));
59
61
  if (sourceResponse && isImageSourceBodyStatus(sourceResponse.status)) return sourceResponse;
60
62
  if (sourceResponse) cancelResponseBody(sourceResponse);
61
63
  return invalidImageSourceResponse();
62
64
  }
63
65
  /**
66
+ * Give a public-file source the Cache-Control middleware or `headers()` staged
67
+ * for it, which a direct request receives in place of the asset default and
68
+ * Next.js' internal source fetch reads.
69
+ */
70
+ function withStagedCacheControl(asset, cacheControl) {
71
+ if (cacheControl === null) return asset;
72
+ const headers = new Headers(asset.headers);
73
+ headers.set("Cache-Control", cacheControl);
74
+ return new Response(asset.body, {
75
+ status: asset.status,
76
+ statusText: asset.statusText,
77
+ headers
78
+ });
79
+ }
80
+ /**
64
81
  * Return the public file a framework static-file signal selects, provided the
65
82
  * pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
66
- * request header overrides, as resolveStaticAssetSignal fetches it.
83
+ * request header overrides, as resolveStaticAssetSignal fetches it, and keeps
84
+ * the Cache-Control the pipeline staged.
67
85
  */
68
86
  function fetchSignalledPublicFile(response, sourceRequest, assets) {
69
87
  const signal = isImageSourceBodyStatus(response.status) ? readStaticFileSignal(response) : null;
70
88
  const requestHeaders = readStaticFileSignalRequestHeaders(response);
89
+ const stagedCacheControl = response.headers.get("Cache-Control");
71
90
  cancelResponseBody(response);
72
91
  if (signal === null) return null;
73
92
  let assetPath = signal;
74
93
  try {
75
94
  assetPath = decodeURIComponent(signal);
76
95
  } catch {}
77
- return Promise.resolve(assets.fetch(createStaticAssetRequest(assetPath, sourceRequest, requestHeaders)));
96
+ return Promise.resolve(assets.fetch(createStaticAssetRequest(assetPath, sourceRequest, requestHeaders))).then((asset) => withStagedCacheControl(asset, stagedCacheControl));
78
97
  }
79
98
  /**
80
99
  * Serve the App Router image endpoint once the request handler admitted it
@@ -111,4 +130,4 @@ async function serveWorkerImageOptimization(request, signal, allowedWidths, imag
111
130
  });
112
131
  }
113
132
  //#endregion
114
- export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization };
133
+ export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization, withStagedCacheControl };
@@ -12,6 +12,7 @@ function deriveExecutionContext(base, dispatchPagesRevalidate, isInternalPagesRe
12
12
  ...base?.cache === void 0 ? {} : { cache: base.cache },
13
13
  ...base?.assets === void 0 ? {} : { assets: base.assets },
14
14
  ...base?.trustedRevalidateOrigin === void 0 ? {} : { trustedRevalidateOrigin: base.trustedRevalidateOrigin },
15
+ ...base?.middlewareOrigin === void 0 ? {} : { middlewareOrigin: base.middlewareOrigin },
15
16
  dispatchPagesRevalidate,
16
17
  isInternalPagesRevalidation
17
18
  };
@@ -6,6 +6,8 @@ export type ErrorBoundaryProps = {
6
6
  reset: () => void;
7
7
  }>;
8
8
  children: React.ReactNode;
9
+ /** Whether this boundary renders global-error, at the root of the page. */
10
+ isGlobalErrorBoundary?: boolean;
9
11
  resetKey?: string | null;
10
12
  };
11
13
  export type SerializedBoundaryError = {
@@ -38,6 +40,7 @@ type RedirectBoundaryProps = {
38
40
  type ErrorBoundaryInnerProps = {
39
41
  isImplicitRootErrorBoundary?: boolean;
40
42
  pathname: string | null;
43
+ wrapsGlobalErrorBoundary?: boolean;
41
44
  } & ErrorBoundaryProps;
42
45
  export type ErrorBoundaryState = {
43
46
  error: CapturedError | null;
@@ -66,13 +69,14 @@ export declare class ErrorBoundaryInner extends React.Component<ErrorBoundaryInn
66
69
  reset: () => void;
67
70
  render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | Iterable<React.ReactNode> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ReactPortal | null | undefined> | null | undefined;
68
71
  }
69
- export declare function ErrorBoundary({ fallback, children, resetKey }: ErrorBoundaryProps): React.JSX.Element;
70
- export declare function GlobalErrorBoundary({ fallback, children }: {
72
+ export declare function ErrorBoundary({ fallback, children, isGlobalErrorBoundary, resetKey }: ErrorBoundaryProps): React.JSX.Element;
73
+ export declare function GlobalErrorBoundary({ fallback, children, wrapsGlobalErrorBoundary }: {
71
74
  fallback: React.ComponentType<{
72
75
  error: unknown;
73
76
  reset: () => void;
74
77
  }>;
75
78
  children: React.ReactNode;
79
+ wrapsGlobalErrorBoundary?: boolean;
76
80
  }): React.JSX.Element;
77
81
  type NotFoundBoundaryProps = {
78
82
  fallback: React.ReactNode;
@@ -174,21 +174,23 @@ var ErrorBoundaryInner = class extends React.Component {
174
174
  return this.props.children;
175
175
  }
176
176
  };
177
- function ErrorBoundary({ fallback, children, resetKey }) {
177
+ function ErrorBoundary({ fallback, children, isGlobalErrorBoundary, resetKey }) {
178
178
  const pathname = useErrorBoundaryPathname();
179
179
  return /* @__PURE__ */ jsx(ErrorBoundaryInner, {
180
180
  pathname,
181
181
  resetKey,
182
182
  fallback,
183
+ isGlobalErrorBoundary,
183
184
  children
184
185
  });
185
186
  }
186
- function GlobalErrorBoundary({ fallback, children }) {
187
+ function GlobalErrorBoundary({ fallback, children, wrapsGlobalErrorBoundary }) {
187
188
  const pathname = useErrorBoundaryPathname();
188
189
  return /* @__PURE__ */ jsx(ErrorBoundaryInner, {
189
190
  pathname,
190
191
  fallback,
191
192
  isImplicitRootErrorBoundary: fallback === DefaultGlobalError,
193
+ wrapsGlobalErrorBoundary,
192
194
  children
193
195
  });
194
196
  }
@@ -46,7 +46,9 @@ export type ExecutionContextLike = {
46
46
  requestSignal?: AbortSignal;
47
47
  /**
48
48
  * Server-owned origin middleware runs against. Set by the Node production
49
- * server, whose request URL comes from the client's Host header. Without it
49
+ * server and by entries that srvx serves on Node (Nitro's Node presets,
50
+ * @vitejs/plugin-rsc's dev and preview handlers), whose request URL comes
51
+ * from the client's Host header. Without it
50
52
  * middleware uses the request URL's origin, which Workers can trust because
51
53
  * the platform routes by hostname.
52
54
  */
@@ -115,12 +115,17 @@ function normalizePathTrailingSlash(path, trailingSlash) {
115
115
  if (!path.startsWith("/") || path.startsWith("//") || process.env.__VINEXT_MANUAL_TRAILING_SLASH === "true") return path;
116
116
  const { pathname, query, hash } = parsePath(path);
117
117
  if (trailingSlash) {
118
- if (/\.[^/]+\/?$/.test(pathname)) return `${removeRouteTrailingSlash(pathname)}${query}${hash}`;
118
+ if (hasFileLikeLastSegment(pathname)) return `${removeRouteTrailingSlash(pathname)}${query}${hash}`;
119
119
  if (pathname.endsWith("/")) return `${pathname}${query}${hash}`;
120
120
  return `${pathname}/${query}${hash}`;
121
121
  }
122
122
  return `${removeRouteTrailingSlash(pathname)}${query}${hash}`;
123
123
  }
124
+ function hasFileLikeLastSegment(pathname) {
125
+ const end = pathname.endsWith("/") ? pathname.length - 1 : pathname.length;
126
+ const dot = pathname.indexOf(".", pathname.lastIndexOf("/", end - 1) + 1);
127
+ return dot !== -1 && dot < end - 1;
128
+ }
124
129
  /**
125
130
  * Prepend basePath to a local path for browser URLs / fetches.
126
131
  */
@@ -11,6 +11,11 @@ export declare const VINEXT_CLIENT_REFERENCE_SOURCES = ".vite/vinext-client-refe
11
11
  export type ClientEntryManifest = {
12
12
  pagesClientEntry?: string;
13
13
  appBrowserEntry?: string;
14
+ /**
15
+ * The chunk holding the App Router browser runtime, whose evaluation starts
16
+ * hydration. A user chunk group can move it out of `appBrowserEntry`.
17
+ */
18
+ appBrowserRuntime?: string;
14
19
  /** The App Router RouteManifest chunk, which the browser entry imports as it boots. */
15
20
  appRouteManifest?: string;
16
21
  /** What each App Router client reference loads in the browser. */
@@ -20,6 +20,7 @@ function readClientEntryManifest(clientDir) {
20
20
  const manifest = {};
21
21
  if (typeof value.pagesClientEntry === "string") manifest.pagesClientEntry = value.pagesClientEntry;
22
22
  if (typeof value.appBrowserEntry === "string") manifest.appBrowserEntry = value.appBrowserEntry;
23
+ if (typeof value.appBrowserRuntime === "string") manifest.appBrowserRuntime = value.appBrowserRuntime;
23
24
  if (typeof value.appRouteManifest === "string") manifest.appRouteManifest = value.appRouteManifest;
24
25
  if (isUnknownRecord(value.appClientReferenceChunks)) manifest.appClientReferenceChunks = value.appClientReferenceChunks;
25
26
  return manifest.pagesClientEntry || manifest.appBrowserEntry ? manifest : void 0;
@@ -3,6 +3,7 @@ import { ClientReferenceChunks } from "./client-reference-chunks.js";
3
3
  type ClientRuntimeMetadata = {
4
4
  clientEntryFile?: string;
5
5
  appBootstrapPreinitModules?: string[];
6
+ appBootstrapPreloadModules?: string[];
6
7
  appRouteManifestModule?: string;
7
8
  appClientReferenceChunks?: ClientReferenceChunks;
8
9
  cssGraph?: Record<string, {
@@ -17,6 +18,10 @@ type ClientRuntimeMetadata = {
17
18
  * Read the client build manifest and compute runtime metadata used by
18
19
  * Cloudflare worker entry injection and Node production server startup.
19
20
  *
21
+ * - `appBootstrapPreinitModules` — chunks the App Router browser entry imports,
22
+ * preinitialized during SSR.
23
+ * - `appBootstrapPreloadModules` — those of them that would start hydration if
24
+ * evaluated ahead of the shell, preloaded during SSR instead.
20
25
  * - `appRouteManifestModule` — the App Router RouteManifest chunk, which the
21
26
  * browser entry imports as it boots, preloaded during SSR.
22
27
  * - `appClientReferenceChunks` — what each App Router client reference loads