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
@@ -30,6 +30,16 @@ function canonicalizeRequestUrlPathname(url) {
30
30
  return canonicalizeRequestPathname(pathname) + search;
31
31
  }
32
32
  /**
33
+ * Whether the pathname has a segment the URL parser resolves as `.` or `..`,
34
+ * literally or percent-encoded. Such a pathname changes when it becomes a URL.
35
+ */
36
+ function hasUrlParserDotSegment(pathname) {
37
+ return pathname.split("/").some((segment) => {
38
+ const decodedDots = segment.replaceAll(/%2e/gi, ".");
39
+ return decodedDots === "." || decodedDots === "..";
40
+ });
41
+ }
42
+ /**
33
43
  * Shared request pipeline utilities.
34
44
  *
35
45
  * Extracted from generated entries and server hot paths to keep codegen focused
@@ -472,4 +482,4 @@ function stripRequestUrlFragment(request) {
472
482
  return attachRequestCfMetadata(new Request(stripped, { signal }), request);
473
483
  }
474
484
  //#endregion
475
- export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
485
+ export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, hasUrlParserDotSegment, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
@@ -23,6 +23,13 @@ export type ResponseStageCacheabilityOptions = {
23
23
  * shared response drops them before admission and storage.
24
24
  */
25
25
  recomposesRequestScopedHeaders?: boolean;
26
+ /**
27
+ * Keeps `X-Vinext-Params` when the request scoped headers are dropped. An
28
+ * intercepted response's params include its target's, which the request
29
+ * stage doesn't match. Its identity holds the interception context, so the
30
+ * stored params are its own.
31
+ */
32
+ keepsParamsHeader?: boolean;
26
33
  /** Generated adapter registration, deferred until the response stage executes. */
27
34
  registerCacheAdapters(): void;
28
35
  request: Request;
@@ -24,13 +24,13 @@ async function withResponseStageCacheability(options, render) {
24
24
  const stripsRequestScopedHeaders = options.recomposesRequestScopedHeaders === true && options.cache === "shared" && !options.probeMode;
25
25
  if (!cacheability) {
26
26
  const rendered = await render(context);
27
- return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered) : rendered;
27
+ return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered, options.keepsParamsHeader === true) : rendered;
28
28
  }
29
29
  if (options.policyHeadersAppliedBeforeRender) cacheability.recordResponseStageCachePolicy(context, options.policyHeaders);
30
30
  const state = Reflect.get(context, CACHEABILITY_REQUEST_STATE);
31
31
  if (options.probeMode && options.forceDynamic && !options.policyHeaders?.length && state) state.patternDynamicReason = "dynamic = \"force-dynamic\"";
32
32
  const renderedResponse = await render(context);
33
- const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse) : renderedResponse;
33
+ const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse, options.keepsParamsHeader === true) : renderedResponse;
34
34
  const response = options.policyHeadersAppliedBeforeRender ? rendered : cacheability.applyResponseStageCachePolicy(rendered, context, options.policyHeaders);
35
35
  const complete = (candidate) => cacheability.finalizeWorkerCacheabilityResponse(candidate, context);
36
36
  const route = state?.route;
@@ -40,10 +40,11 @@ async function withResponseStageCacheability(options, render) {
40
40
  }
41
41
  return complete(response);
42
42
  }
