vinext 1.4.0 → 1.4.1

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 (104) hide show
  1. package/README.md +20 -20
  2. package/dist/build/prerender.js +3 -2
  3. package/dist/check.js +3 -6
  4. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  5. package/dist/client/dev-error-overlay.d.ts +2 -0
  6. package/dist/client/dev-error-overlay.js +19 -3
  7. package/dist/config/config-matchers.js +2 -0
  8. package/dist/config/destination-params.d.ts +2 -1
  9. package/dist/config/destination-params.js +52 -2
  10. package/dist/config/next-config.d.ts +6 -0
  11. package/dist/config/next-config.js +18 -3
  12. package/dist/entries/app-rsc-entry.js +0 -1
  13. package/dist/entries/pages-server-entry.js +3 -1
  14. package/dist/index.js +29 -20
  15. package/dist/plugins/strip-server-exports.js +2 -1
  16. package/dist/server/api-handler.js +2 -1
  17. package/dist/server/app-browser-entry.js +7 -3
  18. package/dist/server/app-browser-rsc-redirect.js +3 -3
  19. package/dist/server/app-browser-server-action-client.js +2 -2
  20. package/dist/server/app-browser-visible-commit.js +2 -2
  21. package/dist/server/app-optimistic-routing.js +2 -3
  22. package/dist/server/app-page-boundary-render.js +5 -10
  23. package/dist/server/app-page-dispatch.d.ts +0 -1
  24. package/dist/server/app-page-dispatch.js +0 -1
  25. package/dist/server/app-page-element-builder.js +3 -3
  26. package/dist/server/app-page-execution.js +9 -6
  27. package/dist/server/app-page-render.d.ts +0 -1
  28. package/dist/server/app-page-render.js +14 -14
  29. package/dist/server/app-page-response.js +1 -1
  30. package/dist/server/app-page-route-wiring.js +22 -5
  31. package/dist/server/app-page-stream.d.ts +10 -8
  32. package/dist/server/app-page-stream.js +2 -1
  33. package/dist/server/app-request-stage-independent-entry.js +1 -1
  34. package/dist/server/app-route-handler-execution.js +3 -2
  35. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  36. package/dist/server/app-route-handler-runtime.js +11 -7
  37. package/dist/server/app-router-entry.js +1 -1
  38. package/dist/server/app-rsc-cache-busting.d.ts +10 -10
  39. package/dist/server/app-rsc-cache-busting.js +27 -68
  40. package/dist/server/app-rsc-error-handler.js +1 -1
  41. package/dist/server/app-rsc-errors.js +4 -1
  42. package/dist/server/app-rsc-handler.js +53 -62
  43. package/dist/server/app-rsc-request-normalization.d.ts +10 -12
  44. package/dist/server/app-rsc-request-normalization.js +11 -14
  45. package/dist/server/app-rsc-response-stage.js +4 -12
  46. package/dist/server/app-server-action-execution.js +8 -4
  47. package/dist/server/app-ssr-entry.d.ts +6 -0
  48. package/dist/server/app-ssr-entry.js +13 -8
  49. package/dist/server/app-worker-stages.d.ts +12 -3
  50. package/dist/server/app-worker-stages.js +37 -4
  51. package/dist/server/bfcache-identity.d.ts +7 -0
  52. package/dist/server/bfcache-identity.js +10 -1
  53. package/dist/server/cacheability-manifest.js +1 -2
  54. package/dist/server/dev-origin-check.d.ts +2 -1
  55. package/dist/server/dev-origin-check.js +2 -1
  56. package/dist/server/dev-server.js +2 -1
  57. package/dist/server/image-optimization.d.ts +5 -0
  58. package/dist/server/image-optimization.js +6 -1
  59. package/dist/server/isr-cache.js +4 -1
  60. package/dist/server/metadata-route-response.js +1 -1
  61. package/dist/server/middleware-matcher.js +19 -6
  62. package/dist/server/middleware-runtime.js +6 -2
  63. package/dist/server/multi-stage.d.ts +7 -3
  64. package/dist/server/pages-node-compat.d.ts +1 -1
  65. package/dist/server/pages-node-compat.js +2 -0
  66. package/dist/server/pages-page-data.js +2 -1
  67. package/dist/server/pages-page-handler.js +17 -6
  68. package/dist/server/pages-page-response.d.ts +9 -0
  69. package/dist/server/pages-page-response.js +18 -4
  70. package/dist/server/pages-request-stage-entry.js +24 -4
  71. package/dist/server/pages-router-entry.js +3 -2
  72. package/dist/server/prod-server.d.ts +2 -2
  73. package/dist/server/prod-server.js +42 -8
  74. package/dist/server/request-error-log.d.ts +9 -0
  75. package/dist/server/request-error-log.js +13 -0
  76. package/dist/server/request-pipeline.d.ts +9 -4
  77. package/dist/server/request-pipeline.js +23 -15
  78. package/dist/server/rsc-completion-metadata.js +3 -0
  79. package/dist/server/static-file-cache.d.ts +9 -0
  80. package/dist/server/static-file-cache.js +13 -2
  81. package/dist/shims/cache-request-state.d.ts +6 -6
  82. package/dist/shims/cache-request-state.js +6 -36
  83. package/dist/shims/cache-runtime.d.ts +3 -1
  84. package/dist/shims/cache-runtime.js +77 -47
  85. package/dist/shims/cache.d.ts +10 -5
  86. package/dist/shims/cache.js +35 -22
  87. package/dist/shims/client-hook-error.d.ts +2 -0
  88. package/dist/shims/client-hook-error.js +16 -1
  89. package/dist/shims/fetch-cache.js +6 -2
  90. package/dist/shims/headers.d.ts +2 -1
  91. package/dist/shims/headers.js +5 -3
  92. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  93. package/dist/shims/link.js +5 -10
  94. package/dist/shims/navigation.js +2 -3
  95. package/dist/shims/request-state-types.d.ts +2 -1
  96. package/dist/shims/slot.d.ts +6 -2
  97. package/dist/shims/slot.js +10 -3
  98. package/dist/shims/unified-request-context.d.ts +6 -0
  99. package/dist/shims/unified-request-context.js +1 -0
  100. package/dist/utils/cache-life-profiles.d.ts +33 -0
  101. package/dist/utils/cache-life-profiles.js +111 -0
  102. package/dist/utils/middleware-request-headers.d.ts +14 -3
  103. package/dist/utils/middleware-request-headers.js +54 -7
  104. package/package.json +1 -1