43
- function withoutRequestScopedHeaders(response) {
44
- if (!response.headers.has("X-Vinext-Params") && !response.headers.has("X-Vinext-Rendered-Path-And-Search")) return response;
43
+ function withoutRequestScopedHeaders(response, keepsParams) {
44
+ const dropsParams = !keepsParams && response.headers.has("X-Vinext-Params");
45
+ if (!dropsParams && !response.headers.has("X-Vinext-Rendered-Path-And-Search")) return response;
45
46
  const headers = new Headers(response.headers);
46
- headers.delete(VINEXT_PARAMS_HEADER);
47
+ if (dropsParams) headers.delete(VINEXT_PARAMS_HEADER);
47
48
  headers.delete(VINEXT_RENDERED_PATH_AND_SEARCH_HEADER);
48
49
  return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
49
50
  headers,
@@ -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,16 +19,23 @@ 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
  /**
29
36
  * Serve the App Router image endpoint once the request handler admitted it
30
- * through middleware and rewrites. Middleware's response headers are sent with
31
- * the image, as Next.js does.
37
+ * through middleware and rewrites. Config and middleware response headers are
38
+ * sent with the image, as Next.js does.
32
39
  */
33
40
  export declare function serveWorkerImageOptimization(request: Request, signal: ImageOptimizationSignal, allowedWidths: number[] | undefined, imageConfig: ImageConfig | undefined, options: WorkerImageSourceOptions): Promise<Response>;
34
41
  //#endregion
@@ -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,33 +53,52 @@ 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
81
- * through middleware and rewrites. Middleware's response headers are sent with
82
- * the image, as Next.js does.
100
+ * through middleware and rewrites. Config and middleware response headers are
101
+ * sent with the image, as Next.js does.
83
102
  */
84
103
  async function serveWorkerImageOptimization(request, signal, allowedWidths, imageConfig, options) {
85
104
  const imageUrl = new URL(request.url);
@@ -93,7 +112,22 @@ async function serveWorkerImageOptimization(request, signal, allowedWidths, imag
93
112
  });
94
113
  const cookies = signal.middlewareHeaders.getSetCookie();
95
114
  if (cookies.length > 0) middlewareHeaders["set-cookie"] = cookies;
96
- return mergeHeaders(response, middlewareHeaders);
115
+ const merged = mergeHeaders(response, middlewareHeaders);
116
+ if (response.status < 400) return merged;
117
+ const errorHeaders = new Headers(merged.headers);
118
+ let replaced = false;
119
+ for (const name of ["content-type", "content-encoding"]) {
120
+ const value = signal.middlewareHeaders.get(name);
121
+ if (value === null) continue;
122
+ errorHeaders.set(name, value);
123
+ replaced = true;
124
+ }
125
+ if (!replaced) return merged;
126
+ return new Response(merged.body, {
127
+ headers: errorHeaders,
128
+ status: merged.status,
129
+ statusText: merged.statusText
130
+ });
97
131
  }
98
132
  //#endregion
99
- 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
  };
@@ -1,6 +1,14 @@
1
1
  //#region src/server/worker-utils.d.ts
2
2
  export declare function finalizeMissingStaticAssetResponse(response: Response, missingBuildAsset: boolean): Response;
3
3
  export declare function mergeHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
4
+ /**
5
+ * Merge middleware/config headers into a file served from the asset layer.
6
+ * Its Cache-Control is only the layer's default (Workers Static Assets sends
7
+ * `public, max-age=0, must-revalidate` with every file), so one set by
8
+ * middleware or `headers()` wins, as Next.js's static file server sets its
9
+ * own only when the response has none.
10
+ */
11
+ export declare function mergeStaticFileHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
4
12
  export declare function resolveStaticAssetSignal(signalResponse: Response, options: {
5
13
  /** `requestHeaders` are the post-middleware request headers, when middleware set any. */
6
14
  fetchAsset(path: string, requestHeaders: Headers | null): Promise<Response>;
@@ -82,6 +82,23 @@ function mergeHeaders(response, extraHeaders, statusOverride) {
82
82
  headers: merged
83
83
  });
84
84
  }
85
+ /**
86
+ * Merge middleware/config headers into a file served from the asset layer.
87
+ * Its Cache-Control is only the layer's default (Workers Static Assets sends
88
+ * `public, max-age=0, must-revalidate` with every file), so one set by
89
+ * middleware or `headers()` wins, as Next.js's static file server sets its
90
+ * own only when the response has none.
91
+ */
92
+ function mergeStaticFileHeaders(response, extraHeaders, statusOverride) {
93
+ const merged = mergeHeaders(response, extraHeaders, statusOverride);
94
+ for (const [name, value] of Object.entries(extraHeaders)) {
95
+ if (name.toLowerCase() !== "cache-control") continue;
96
+ const cacheControl = (Array.isArray(value) ? value.join(", ") : value).trim();
97
+ if (!cacheControl) continue;
98
+ merged.headers.set(name, cacheControl);
99
+ }
100
+ return merged;
101
+ }
85
102
  async function resolveStaticAssetSignal(signalResponse, options) {
86
103
  const signal = readStaticFileSignal(signalResponse);
87
104
  if (!signal) return null;
@@ -100,7 +117,7 @@ async function resolveStaticAssetSignal(signalResponse, options) {
100
117
  const requestHeaders = readStaticFileSignalRequestHeaders(signalResponse);
101
118
  cancelResponseBody(signalResponse);
102
119
  const assetResponse = await options.fetchAsset(assetPath, requestHeaders);
103
- return mergeHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
120
+ return mergeStaticFileHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
104
121
  }
105
122
  /**
106
123
  * Retarget a Worker asset request without dropping its conditional/range
@@ -116,4 +133,4 @@ function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
116
133
  });
117
134
  }
118
135
  //#endregion
119
- export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, resolveStaticAssetSignal };
136
+ export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders, resolveStaticAssetSignal };
@@ -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
  }
@@ -71,6 +71,12 @@ export declare function resolveImageSource(v: {
71
71
  * Configurable via `images.deviceSizes` in next.config.js.
72
72
  */
73
73
  export declare const RESPONSIVE_WIDTHS: number[];
74
+ /**
75
+ * Parse a `quality` prop like Next.js's `getInt` (shared/lib/get-img-props.ts):
76
+ * finite numbers and digit-only strings pass, anything else becomes NaN, which
77
+ * the default loader treats as 75.
78
+ */
79
+ export declare function parseImageQuality(quality: unknown): number | undefined;
74
80
  /**
75
81
  * Build a `/_next/image` optimization URL.
76
82
  *
@@ -52,6 +52,14 @@ const __imageSizes = (() => {
52
52
  ];
53
53
  }
54
54
  })();
55
+ const __imageQualities = (() => {
56
+ try {
57
+ const qualities = JSON.parse(process.env.__VINEXT_IMAGE_QUALITIES ?? "null");
58
+ return Array.isArray(qualities) ? qualities.sort((a, b) => a - b) : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ })();
55
63
  /**
56
64
  * Whether dangerouslyAllowSVG is enabled in next.config.js.
57
65
  * When false (default), .svg sources auto-skip the optimization endpoint
@@ -195,6 +203,33 @@ function extractLocalDeploymentId(src) {
195
203
  }
196
204
  const __imageEndpoint = `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/_next/image${process.env.__VINEXT_TRAILING_SLASH === "true" ? "/" : ""}`;
197
205
  /**
206
+ * Parse a `quality` prop like Next.js's `getInt` (shared/lib/get-img-props.ts):
207
+ * finite numbers and digit-only strings pass, anything else becomes NaN, which
208
+ * the default loader treats as 75.
209
+ */
210
+ function parseImageQuality(quality) {
211
+ if (quality === void 0) return void 0;
212
+ if (typeof quality === "number") return Number.isFinite(quality) ? quality : NaN;
213
+ if (typeof quality === "string" && /^[0-9]+$/.test(quality)) return parseInt(quality, 10);
214
+ return NaN;
215
+ }
216
+ /**
217
+ * Round a `quality` prop to the closest entry of `images.qualities`, as the
218
+ * Next.js default loader does (shared/lib/find-closest-quality.ts), so the
219
+ * URL is one the optimization endpoint accepts.
220
+ *
221
+ * With no list configured the prop is used as written (75 when absent). This
222
+ * is deliberate: Next.js 16 defaults the list to `[75]` and would round every
223
+ * quality to 75, but vinext keeps the Next.js 14/15 behaviour for an unset
224
+ * list so migrating apps keep the qualities they ask for (see
225
+ * ImageConfig.qualities in server/image-optimization.ts).
226
+ */
227
+ function findClosestQuality(quality) {
228
+ const q = quality || 75;
229
+ if (!__imageQualities?.length) return q;
230
+ return __imageQualities.reduce((prev, cur) => Math.abs(cur - q) < Math.abs(prev - q) ? cur : prev, __imageQualities[0]);
231
+ }
232
+ /**
198
233
  * Build a `/_next/image` optimization URL.
199
234
  *
200
235
  * In production (Cloudflare Workers), the worker intercepts this path and uses
@@ -202,9 +237,10 @@ const __imageEndpoint = `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/_next/image
202
237
  * server handles it as a passthrough (serves the original file).
203
238
  */
204
239
  function imageOptimizationUrl(src, width, quality = 75) {
240
+ const q = findClosestQuality(quality);
205
241
  const source = extractLocalDeploymentId(src);
206
242
  const deploymentQuery = source.src.startsWith("/") && source.deploymentId ? `&dpl=${source.deploymentId}` : "";
207
- return `${__imageEndpoint}?url=${encodeURIComponent(source.src)}&w=${width}&q=${quality}${deploymentQuery}`;
243
+ return `${__imageEndpoint}?url=${encodeURIComponent(source.src)}&w=${width}&q=${q}${deploymentQuery}`;
208
244
  }
209
245
  /**
210
246
  * Generate a srcSet string for responsive images.
@@ -283,7 +319,7 @@ function getImageProps(props) {
283
319
  }
284
320
  }
285
321
  }
286
- const imgQuality = typeof _quality === "string" ? Number(_quality) : _quality ?? 75;
322
+ const imgQuality = parseImageQuality(_quality) ?? 75;
287
323
  const resolvedSrc = blockedInProd ? "" : loader ? loader({
288
324
  src,
289
325
  width: imgWidth ?? 0,
@@ -320,4 +356,4 @@ function getImageProps(props) {
320
356
  return { props: Object.assign(imageProps, { "data-nimg": fill ? "fill" : "1" }) };
321
357
  }
322
358
  //#endregion
323
- export { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl };
359
+ export { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, parseImageQuality, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useMergedRef } from "./use-merged-ref.js";
3
- import { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl } from "./image-props.js";
3
+ import { RESPONSIVE_WIDTHS, __dangerouslyAllowSVG, __globallyUnoptimized, __isDev, generateImageAttributes, getFillStyle, getImageProps, imageOptimizationUrl, isRemoteUrl, isSvgUrl, parseImageQuality, resolveImageSource, sanitizeBlurDataURL, validateRemoteUrl } from "./image-props.js";
4
4
  import { forwardRef, useEffect, useLayoutEffect, useRef, useState } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import * as ReactDOM from "react-dom";
@@ -197,7 +197,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
197
197
  });
198
198
  }
199
199
  if (loader) {
200
- const resolvedQuality = typeof quality === "string" ? Number(quality) : quality ?? 75;
200
+ const resolvedQuality = parseImageQuality(quality) ?? 75;
201
201
  const resolvedSrc = loader({
202
202
  src,
203
203
  width: imgWidth ?? 0,
@@ -293,7 +293,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
293
293
  });
294
294
  }
295
295
  }
296
- const imgQuality = typeof quality === "string" ? Number(quality) : quality ?? 75;
296
+ const imgQuality = parseImageQuality(quality) ?? 75;
297
297
  const skipOptimization = isSvgUrl(src) && !__dangerouslyAllowSVG;
298
298
  const optimizedAttributes = imgWidth && !fill && !skipOptimization ? generateImageAttributes(src, imgWidth, imgQuality, sizes) : void 0;
299
299
  const srcSet = optimizedAttributes ? optimizedAttributes.srcSet : imgWidth && !fill ? RESPONSIVE_WIDTHS.filter((w) => w <= imgWidth * 2).map((w) => `${src} ${w}w`).join(", ") || `${src} ${imgWidth}w` : void 0;
@@ -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
  */
@@ -28,10 +28,13 @@ export declare function setAppElementsRenderedSearch(elements: AppElements, sear
28
28
  * that arrives after the head (initial hydration) is still seen.
29
29
  */
30
30
  export declare function bindAppElementsRenderedSearch(elements: AppElements, snapshot: Pick<ClientNavigationRenderSnapshot, "renderedSearch" | "search">): void;
31
+ export declare function isBfcacheIdentityRetained(identity: string): boolean;
31
32
  export declare const BfcacheIdentityMapContext: React$1.Context<Readonly<Record<string, string>>>;
32
33
  export type BfcacheSlotEntry = {
33
34
  content: React$1.ReactNode;
34
35
  elements?: AppElements;
36
+ /** Every entry key inside a hidden entry, its own included. */
37
+ retainedKeys?: readonly string[];
35
38
  segmentId?: string;
36
39
  stateKey: string;
37
40
  stateKeyMap?: Readonly<Record<string, string>>;