@@ -8,6 +8,7 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
8
8
  import { extractLocaleFromUrl, resolvePagesI18nRequest } from "./pages-i18n.js";
9
9
  import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
10
10
  import { reportRequestError } from "./instrumentation.js";
11
+ import { logRequestError } from "./request-error-log.js";
11
12
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
12
13
  import { beginRouteCacheability, isRouteCacheabilityIdentityProbe, isRouteCacheabilityProbe, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityFinalResponseUncacheable, recordRouteCacheability } from "../shims/cacheability-classification.js";
13
14
  import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, shouldUseNextDeployCacheControl } from "./cache-control.js";
@@ -22,7 +23,7 @@ import { buildPagesReadinessNextData } from "./pages-readiness.js";
22
23
  import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
23
24
  import { setCacheStateHeaders } from "./cache-headers.js";
24
25
  import { pagesPathCacheTag } from "./pages-cache-tag.js";
25
- import { renderPagesPageResponse } from "./pages-page-response.js";
26
+ import { applyPagesPrerenderRevalidateHeader, renderPagesPageResponse } from "./pages-page-response.js";
26
27
  import { hasPagesGetInitialProps } from "./pages-get-initial-props.js";
27
28
  import { traceFindPageComponents, tracePagesDocumentStream } from "./pages-execution-tracing.js";
28
29
  import { mergePagesNotFoundSourceHeaders, resolvePagesPageData, resolvePagesStatusPageRenderPath, setPagesResponseHeader } from "./pages-page-data.js";
@@ -244,10 +245,12 @@ function createPagesPageHandler(opts) {
244
245
  hasRewrites
245
246
  });
246
247
  const isCacheabilityProbe = isRouteCacheabilityProbe();
247
- if (!isRouteMissErrorRender && options?.__forcedRoute === void 0) {
248
+ const isTopLevelPageRoute = !isRouteMissErrorRender && options?.__forcedRoute === void 0;
249
+ const isInitialPropsRender = isTopLevelPageRoute && !isStaticPropsRoute && pagesReadiness.gssp !== true && (pagesReadiness.gip === true || pagesReadiness.appGip === true);
250
+ if (isTopLevelPageRoute) {
248
251
  beginRouteCacheability("pages-page", route.pattern);
249
252
  if (isRouteCacheabilityIdentityProbe()) return new Response(null, { status: 204 });
250
- if (pagesReadiness.gssp !== true && pagesReadiness.gip !== true && !(pagesReadiness.appGip === true && !isStaticPropsRoute) && !isStaticPropsRoute) recordRouteCacheability({
253
+ if (pagesReadiness.gssp !== true && !isInitialPropsRender && !isStaticPropsRoute) recordRouteCacheability({
251
254
  cacheable: true,
252
255
  cacheControl: STATIC_CACHE_CONTROL
253
256
  });
@@ -282,7 +285,7 @@ function createPagesPageHandler(opts) {
282
285
  resolveSentResponse = resolve;
283
286
  });
284
287
  const reportSsrError = async (e) => {
285
- console.error("[vinext] SSR error:", e);
288
+ logRequestError("[vinext] SSR error:", e);
286
289
  await reportRequestError(e, {
287
290
  path: url,
288
291
  method: request.method,
@@ -435,6 +438,7 @@ function createPagesPageHandler(opts) {
435
438
  clientTraceMetadata: shouldEmitPagesClientTraceMetadata(pageModule, AppComponent) ? vinextConfig.clientTraceMetadata : void 0,
436
439
  documentReqRes: render.documentReqRes,
437
440
  gsspRes: render.gsspRes,
441
+ isInitialPropsRender,
438
442
  isrCacheKey: pageIsrCacheKey,
439
443
  isrCachePathname,
440
444
  expireSeconds: render.expireSeconds,
@@ -585,6 +589,7 @@ function createPagesPageHandler(opts) {
585
589
  notFoundResponse = stripPagesNotFoundFramingHeaders(notFoundResponse);
586
590
  if (isOnDemandRevalidate) notFoundResponse = withPagesCacheState(notFoundResponse, "REVALIDATED", isrCachePathname);
587
591
  else if (pageDataResult.cacheState) notFoundResponse = withPagesCacheState(notFoundResponse, pageDataResult.cacheState);
592
+ if (pageDataResult.revalidateSeconds !== void 0) applyPagesPrerenderRevalidateHeader(notFoundResponse.headers, pageDataResult.revalidateSeconds);
588
593
  return finalizePagesPreviewResponse(withBrowserPolicy(notFoundResponse), preview);
589
594
  }
590
595
  if (pageDataResult.kind === "response") {
@@ -614,6 +619,7 @@ function createPagesPageHandler(opts) {
614
619
  });
615
620
  if (isDataReq) {
616
621
  const headers = new Headers();
622
+ let resHasCacheControl = false;
617
623
  if (gsspRes && typeof gsspRes.getHeaders === "function") {
618
624
  const gsspHeaders = gsspRes.getHeaders();
619
625
  for (const k of Object.keys(gsspHeaders)) {
@@ -622,10 +628,15 @@ function createPagesPageHandler(opts) {
622
628
  if (k.toLowerCase() === "set-cookie" && Array.isArray(v)) for (const cookie of v) headers.append(k, String(cookie));
623
629
  else headers.set(k, Array.isArray(v) ? v.join(", ") : String(v));
624
630
  }
631
+ resHasCacheControl = headers.has("Cache-Control");
632
+ } else if (isInitialPropsRender && documentReqRes) {
633
+ const resHeaders = documentReqRes.res.getHeaders();
634
+ for (const [name, value] of Object.entries(resHeaders)) if (isCdnResponsePolicyHeader(name)) setPagesResponseHeader(headers, name, value);
635
+ resHasCacheControl = Boolean(resHeaders["cache-control"]);
625
636
  }
626
637
  if (hasCdnResponsePolicy(headers)) markRouteCacheabilityExplicitResponsePolicy();
627
- if (gsspRes) {
628
- if (!headers.has("Cache-Control")) headers.set("Cache-Control", NEVER_CACHE_CONTROL);
638
+ if (gsspRes || isInitialPropsRender) {
639
+ if (!resHasCacheControl) headers.set("Cache-Control", NEVER_CACHE_CONTROL);
629
640
  } else if (isStaticPropsRoute) {
630
641
  if (isrRevalidateSeconds !== null) applyCdnResponseHeaders(headers, {
631
642
  cacheControl: buildMissIsrCacheControl(isrRevalidateSeconds, vinextConfig.expireTime),
@@ -17,6 +17,13 @@ export declare function generatePagesETag(payload: string): string;
17
17
  * A page whose status carries no body has nothing to store.
18
18
  */
19
19
  export declare function rendersPagesNotModified(status: number): boolean;
20
+ /**
21
+ * Hand the revalidate getStaticProps returned to build/prerender.ts, which
22
+ * records it in vinext-prerender.json like Next.js's `initialRevalidateSeconds`.
23
+ * The CDN adapter may rewrite Cache-Control, so the build cannot read it there.
24
+ * Only build-time renders carry this header.
25
+ */
26
+ export declare function applyPagesPrerenderRevalidateHeader(headers: Headers, revalidateSeconds: number | false): void;
20
27
  type PagesFontPreload = {
21
28
  href: string;
22
29
  type: string;
@@ -79,6 +86,8 @@ type RenderPagesPageResponseOptions = {
79
86
  setDocumentInitialHead?: ((head: ReactNode[]) => void) | undefined;
80
87
  documentReqRes?: PagesDocumentReqRes | null;
81
88
  gsspRes: PagesGsspResponse | null;
89
+ /** A page or custom `_app` getInitialProps render; rendered per request like gSSP. */
90
+ isInitialPropsRender?: boolean;
82
91
  isrCacheKey: (router: string, pathname: string) => string;
83
92
  /** Filesystem-route identity used for ISR persistence and cache tags. */
84
93
  isrCachePathname?: string;
@@ -1,7 +1,8 @@
1
1
  import { isBotUserAgent } from "../utils/html-limited-bots.js";
2
- import { NEXTJS_CACHE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
2
+ import { NEXTJS_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { reportRequestError } from "./instrumentation.js";
5
+ import { logRequestError } from "./request-error-log.js";
5
6
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
6
7
  import { markRouteCacheabilityExplicitResponsePolicy } from "../shims/cacheability-classification.js";
7
8
  import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, shouldUseNextDeployCacheControl } from "./cache-control.js";
@@ -17,6 +18,7 @@ import { spliceDocumentHeadClose, spliceDocumentHeadOpen, spliceDocumentScripts,
17
18
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
18
19
  import { setCacheStateHeaders } from "./cache-headers.js";
19
20
  import { pagesPathCacheTag } from "./pages-cache-tag.js";
21
+ import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
20
22
  import { readStreamAsText } from "../utils/text-stream.js";
21
23
  import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
22
24
  import { callDocumentGetInitialProps } from "./document-initial-head.js";
@@ -42,6 +44,17 @@ function generatePagesETag(payload) {
42
44
  function rendersPagesNotModified(status) {
43
45
  return status === 204 || status === 205 || status === 304 || getCdnCacheAdapter().deferNotModifiedResponse?.() !== true;
44
46
  }
47
+ /**
48
+ * Hand the revalidate getStaticProps returned to build/prerender.ts, which
49
+ * records it in vinext-prerender.json like Next.js's `initialRevalidateSeconds`.
50
+ * The CDN adapter may rewrite Cache-Control, so the build cannot read it there.
51
+ * Only build-time renders carry this header.
52
+ */
53
+ function applyPagesPrerenderRevalidateHeader(headers, revalidateSeconds) {
54
+ if (typeof process === "undefined" || process.env?.VINEXT_PRERENDER !== "1") return;
55
+ headers.delete(VINEXT_PRERENDER_CACHE_LIFE_HEADER);
56
+ if (typeof revalidateSeconds === "number") applyPrerenderCacheLifeHeader(headers, { revalidate: revalidateSeconds });
57
+ }
45
58
  function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles, scriptNonce) {
46
59
  let html = "";
47
60
  const nonceAttr = createNonceAttribute(scriptNonce);
@@ -121,7 +134,7 @@ async function buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix) {
121
134
  } });
122
135
  }
123
136
  async function reportPagesIsrCacheWriteError(error, cacheKey, routePattern) {
124
- console.error(`[vinext] Pages ISR cache write failed for ${cacheKey}:`, error);
137
+ logRequestError(`[vinext] Pages ISR cache write failed for ${cacheKey}:`, error);
125
138
  try {
126
139
  await reportRequestError(error, {
127
140
  path: cacheKey,
@@ -284,12 +297,13 @@ async function renderPagesPageResponse(options) {
284
297
  cacheControl: buildMissIsrCacheControl(options.isrRevalidateSeconds, options.expireSeconds),
285
298
  tags: [pathTag]
286
299
  });
300
+ applyPagesPrerenderRevalidateHeader(responseHeaders, options.isrRevalidateSeconds);
287
301
  if (options.isOnDemandRevalidate) {
288
302
  responseHeaders.set(NEXTJS_CACHE_HEADER, "REVALIDATED");
289
303
  responseHeaders.set(VINEXT_REVALIDATED_CACHE_TAG_HEADER, encodeURIComponent(pathTag));
290
304
  } else setCacheStateHeaders(responseHeaders, "MISS");
291
305
  } else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
292
- else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
306
+ else if ((options.gsspRes || options.isInitialPropsRender || options.isFallback) && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
293
307
  if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
294
308
  if (options.userAgent && isPagesStreamingBot(options.userAgent)) {
295
309
  const fullHtml = await readStreamAsText(compositeStream);
@@ -314,4 +328,4 @@ async function renderPagesPageResponse(options) {
314
328
  }), { __vinextStreamedHtmlResponse: true });
315
329
  }
316
330
  //#endregion
317
- export { buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, rendersPagesNotModified };
331
+ export { applyPagesPrerenderRevalidateHeader, buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, rendersPagesNotModified };
@@ -1,9 +1,10 @@
1
1
  import { VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
2
+ import { applyMiddlewareResponseHeadersToRequest } from "../utils/middleware-request-headers.js";
2
3
  import { requestContextFromRequest } from "../config/request-context.js";
3
4
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
4
5
  import { VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./headers.js";
5
6
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
6
- import { attachRequestCfMetadata, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl, haveSameHeaders, withRequestHeaders } from "./request-pipeline.js";
7
+ import { attachRequestCfMetadata, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl, haveSameHeaders, stripRequestUrlFragment, withRequestHeaders } from "./request-pipeline.js";
7
8
  import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js";
8
9
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
9
10
  import { runWithExecutionContext } from "../shims/request-context.js";
@@ -35,6 +36,15 @@ import * as pagesEntry from "virtual:vinext-pages-request-entry";
35
36
  * The public pages-router-entry delegates here. Multi-stage hosts import the
36
37
  * named request handler directly and provide their response-stage dispatcher.
37
38
  */
39
+ /**
40
+ * Whether middleware's request-header overrides changed the request. Next.js
41
+ * also copies ordinary middleware response headers onto the request; like a
42
+ * client's own headers, those do not opt the render out of the shared stage.
43
+ */
44
+ function hasRequestHeaderOverrides(originalHeaders, requestHeaders, middlewareResponseHeaders) {
45
+ const expectedHeaders = middlewareResponseHeaders ? applyMiddlewareResponseHeadersToRequest(originalHeaders, middlewareResponseHeaders) : originalHeaders;
46
+ return !haveSameHeaders(expectedHeaders, requestHeaders);
47
+ }
38
48
  function captureOuterResponsePolicyHeaders(stagedHeaders, transportedPolicyHeaders) {
39
49
  return captureCdnResponsePolicyOverrides(stagedHeaders, new Headers(transportedPolicyHeaders ?? []));
40
50
  }
@@ -86,6 +96,7 @@ function handleRequestStageLocally(request, env, ctx, dispatchResponseStage) {
86
96
  return handleRequest(request, env, ctx, (stageRequest, props, _options, stageEnv, stageCtx) => dispatchResponseStage(stageRequest, stageEnv, stageCtx, props), true, "worker", ctx?.assets).then((response) => applyCdnResponseIdentityHeaders(response, originalRequest));
87
97
  }
88
98
  function handleRequest(request, env, platformCtx, dispatchResponseStage, forceCacheBypass, defaultHostRuntime, assets) {
99
+ request = stripRequestUrlFragment(request);
89
100
  const url = new URL(request.url);
90
101
  const trace = async () => {
91
102
  await pagesEntry.__ensureInstrumentation?.(platformCtx);
@@ -208,6 +219,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
208
219
  requestContext: requestContextFromRequest(request)
209
220
  });
210
221
  let speculativeRenderRequest = null;
222
+ let middlewareResponseHeaders;
211
223
  const deps = {
212
224
  assetPrefix: vinextConfig?.assetPrefix,
213
225
  basePath,
@@ -229,7 +241,15 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
229
241
  authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
230
242
  matchApiRoute: typeof matchApiRoute === "function" ? matchApiRoute : null,
231
243
  matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
232
- runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
244
+ runMiddleware: typeof runMiddleware === "function" ? async (middlewareInput, middlewareCtx, middlewareOptions) => {
245
+ const result = await wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath)(middlewareInput, middlewareCtx, middlewareOptions);
246
+ middlewareResponseHeaders = void 0;
247
+ if (result.responseHeaders) {
248
+ middlewareResponseHeaders = new Headers();
249
+ for (const [key, value] of result.responseHeaders) middlewareResponseHeaders.append(key, value);
250
+ }
251
+ return result;
252
+ } : null,
233
253
  renderPage: (req, resolvedUrl, options, stagedHeaders) => {
234
254
  if (options?.renderErrorPageOnMiss === false && req.body !== null && !req.bodyUsed) speculativeRenderRequest = req;
235
255
  const stageRequest = speculativeRenderRequest === req && req.body !== null && !req.bodyUsed ? attachRequestCfMetadata(req.clone(), req) : req;
@@ -238,7 +258,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
238
258
  authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
239
259
  hasRequestAwareDocument: hasRequestAwareDocument === true,
240
260
  request: req,
241
- requestHeadersChanged: !haveSameHeaders(filteredHeaders, req.headers),
261
+ requestHeadersChanged: hasRequestHeaderOverrides(filteredHeaders, req.headers, middlewareResponseHeaders),
242
262
  routeDataKind: matchedPage?.route.dataKind,
243
263
  stagedHeaders
244
264
  });
@@ -306,7 +326,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
306
326
  const cache = forceCacheBypass || probeMode ? "bypass" : getPagesResponseStageCacheDisposition({
307
327
  authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
308
328
  request: req,
309
- requestHeadersChanged: !haveSameHeaders(filteredHeaders, req.headers),
329
+ requestHeadersChanged: hasRequestHeaderOverrides(filteredHeaders, req.headers, middlewareResponseHeaders),
310
330
  stagedHeaders
311
331
  });
312
332
  const dispatched = trackedDispatchResponseStage(req, responseStageProps(), { cache }, env, ctx);
@@ -1,5 +1,5 @@
1
1
  import { VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM } from "./headers.js";
2
- import { cloneRequestWithHeaders, cloneRequestWithUrl } from "./request-pipeline.js";
2
+ import { cloneRequestWithHeaders, cloneRequestWithUrl, stripRequestUrlFragment } from "./request-pipeline.js";
3
3
  import { runWithExecutionContext } from "../shims/request-context.js";
4
4
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
5
5
  import { validateCdnRequest } from "./cache-control.js";
@@ -52,7 +52,8 @@ async function handleSingleStageRequest(request, env, platformCtx) {
52
52
  return finalize ? finalize(response, ctx) : response;
53
53
  }
54
54
  var pages_router_entry_default = { fetch(request, env, ctx) {
55
- const handle = () => handleSingleStageRequest(request, env, ctx);
55
+ const strippedRequest = stripRequestUrlFragment(request);
56
+ const handle = () => handleSingleStageRequest(strippedRequest, env, ctx);
56
57
  return ctx && typeof ctx.waitUntil === "function" ? runWithExecutionContext(ctx, handle) : handle();
57
58
  } };
58
59
  //#endregion
@@ -102,11 +102,11 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
102
102
  * Without a cache, falls back to async filesystem probing (still non-blocking,
103
103
  * unlike the old sync existsSync/statSync approach).
104
104
  *
105
- * `extraHeaders` go on every response for the file. `representationHeaders` go
105
+ * `stagedHeaders` go on every response for the file. `representationHeaders` go
106
106
  * only on responses that carry the file's bytes (200 and 206), never on a 304,
107
107
  * 412, 416 or 405.
108
108
  */
109
- declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number, representationHeaders?: Record<string, string>): Promise<boolean>;
109
+ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, stagedHeaders?: Record<string, string | string[]>, statusCode?: number, representationHeaders?: Record<string, string>): Promise<boolean>;
110
110
  /**
111
111
  * Convert a Node.js IncomingMessage to a Web Request object.
112
112
  *
@@ -8,7 +8,7 @@ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedire
8
8
  import { readStaticFileSignal } from "./static-file-signal.js";
9
9
  import { canonicalizeRequestPathname, filterInternalHeaders } from "./request-pipeline.js";
10
10
  import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix, isAssetPrefixBuildAsset } from "../utils/asset-prefix.js";
11
- import { StaticFileCache, contentTypeForPath, etagFromFilenameHash } from "./static-file-cache.js";
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";
14
14
  import { normalizePath } from "./normalize-path.js";
@@ -19,7 +19,7 @@ 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, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, parseImageParams, readImageOptimizationSignal } from "./image-optimization.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";
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";
@@ -284,6 +284,16 @@ function readHeaderCaseInsensitive(headersRecord, name) {
284
284
  const value = headersRecord[key];
285
285
  return Array.isArray(value) ? value.join(", ") : value;
286
286
  }
287
+ /** Spell a header's name as `name`, so spreading the record replaces that key. */
288
+ function withHeaderName(headersRecord, name) {
289
+ const lowerName = name.toLowerCase();
290
+ const key = Object.keys(headersRecord).find((entry) => entry !== name && entry.toLowerCase() === lowerName);
291
+ if (key === void 0) return headersRecord;
292
+ const { [key]: value, ...rest } = headersRecord;
293
+ const existing = rest[name];
294
+ rest[name] = existing === void 0 ? value : [existing, value].flat();
295
+ return rest;
296
+ }
287
297
  /** Remove one field name from a Vary header, dropping the header if it empties. */
288
298
  function omitVaryToken(headers, token) {
289
299
  const varyKey = Object.keys(headers).find((key) => key.toLowerCase() === "vary");
@@ -464,6 +474,18 @@ function staticCacheLookupPath(pathname) {
464
474
  if (lookupPath.startsWith("/.vite/") || lookupPath === "/.vite") return null;
465
475
  return lookupPath;
466
476
  }
477
+ /**
478
+ * Give a Node image passthrough Next.js's image Cache-Control instead of the
479
+ * public-file default: a hashed build asset keeps its immutable default, and
480
+ * any other source is revalidated after the default `images.minimumCacheTTL`.
481
+ */
482
+ function withImageSourceCacheControl(headers, sourcePath) {
483
+ if (sourcePath.includes(`/_next/static/`)) return headers;
484
+ return {
485
+ ...headers,
486
+ "Cache-Control": MUTABLE_IMAGE_SOURCE_CACHE_CONTROL
487
+ };
488
+ }
467
489
  /** Pathnames the Pages Router serves from routes rather than the client directory. */
468
490
  function isPagesFilesystemExcludedPath(pathname, phase) {
469
491
  return pathname === "/" || pathname === "/api" || pathname.startsWith("/api/") || phase === "direct" && pathname.startsWith(`/_next/static/`);
@@ -479,12 +501,13 @@ function isPagesFilesystemExcludedPath(pathname, phase) {
479
501
  * Without a cache, falls back to async filesystem probing (still non-blocking,
480
502
  * unlike the old sync existsSync/statSync approach).
481
503
  *
482
- * `extraHeaders` go on every response for the file. `representationHeaders` go
504
+ * `stagedHeaders` go on every response for the file. `representationHeaders` go
483
505
  * only on responses that carry the file's bytes (200 and 206), never on a 304,
484
506
  * 412, 416 or 405.
485
507
  */
486
- async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode, representationHeaders) {
508
+ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, stagedHeaders, statusCode, representationHeaders) {
487
509
  if (pathname === "/") return false;
510
+ const extraHeaders = stagedHeaders && withHeaderName(stagedHeaders, "Cache-Control");
488
511
  const responseStatus = statusCode ?? 200;
489
512
  const omitBody = isNoBodyResponseStatus(responseStatus);
490
513
  const requestRange = req.method === "GET" || req.method === "HEAD" ? req.headers.range : void 0;
@@ -610,7 +633,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
610
633
  const ext = path.extname(resolved.path);
611
634
  const ct = contentTypeForPath(resolved.path);
612
635
  const isHashed = pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`);
613
- const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
636
+ const cacheControl = staticFileCacheControl(isHashed);
614
637
  const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
615
638
  const baseHeaders = {
616
639
  "Content-Type": ct,
@@ -798,6 +821,15 @@ async function statIfFile(filePath) {
798
821
  }
799
822
  }
800
823
  /**
824
+ * Node's parser keeps a `#fragment` sent on the request line in `req.url`.
825
+ * Next.js rebuilds request URLs from pathname + search, so neither routing,
826
+ * middleware nor handlers observe one; drop it before anything reads the URL.
827
+ */
828
+ function stripNodeRequestUrlFragment(req) {
829
+ const hashIndex = req.url?.indexOf("#") ?? -1;
830
+ if (hashIndex !== -1) req.url = req.url?.slice(0, hashIndex);
831
+ }
832
+ /**
801
833
  * Convert a Node.js IncomingMessage to a Web Request object.
802
834
  *
803
835
  * When `urlOverride` is provided, it is used as the path + query string
@@ -1182,7 +1214,7 @@ async function startAppRouterServer(options) {
1182
1214
  "X-Content-Type-Options": "nosniff",
1183
1215
  "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1184
1216
  };
1185
- const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
1217
+ const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
1186
1218
  const sourceUrl = new URL(params.imageUrl, request.url);
1187
1219
  let sourcePathname = null;
1188
1220
  try {
@@ -1299,6 +1331,7 @@ async function startAppRouterServer(options) {
1299
1331
  }
1300
1332
  };
1301
1333
  const handleRequest = async (req, res) => {
1334
+ stripNodeRequestUrlFragment(req);
1302
1335
  try {
1303
1336
  await ensureInstrumentation();
1304
1337
  } catch (error) {
@@ -1319,7 +1352,7 @@ async function startAppRouterServer(options) {
1319
1352
  },
1320
1353
  getStatus: () => res.statusCode,
1321
1354
  headers,
1322
- isRsc: target.split("?", 1)[0].endsWith(".rsc") || headers.get("RSC") === "1",
1355
+ isRsc: headers.get("RSC") === "1",
1323
1356
  method: req.method ?? "GET",
1324
1357
  target
1325
1358
  });
@@ -1434,6 +1467,7 @@ async function createPagesRouterRequestHandler(options) {
1434
1467
  });
1435
1468
  const staticCache = await StaticFileCache.create(clientDir);
1436
1469
  const handleRequest = async (req, res) => {
1470
+ stripNodeRequestUrlFragment(req);
1437
1471
  try {
1438
1472
  await ensureInstrumentation();
1439
1473
  } catch (error) {
@@ -1671,7 +1705,7 @@ async function createPagesRouterRequestHandler(options) {
1671
1705
  "X-Content-Type-Options": "nosniff",
1672
1706
  "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1673
1707
  };
1674
- const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
1708
+ const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
1675
1709
  const sourceUrl = new URL(params.imageUrl, webRequest.url);
1676
1710
  let sourcePathname = null;
1677
1711
  try {
@@ -0,0 +1,9 @@
1
+ //#region src/server/request-error-log.d.ts
2
+ /**
3
+ * Logs an error vinext also passes to instrumentation's `onRequestError`, as
4
+ * `next start` logs it. `VINEXT_QUIET=1` turns these logs off, like Next.js'
5
+ * `quiet` server option, so an `onRequestError` that redacts errors before
6
+ * recording them is the only place they reach.
7
+ */
8
+ export declare function logRequestError(...args: unknown[]): void;
9
+ //#endregion
@@ -0,0 +1,13 @@
1
+ //#region src/server/request-error-log.ts
2
+ /**
3
+ * Logs an error vinext also passes to instrumentation's `onRequestError`, as
4
+ * `next start` logs it. `VINEXT_QUIET=1` turns these logs off, like Next.js'
5
+ * `quiet` server option, so an `onRequestError` that redacts errors before
6
+ * recording them is the only place they reach.
7
+ */
8
+ function logRequestError(...args) {
9
+ if (process.env.VINEXT_QUIET === "1") return;
10
+ console.error(...args);
11
+ }
12
+ //#endregion
13
+ export { logRequestError };
@@ -73,7 +73,6 @@ type ResolvePublicFileRouteOptions = {
73
73
  middlewareContext: StaticFileSignalContext & {
74
74
  requestHeaders?: Headers | null;
75
75
  };
76
- pathname: string;
77
76
  publicFiles: ReadonlySet<string>;
78
77
  request: Request;
79
78
  };
@@ -82,7 +81,7 @@ type ResolvePublicFileRouteOptions = {
82
81
  *
83
82
  * Public files are checked after middleware and before afterFiles/fallback
84
83
  * rewrites. The generated App Router entry provides the public-file set; this
85
- * helper owns the RSC exclusion, existence-first method enforcement, and
84
+ * helper owns existence-first method enforcement and
86
85
  * static-file signaling. Missing mutation targets continue through routing.
87
86
  */
88
87
  export declare function resolvePublicFileRoute(options: ResolvePublicFileRouteOptions): Response | null;
@@ -91,8 +90,8 @@ export declare function normalizeTrailingSlashPathname(pathname: string, trailin
91
90
  * Check if the pathname needs a trailing slash redirect, and return the
92
91
  * redirect Response if so.
93
92
  *
94
- * Follows Next.js behavior:
95
- * - `/api` routes are never redirected
93
+ * Follows Next.js behavior, whose internal trailing-slash redirects cover
94
+ * every path, `/api` included:
96
95
  * - The root path `/` is never redirected
97
96
  * - If `trailingSlash` is true, redirect `/about` → `/about/`
98
97
  * - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
@@ -200,5 +199,11 @@ export declare function withRequestHeaders(request: Request, headers: Headers):
200
199
  * exists.
201
200
  */
202
201
  export declare function cloneRequestWithUrl(request: Request, url: string): Request;
202
+ /**
203
+ * Drop a `#fragment` the host kept from the request line. Next.js rebuilds
204
+ * request URLs from pathname + search, so neither middleware nor handlers
205
+ * ever observe one, and pathname-keyed cache entries must not either.
206
+ */
207
+ export declare function stripRequestUrlFragment(request: Request): Request;
203
208
  //#endregion
204
209
  export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, createStaticFileSignal, getRepeatedSlashRedirect, hasBasePath, isOpenRedirectShaped, sendRepeatedSlashRedirect, stripBasePath };
@@ -84,7 +84,8 @@ function guardProtocolRelativeUrl(rawPathname, search = "") {
84
84
  if (isOpenRedirectShaped(rawPathname)) return notFoundResponse();
85
85
  return null;
86
86
  }
87
- const FILE_LIKE_PATHNAME_RE = /\.[^/]+\/?$/;
87
+ const SLASHED_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/]+\.\w+\/$/;
88
+ const UNSLASHED_NON_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/.]+$/;
88
89
  function isWellKnownPathname(pathname) {
89
90
  return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
90
91
  }
@@ -93,37 +94,32 @@ function isWellKnownPathname(pathname) {
93
94
  *
94
95
  * Public files are checked after middleware and before afterFiles/fallback
95
96
  * rewrites. The generated App Router entry provides the public-file set; this
96
- * helper owns the RSC exclusion, existence-first method enforcement, and
97
+ * helper owns existence-first method enforcement and
97
98
  * static-file signaling. Missing mutation targets continue through routing.
98
99
  */
99
100
  function resolvePublicFileRoute(options) {
100
- if (options.pathname.endsWith(".rsc")) return null;
101
101
  if (!options.publicFiles.has(options.cleanPathname)) return null;
102
102
  if (options.request.method !== "GET" && options.request.method !== "HEAD") return methodNotAllowedResponse("GET, HEAD", { headers: options.middlewareContext.headers ?? void 0 });
103
103
  const { requestHeaders } = options.middlewareContext;
104
104
  return createStaticFileSignal(options.cleanPathname, options.middlewareContext, requestHeaders ? buildRequestHeadersFromMiddlewareResponse(options.request.headers, requestHeaders) : null);
105
105
  }
106
106
  function normalizeTrailingSlashPathname(pathname, trailingSlash) {
107
- if (pathname === "/" || pathname === "/api" || pathname.startsWith("/api/")) return null;
108
- const hasTrailing = pathname.endsWith("/");
107
+ if (pathname === "/") return null;
109
108
  if (trailingSlash) {
110
109
  if (isWellKnownPathname(pathname)) return null;
111
- if (FILE_LIKE_PATHNAME_RE.test(pathname)) {
112
- const normalized = removeTrailingSlash(pathname);
113
- return normalized === pathname ? null : normalized;
114
- }
115
- if (!hasTrailing && !pathname.endsWith(".rsc")) return `${pathname}/`;
110
+ if (SLASHED_FILE_PATHNAME_RE.test(pathname)) return pathname.slice(0, -1);
111
+ if (UNSLASHED_NON_FILE_PATHNAME_RE.test(pathname)) return `${pathname}/`;
116
112
  return null;
117
113
  }
118
- if (hasTrailing) return removeTrailingSlash(pathname);
114
+ if (pathname.endsWith("/")) return removeTrailingSlash(pathname);
119
115
  return null;
120
116
  }
121
117
  /**
122
118
  * Check if the pathname needs a trailing slash redirect, and return the
123
119
  * redirect Response if so.
124
120
  *
125
- * Follows Next.js behavior:
126
- * - `/api` routes are never redirected
121
+ * Follows Next.js behavior, whose internal trailing-slash redirects cover
122
+ * every path, `/api` included:
127
123
  * - The root path `/` is never redirected
128
124
  * - If `trailingSlash` is true, redirect `/about` → `/about/`
129
125
  * - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
@@ -137,7 +133,7 @@ function normalizeTrailingSlashPathname(pathname, trailingSlash) {
137
133
  * @returns A 308 redirect Response, or null if no redirect is needed
138
134
  */
139
135
  function normalizeTrailingSlash(pathname, basePath, trailingSlash, search) {
140
- if (pathname === "/" || pathname === "/api" || pathname.startsWith("/api/")) return null;
136
+ if (pathname === "/") return null;
141
137
  if (isOpenRedirectShaped(pathname)) return notFoundResponse();
142
138
  const normalizedPathname = normalizeTrailingSlashPathname(pathname, trailingSlash);
143
139
  if (normalizedPathname === null) return null;
@@ -463,5 +459,17 @@ function cloneRequestWithUrl(request, url) {
463
459
  }
464
460
  return attachRequestCfMetadata(cloned, request);
465
461
  }
462
+ /**
463
+ * Drop a `#fragment` the host kept from the request line. Next.js rebuilds
464
+ * request URLs from pathname + search, so neither middleware nor handlers
465
+ * ever observe one, and pathname-keyed cache entries must not either.
466
+ */
467
+ function stripRequestUrlFragment(request) {
468
+ const hashIndex = request.url.indexOf("#");
469
+ if (hashIndex === -1) return request;
470
+ const signal = request.signal;
471
+ const stripped = cloneRequestWithUrl(request, request.url.slice(0, hashIndex));
472
+ return attachRequestCfMetadata(new Request(stripped, { signal }), request);
473
+ }
466
474
  //#endregion
467
- 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, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
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 };
@@ -88,9 +88,11 @@ function extractRscCompletionMetadata(buffer) {
88
88
  }
89
89
  function appendRscCompletionMetadata(source, getMetadata) {
90
90
  const reader = source.getReader();
91
+ let cancelled = false;
91
92
  return new ReadableStream({
92
93
  async pull(controller) {
93
94
  const next = await reader.read();
95
+ if (cancelled) return;
94
96
  if (!next.done) {
95
97
  controller.enqueue(escapeFlightChunk(next.value));
96
98
  return;
@@ -100,6 +102,7 @@ function appendRscCompletionMetadata(source, getMetadata) {
100
102
  controller.close();
101
103
  },
102
104
  cancel(reason) {
105
+ cancelled = true;
103
106
  return reader.cancel(reason);
104
107
  }
105
108
  });
@@ -3,6 +3,15 @@
3
3
  export declare const CONTENT_TYPES: Record<string, string>;
4
4
  /** Resolve a path's MIME type with case-insensitive extension matching. */
5
5
  export declare function contentTypeForPath(filePath: string): string;
6
+ /**
7
+ * Default `Cache-Control` for a file in the client build directory. Hashed
8
+ * build assets never change at their URL. Every other file (`public/`) must be
9
+ * revalidated on each reuse, as Next.js's `send` serves it with `max-age=0`:
10
+ * middleware may admit it per request, so a shared cache must not reuse it
11
+ * without asking the origin. A `Cache-Control` set by middleware or config
12
+ * headers replaces this default.
13
+ */
14
+ export declare function staticFileCacheControl(isHashed: boolean): string;
6
15
  /** A servable file variant with pre-computed response headers. */
7
16
  type FileVariant = {
8
17
  /** Absolute file path (used for streaming large files). */
@@ -57,6 +57,17 @@ function contentTypeForPath(filePath) {
57
57
  return CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
58
58
  }
59
59
  /**
60
+ * Default `Cache-Control` for a file in the client build directory. Hashed
61
+ * build assets never change at their URL. Every other file (`public/`) must be
62
+ * revalidated on each reuse, as Next.js's `send` serves it with `max-age=0`:
63
+ * middleware may admit it per request, so a shared cache must not reuse it
64
+ * without asking the origin. A `Cache-Control` set by middleware or config
65
+ * headers replaces this default.
66
+ */
67
+ function staticFileCacheControl(isHashed) {
68
+ return isHashed ? "public, max-age=31536000, immutable" : "public, max-age=0";
69
+ }
70
+ /**
60
71
  * Files below this size are buffered in memory at startup for zero-syscall
61
72
  * serving via res.end(buffer). Above this, files stream via createReadStream.
62
73
  * 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses
@@ -98,7 +109,7 @@ var StaticFileCache = class StaticFileCache {
98
109
  if (isHashed && PRECOMPRESSION_TEMP_FILE_RE.test(relativePath)) continue;
99
110
  const ext = path.extname(relativePath);
100
111
  const contentType = contentTypeForPath(relativePath);
101
- const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
112
+ const cacheControl = staticFileCacheControl(isHashed);
102
113
  const etag = isHashed && etagFromFilenameHash(relativePath, ext) || `W/"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1e3)}"`;
103
114
  const lastModified = new Date(fileInfo.mtimeMs).toUTCString();
104
115
  const baseHeaders = {
@@ -266,4 +277,4 @@ async function* walkFilesWithStats(dir, base = dir) {
266
277
  }
267
278
  }
268
279
  //#endregion
269
- export { CONTENT_TYPES, StaticFileCache, contentTypeForPath, etagFromFilenameHash };
280
+ export { CONTENT_TYPES, StaticFileCache, contentTypeForPath, etagFromFilenameHash, staticFileCacheControl };