vinext 0.1.1 → 0.1.2

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 (147) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/check.js +4 -3
  5. package/dist/client/navigation-runtime.d.ts +3 -2
  6. package/dist/client/window-next.d.ts +6 -4
  7. package/dist/config/config-matchers.d.ts +11 -4
  8. package/dist/config/config-matchers.js +15 -2
  9. package/dist/config/next-config.d.ts +13 -0
  10. package/dist/config/next-config.js +2 -0
  11. package/dist/deploy.js +9 -2
  12. package/dist/entries/app-rsc-entry.js +7 -1
  13. package/dist/entries/pages-client-entry.js +1 -1
  14. package/dist/entries/pages-server-entry.js +7 -6
  15. package/dist/index.d.ts +0 -2
  16. package/dist/index.js +86 -78
  17. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  18. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  19. package/dist/plugins/og-assets.js +2 -2
  20. package/dist/plugins/optimize-imports.d.ts +8 -4
  21. package/dist/plugins/optimize-imports.js +16 -12
  22. package/dist/plugins/sass.d.ts +53 -24
  23. package/dist/plugins/sass.js +249 -1
  24. package/dist/plugins/wasm-module-import.d.ts +15 -0
  25. package/dist/plugins/wasm-module-import.js +50 -0
  26. package/dist/routing/app-route-graph.d.ts +23 -1
  27. package/dist/routing/app-route-graph.js +47 -8
  28. package/dist/routing/file-matcher.js +1 -1
  29. package/dist/server/app-browser-entry.js +108 -213
  30. package/dist/server/app-browser-error.d.ts +4 -1
  31. package/dist/server/app-browser-error.js +7 -1
  32. package/dist/server/app-browser-history-controller.d.ts +104 -0
  33. package/dist/server/app-browser-history-controller.js +210 -0
  34. package/dist/server/app-browser-navigation-controller.d.ts +3 -2
  35. package/dist/server/app-browser-navigation-controller.js +10 -7
  36. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  37. package/dist/server/app-browser-rsc-redirect.js +30 -8
  38. package/dist/server/app-browser-state.js +4 -7
  39. package/dist/server/app-browser-visible-commit.js +1 -1
  40. package/dist/server/app-fallback-renderer.d.ts +2 -1
  41. package/dist/server/app-fallback-renderer.js +3 -1
  42. package/dist/server/app-middleware.js +1 -0
  43. package/dist/server/app-optimistic-routing.js +22 -1
  44. package/dist/server/app-page-boundary-render.d.ts +2 -1
  45. package/dist/server/app-page-boundary-render.js +4 -2
  46. package/dist/server/app-page-cache.js +9 -7
  47. package/dist/server/app-page-dispatch.d.ts +8 -0
  48. package/dist/server/app-page-dispatch.js +18 -5
  49. package/dist/server/app-page-element-builder.d.ts +22 -2
  50. package/dist/server/app-page-element-builder.js +37 -8
  51. package/dist/server/app-page-execution.d.ts +1 -1
  52. package/dist/server/app-page-execution.js +32 -17
  53. package/dist/server/app-page-render.d.ts +1 -1
  54. package/dist/server/app-page-render.js +7 -14
  55. package/dist/server/app-page-request.d.ts +1 -0
  56. package/dist/server/app-page-request.js +3 -2
  57. package/dist/server/app-page-response.js +1 -1
  58. package/dist/server/app-page-route-wiring.d.ts +3 -1
  59. package/dist/server/app-page-route-wiring.js +8 -7
  60. package/dist/server/app-page-stream.d.ts +1 -6
  61. package/dist/server/app-page-stream.js +1 -4
  62. package/dist/server/app-route-handler-response.js +11 -10
  63. package/dist/server/app-route-handler-runtime.js +12 -1
  64. package/dist/server/app-rsc-handler.js +1 -1
  65. package/dist/server/app-rsc-response-finalizer.js +1 -1
  66. package/dist/server/app-server-action-execution.d.ts +11 -0
  67. package/dist/server/app-server-action-execution.js +5 -2
  68. package/dist/server/app-ssr-entry.js +2 -2
  69. package/dist/server/app-ssr-stream.js +9 -1
  70. package/dist/server/dev-lockfile.js +2 -1
  71. package/dist/server/dev-server.js +43 -12
  72. package/dist/server/headers.d.ts +8 -1
  73. package/dist/server/headers.js +8 -1
  74. package/dist/server/instrumentation-runtime.d.ts +6 -0
  75. package/dist/server/instrumentation-runtime.js +8 -0
  76. package/dist/server/isr-decision.d.ts +79 -0
  77. package/dist/server/isr-decision.js +70 -0
  78. package/dist/server/metadata-route-response.js +5 -3
  79. package/dist/server/middleware-runtime.d.ts +13 -0
  80. package/dist/server/middleware-runtime.js +11 -7
  81. package/dist/server/middleware.js +1 -0
  82. package/dist/server/navigation-planner.d.ts +62 -1
  83. package/dist/server/navigation-planner.js +188 -0
  84. package/dist/server/navigation-trace.d.ts +11 -1
  85. package/dist/server/navigation-trace.js +11 -1
  86. package/dist/server/normalize-path.d.ts +0 -8
  87. package/dist/server/normalize-path.js +3 -1
  88. package/dist/server/otel-tracer-extension.d.ts +45 -0
  89. package/dist/server/otel-tracer-extension.js +89 -0
  90. package/dist/server/pages-api-route.d.ts +14 -3
  91. package/dist/server/pages-api-route.js +6 -1
  92. package/dist/server/pages-asset-tags.d.ts +15 -4
  93. package/dist/server/pages-asset-tags.js +18 -12
  94. package/dist/server/pages-data-route.js +5 -1
  95. package/dist/server/pages-node-compat.d.ts +3 -11
  96. package/dist/server/pages-node-compat.js +174 -121
  97. package/dist/server/pages-page-data.d.ts +28 -0
  98. package/dist/server/pages-page-data.js +61 -17
  99. package/dist/server/pages-page-handler.d.ts +1 -0
  100. package/dist/server/pages-page-handler.js +22 -6
  101. package/dist/server/pages-page-response.d.ts +45 -1
  102. package/dist/server/pages-page-response.js +66 -5
  103. package/dist/server/pages-readiness.d.ts +1 -1
  104. package/dist/server/pages-request-pipeline.d.ts +15 -1
  105. package/dist/server/pages-request-pipeline.js +23 -2
  106. package/dist/server/prod-server.d.ts +39 -1
  107. package/dist/server/prod-server.js +98 -34
  108. package/dist/shims/cache-runtime.js +9 -2
  109. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  110. package/dist/shims/dynamic-preload-chunks.js +77 -0
  111. package/dist/shims/dynamic.d.ts +4 -0
  112. package/dist/shims/dynamic.js +4 -2
  113. package/dist/shims/error-boundary.d.ts +4 -4
  114. package/dist/shims/error.js +37 -11
  115. package/dist/shims/fetch-cache.d.ts +9 -1
  116. package/dist/shims/fetch-cache.js +11 -1
  117. package/dist/shims/head.js +6 -1
  118. package/dist/shims/headers.d.ts +16 -2
  119. package/dist/shims/headers.js +37 -1
  120. package/dist/shims/image-config.js +7 -1
  121. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  122. package/dist/shims/internal/app-route-detection.js +10 -6
  123. package/dist/shims/internal/app-router-context.d.ts +5 -0
  124. package/dist/shims/metadata.d.ts +6 -2
  125. package/dist/shims/metadata.js +32 -14
  126. package/dist/shims/navigation.d.ts +7 -16
  127. package/dist/shims/navigation.js +33 -16
  128. package/dist/shims/router.js +28 -1
  129. package/dist/shims/script-nonce-context.d.ts +1 -1
  130. package/dist/shims/script-nonce-context.js +11 -3
  131. package/dist/shims/server.d.ts +17 -1
  132. package/dist/shims/server.js +31 -6
  133. package/dist/shims/slot.js +1 -1
  134. package/dist/shims/unified-request-context.js +1 -0
  135. package/dist/typegen.js +1 -0
  136. package/dist/utils/client-build-manifest.js +15 -5
  137. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  138. package/dist/utils/client-runtime-metadata.js +63 -0
  139. package/dist/utils/hash.d.ts +17 -1
  140. package/dist/utils/hash.js +36 -1
  141. package/dist/utils/lazy-chunks.d.ts +27 -1
  142. package/dist/utils/lazy-chunks.js +65 -1
  143. package/dist/utils/manifest-paths.d.ts +20 -2
  144. package/dist/utils/manifest-paths.js +38 -3
  145. package/dist/utils/path.d.ts +2 -1
  146. package/dist/utils/path.js +5 -1
  147. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { addBasePathToPathname } from "../utils/base-path.js";
1
2
  import { buildRequestHeadersFromMiddlewareResponse } from "./middleware-request-headers.js";
2
3
  import { NextRequest, RequestCookies, sealRequestCookies, sealRequestHeaders } from "../shims/server.js";
3
4
  //#region src/server/app-route-handler-runtime.ts
@@ -156,7 +157,17 @@ function createTrackedAppRouteRequest(request, options = {}) {
156
157
  }
157
158
  } });
158
159
  };
159
- const wrapRequest = (input) => {
160
+ const wrapRequest = (rawInput) => {
161
+ let input = rawInput;
162
+ if (options.basePath) {
163
+ const inputUrl = new URL(rawInput.url);
164
+ const prefixedPathname = addBasePathToPathname(inputUrl.pathname, options.basePath);
165
+ if (prefixedPathname !== inputUrl.pathname) {
166
+ inputUrl.pathname = prefixedPathname;
167
+ const bodySource = rawInput.body && !rawInput.bodyUsed ? rawInput.clone() : rawInput;
168
+ input = new Request(inputUrl, bodySource);
169
+ }
170
+ }
160
171
  const requestHeaders = options.middlewareHeaders ? buildRequestHeadersFromMiddlewareResponse(input.headers, options.middlewareHeaders) : null;
161
172
  const requestWithOverrides = requestHeaders ? rebuildRequestWithHeaders(input, requestHeaders) : input;
162
173
  const nextRequest = requestWithOverrides instanceof NextRequest ? requestWithOverrides : new NextRequest(requestWithOverrides, { nextConfig: nextConfig ?? void 0 });
@@ -14,10 +14,10 @@ import { isImageOptimizationPath } from "./image-optimization.js";
14
14
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
15
15
  import "./app-page-response.js";
16
16
  import { prerenderRouteParamsPayloadMatchesRoute, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
17
- import { pickRootParams, setRootParams } from "../shims/root-params.js";
18
17
  import { applyAppMiddleware } from "./app-middleware.js";
19
18
  import { buildPageCacheTags } from "./implicit-tags.js";
20
19
  import { buildPostMwRequestContext } from "./app-post-middleware-context.js";
20
+ import { pickRootParams, setRootParams } from "../shims/root-params.js";
21
21
  import { handleAppPrerenderEndpoint } from "./app-prerender-endpoints.js";
22
22
  import { flattenErrorCauses } from "../utils/error-cause.js";
23
23
  import { finalizeAppRscResponse } from "./app-rsc-response-finalizer.js";
@@ -3,9 +3,9 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import "./headers.js";
4
4
  import { normalizePath } from "./normalize-path.js";
5
5
  import { applyConfigHeadersToResponse } from "./request-pipeline.js";
6
+ import { applyCdnResponseHeaders } from "./cache-control.js";
6
7
  import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-cache-busting.js";
7
8
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
8
- import { applyCdnResponseHeaders } from "./cache-control.js";
9
9
  import { mergeVaryHeader } from "./middleware-response-headers.js";
10
10
  //#region src/server/app-rsc-response-finalizer.ts
11
11
  /**
@@ -31,6 +31,17 @@ type AppServerActionRoute = {
31
31
  pattern: string;
32
32
  routeHandler?: unknown;
33
33
  routeSegments?: readonly string[];
34
+ params?: readonly string[] | null;
35
+ slots?: Readonly<Record<string, {
36
+ default?: {
37
+ default?: unknown;
38
+ } | null;
39
+ page?: {
40
+ default?: unknown;
41
+ } | null;
42
+ slotPatternParts?: readonly string[] | null;
43
+ slotParamNames?: readonly string[] | null;
44
+ }>> | null;
34
45
  };
35
46
  /**
36
47
  * Side-effect headers captured during a progressive (no-JS) server action's
@@ -15,6 +15,7 @@ import { applyEdgeRuntimeHeader } from "./app-page-response.js";
15
15
  import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
16
16
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
17
17
  import { deferUntilStreamConsumed } from "./app-page-stream.js";
18
+ import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
18
19
  import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
19
20
  import { buildPageCacheTags } from "./implicit-tags.js";
20
21
  import { getSetCookieName } from "./cookie-utils.js";
@@ -591,10 +592,11 @@ async function handleServerActionRscRequest(options) {
591
592
  url: redirectTarget
592
593
  });
593
594
  setHeadersContext(headersContextFromRequest(redirectRenderRequest));
595
+ const redirectNavigationParams = resolveAppPageNavigationParams(targetMatch.route, targetMatch.params, targetPathname, null);
594
596
  options.setNavigationContext({
595
597
  pathname: targetPathname,
596
598
  searchParams: redirectTarget.searchParams,
597
- params: targetMatch.params
599
+ params: redirectNavigationParams
598
600
  });
599
601
  setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(targetMatch.route) ?? null);
600
602
  setCurrentFetchSoftTags(buildServerActionPageTags(targetMatch.route, targetPathname));
@@ -667,10 +669,11 @@ async function handleServerActionRscRequest(options) {
667
669
  isRscRequest: options.isRscRequest,
668
670
  toInterceptOpts: options.toInterceptOpts
669
671
  });
672
+ const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
670
673
  options.setNavigationContext({
671
674
  pathname: options.cleanPathname,
672
675
  searchParams: options.searchParams,
673
- params: actionRerenderTarget.navigationParams
676
+ params: resolvedActionNavigationParams
674
677
  });
675
678
  await options.ensureRouteLoaded?.(actionRerenderTarget.route);
676
679
  setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
@@ -12,12 +12,12 @@ import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
12
12
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
13
13
  import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
14
14
  import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
15
- import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
16
- import { runWithRootParamsScope } from "../shims/root-params.js";
17
15
  import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-browser-state.js";
18
16
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
19
17
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
20
18
  import { deferUntilStreamConsumed } from "./app-page-stream.js";
19
+ import { runWithRootParamsScope } from "../shims/root-params.js";
20
+ import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
21
21
  import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
22
22
  import { Fragment, createElement, use } from "react";
23
23
  import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
@@ -89,6 +89,12 @@ function fixPreloadAs(html) {
89
89
  const LINK_TAG_RE = /<link\b[^>]*>/gi;
90
90
  const HTML_REWRITE_EXCLUDED_REGION_RE = /<!--[\s\S]*?-->|<(script|style|textarea|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
91
91
  const HTML_REWRITE_EXCLUDED_REGION_START_RE = /<!--|<(script|style|textarea|title)\b[^>]*>/gi;
92
+ const CLOSE_TAG_RES = {
93
+ script: /<\/script\s*>/i,
94
+ style: /<\/style\s*>/i,
95
+ textarea: /<\/textarea\s*>/i,
96
+ title: /<\/title\s*>/i
97
+ };
92
98
  function getHtmlAttribute(tag, name) {
93
99
  const attrRe = /\s([^\s"'=<>`]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
94
100
  let match;
@@ -141,7 +147,9 @@ function findTrailingOpenHtmlRewriteExcludedRegionStart(html) {
141
147
  }
142
148
  const tagName = match[1]?.toLowerCase();
143
149
  if (!tagName) continue;
144
- const close = new RegExp(`</${tagName}\\s*>`, "i").exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));
150
+ const closeTagRe = CLOSE_TAG_RES[tagName];
151
+ if (!closeTagRe) continue;
152
+ const close = closeTagRe.exec(html.slice(HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex));
145
153
  if (!close) return start;
146
154
  HTML_REWRITE_EXCLUDED_REGION_START_RE.lastIndex += close.index + close[0].length;
147
155
  }
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  //#region src/server/dev-lockfile.ts
@@ -98,7 +99,7 @@ function formatAlreadyRunningError(opts) {
98
99
  if (!existing) return [
99
100
  "Another vinext dev server appears to be running in this directory.",
100
101
  "",
101
- `Stale lock file: ${path.relative(cwd, lockfilePath)}`,
102
+ `Stale lock file: ${normalizePathSeparators(path.relative(cwd, lockfilePath))}`,
102
103
  "Remove it manually if no server is running, then re-run `vinext dev`."
103
104
  ].join("\n");
104
105
  const killCommand = process.platform === "win32" ? `taskkill /PID ${existing.pid} /F` : `kill ${existing.pid}`;
@@ -2,10 +2,13 @@ import { createRequestContext, runWithRequestContext } from "../shims/unified-re
2
2
  import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
3
3
  import { patternToNextFormat } from "../routing/route-validation.js";
4
4
  import { matchRoute } from "../routing/pages-router.js";
5
+ import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
5
6
  import { normalizeStaticPathname } from "../routing/route-pattern.js";
6
7
  import { importModule, reportRequestError } from "./instrumentation.js";
7
8
  import { buildCacheStateHeaders } from "./cache-headers.js";
8
9
  import { _runWithCacheState } from "../shims/cache.js";
10
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
11
+ import { buildMissIsrCacheControl, decideIsr } from "./isr-decision.js";
9
12
  import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
10
13
  import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
11
14
  import { runWithPrivateCache } from "../shims/cache-runtime.js";
@@ -62,7 +65,10 @@ function writeGsspRedirect(res, redirect, isDataReq) {
62
65
  let dest = redirect.destination;
63
66
  if (!dest.startsWith("http://") && !dest.startsWith("https://")) dest = dest.replace(/^[\\/]+/, "/");
64
67
  if (isDataReq) {
65
- res.writeHead(200, { "Content-Type": "application/json" });
68
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
69
+ const dataHeaders = { "Content-Type": "application/json" };
70
+ if (deploymentId) dataHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
71
+ res.writeHead(200, dataHeaders);
66
72
  res.end(JSON.stringify({ pageProps: {
67
73
  __N_REDIRECT: dest,
68
74
  __N_REDIRECT_STATUS: status
@@ -228,7 +234,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
228
234
  const match = matchRoute(localeStrippedUrl, routes);
229
235
  if (!match) {
230
236
  if (isDataReq) {
231
- res.writeHead(404, { "Content-Type": "application/json" });
237
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
238
+ const notFoundHeaders = { "Content-Type": "application/json" };
239
+ if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
240
+ res.writeHead(404, notFoundHeaders);
232
241
  res.end("{}");
233
242
  return;
234
243
  }
@@ -325,7 +334,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
325
334
  });
326
335
  if (fallback === false && !isValidPath) {
327
336
  if (isDataReq) {
328
- res.writeHead(404, { "Content-Type": "application/json" });
337
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
338
+ const notFoundHeaders = { "Content-Type": "application/json" };
339
+ if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
340
+ res.writeHead(404, notFoundHeaders);
329
341
  res.end("{}");
330
342
  return;
331
343
  }
@@ -369,7 +381,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
369
381
  }
370
382
  if (result && "notFound" in result && result.notFound) {
371
383
  if (isDataReq) {
372
- res.writeHead(404, { "Content-Type": "application/json" });
384
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
385
+ const notFoundHeaders = { "Content-Type": "application/json" };
386
+ if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
387
+ res.writeHead(404, notFoundHeaders);
373
388
  res.end("{}");
374
389
  return;
375
390
  }
@@ -385,7 +400,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
385
400
  if (Array.isArray(val)) gsspExtraHeaders[key] = val.map(String);
386
401
  else gsspExtraHeaders[key] = String(val);
387
402
  }
388
- if (!Object.keys(gsspExtraHeaders).some((k) => k.toLowerCase() === "cache-control")) gsspExtraHeaders["Cache-Control"] = "private, no-cache, no-store, max-age=0, must-revalidate";
403
+ if (!Object.keys(gsspExtraHeaders).some((k) => k.toLowerCase() === "cache-control")) gsspExtraHeaders["Cache-Control"] = NEVER_CACHE_CONTROL;
389
404
  }
390
405
  const responseHeaders = typeof res.getHeaders === "function" ? res.getHeaders() : void 0;
391
406
  const scriptNonce = getScriptNonceFromNodeHeaderSources(req.headers, responseHeaders);
@@ -405,11 +420,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
405
420
  if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
406
421
  const cachedHtml = cached.value.value.html;
407
422
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
408
- const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
423
+ const { cacheControl: hitCacheControl } = decideIsr({
424
+ cacheState: "HIT",
425
+ kind: "dev",
426
+ revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
427
+ });
409
428
  const hitHeaders = {
410
429
  "Content-Type": "text/html",
411
430
  ...buildCacheStateHeaders("HIT"),
412
- "Cache-Control": `s-maxage=${revalidateSecs}, stale-while-revalidate`
431
+ "Cache-Control": hitCacheControl
413
432
  };
414
433
  if (earlyFontLinkHeader) hitHeaders["Link"] = earlyFontLinkHeader;
415
434
  res.writeHead(200, hitHeaders);
@@ -498,11 +517,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
498
517
  routePath: route.pattern,
499
518
  routeType: "render"
500
519
  });
501
- const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
520
+ const { cacheControl: staleCacheControl } = decideIsr({
521
+ cacheState: "STALE",
522
+ kind: "dev",
523
+ revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
524
+ });
502
525
  const staleHeaders = {
503
526
  "Content-Type": "text/html",
504
527
  ...buildCacheStateHeaders("STALE"),
505
- "Cache-Control": `s-maxage=${revalidateSecs}, stale-while-revalidate`
528
+ "Cache-Control": staleCacheControl
506
529
  };
507
530
  if (earlyFontLinkHeader) staleHeaders["Link"] = earlyFontLinkHeader;
508
531
  res.writeHead(200, staleHeaders);
@@ -524,7 +547,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
524
547
  }
525
548
  if (result && "notFound" in result && result.notFound) {
526
549
  if (isDataReq) {
527
- res.writeHead(404, { "Content-Type": "application/json" });
550
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
551
+ const notFoundHeaders = { "Content-Type": "application/json" };
552
+ if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
553
+ res.writeHead(404, notFoundHeaders);
528
554
  res.end("{}");
529
555
  return;
530
556
  }
@@ -554,6 +580,11 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
554
580
  if (isDataReq) {
555
581
  const dataHeaders = { "Content-Type": "application/json" };
556
582
  if (gsspExtraHeaders) for (const [k, v] of Object.entries(gsspExtraHeaders)) dataHeaders[k] = v;
583
+ const dataRoutePattern = patternToNextFormat(route.pattern);
584
+ if (dataRoutePattern !== "/_error" && dataRoutePattern !== "/500") {
585
+ const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
586
+ if (deploymentId) dataHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
587
+ }
557
588
  res.writeHead(statusCode ?? 200, dataHeaders);
558
589
  res.end(JSON.stringify({ pageProps }));
559
590
  _renderEnd = now();
@@ -677,9 +708,9 @@ hydrate();
677
708
  } catch {}
678
709
  const allScripts = `${nextDataScript}\n ${createInlineScriptTag(`window.__VINEXT_PAGE_PATTERNS__=${safeJsonStringify(pagePatterns)}`, scriptNonce)}\n ${hydrationScript}`;
679
710
  const extraHeaders = { ...gsspExtraHeaders };
680
- if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] = "no-store, must-revalidate";
711
+ if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] = NO_STORE_CACHE_CONTROL;
681
712
  else {
682
- extraHeaders["Cache-Control"] = `s-maxage=${isrRevalidateSeconds}, stale-while-revalidate`;
713
+ extraHeaders["Cache-Control"] = buildMissIsrCacheControl(isrRevalidateSeconds);
683
714
  Object.assign(extraHeaders, buildCacheStateHeaders("MISS"));
684
715
  }
685
716
  if (allFontPreloads.length > 0) extraHeaders["Link"] = allFontPreloads.map((p) => `<${p.href}>; rel=preload; as=font; type=${p.type}; crossorigin`).join(", ");
@@ -60,6 +60,13 @@ declare const RSC_ACTION_HEADER = "x-rsc-action";
60
60
  declare const NEXT_ACTION_HEADER = "next-action";
61
61
  /** Next.js action-not-found indicator (value "1"). */
62
62
  declare const NEXTJS_ACTION_NOT_FOUND_HEADER = "x-nextjs-action-not-found";
63
+ /**
64
+ * Deployment ID header used by the Pages Router for deployment-skew
65
+ * protection. Set on every `/_next/data/` response so the client can detect
66
+ * when a new deployment has been rolled out and trigger a hard navigation.
67
+ * Mirrors `NEXT_NAV_DEPLOYMENT_ID_HEADER` from Next.js `lib/constants.ts`.
68
+ */
69
+ declare const NEXTJS_DEPLOYMENT_ID_HEADER = "x-nextjs-deployment-id";
63
70
  /** Forwarded action marker — set when a request has already been forwarded between workers. */
64
71
  declare const ACTION_FORWARDED_HEADER = "x-action-forwarded";
65
72
  /** Indicates revalidation occurred — value is JSON kind (1 = path/tag, 2 = dynamic-only). */
@@ -100,4 +107,4 @@ declare const INTERNAL_HEADERS: string[];
100
107
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
101
108
  declare const VINEXT_INTERNAL_HEADERS: string[];
102
109
  //#endregion
103
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
110
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -60,6 +60,13 @@ const RSC_ACTION_HEADER = "x-rsc-action";
60
60
  const NEXT_ACTION_HEADER = "next-action";
61
61
  /** Next.js action-not-found indicator (value "1"). */
62
62
  const NEXTJS_ACTION_NOT_FOUND_HEADER = "x-nextjs-action-not-found";
63
+ /**
64
+ * Deployment ID header used by the Pages Router for deployment-skew
65
+ * protection. Set on every `/_next/data/` response so the client can detect
66
+ * when a new deployment has been rolled out and trigger a hard navigation.
67
+ * Mirrors `NEXT_NAV_DEPLOYMENT_ID_HEADER` from Next.js `lib/constants.ts`.
68
+ */
69
+ const NEXTJS_DEPLOYMENT_ID_HEADER = "x-nextjs-deployment-id";
63
70
  /** Forwarded action marker — set when a request has already been forwarded between workers. */
64
71
  const ACTION_FORWARDED_HEADER = "x-action-forwarded";
65
72
  /** Indicates revalidation occurred — value is JSON kind (1 = path/tag, 2 = dynamic-only). */
@@ -122,4 +129,4 @@ const INTERNAL_HEADERS = [
122
129
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
123
130
  const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
124
131
  //#endregion
125
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
132
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -34,6 +34,12 @@
34
34
  * Ensure the instrumentation module's `register()` and `onRequestError`
35
35
  * hooks have been applied exactly once.
36
36
  *
37
+ * After `register()` runs, we extend the OTel tracer provider so that spans
38
+ * created inside Cache Component renders (warmup / fallback-resume phases) use
39
+ * a fresh OTel context rather than inheriting the prerender work unit store.
40
+ * This mirrors Next.js's `afterRegistration()` call in
41
+ * `instrumentation-node-extensions.ts`.
42
+ *
37
43
  * @param instrumentationModule - The imported `instrumentation.ts` module.
38
44
  * Passed as an argument so the generated entry can import it normally
39
45
  * without this helper needing to know the module path.
@@ -1,3 +1,4 @@
1
+ import { extendTracerProviderForCacheComponents } from "./otel-tracer-extension.js";
1
2
  //#region src/server/instrumentation-runtime.ts
2
3
  let initialized = false;
3
4
  let initPromise = null;
@@ -8,6 +9,12 @@ function isOnRequestErrorHandler(value) {
8
9
  * Ensure the instrumentation module's `register()` and `onRequestError`
9
10
  * hooks have been applied exactly once.
10
11
  *
12
+ * After `register()` runs, we extend the OTel tracer provider so that spans
13
+ * created inside Cache Component renders (warmup / fallback-resume phases) use
14
+ * a fresh OTel context rather than inheriting the prerender work unit store.
15
+ * This mirrors Next.js's `afterRegistration()` call in
16
+ * `instrumentation-node-extensions.ts`.
17
+ *
11
18
  * @param instrumentationModule - The imported `instrumentation.ts` module.
12
19
  * Passed as an argument so the generated entry can import it normally
13
20
  * without this helper needing to know the module path.
@@ -18,6 +25,7 @@ async function ensureInstrumentationRegistered(instrumentationModule) {
18
25
  if (initPromise) return initPromise;
19
26
  initPromise = (async () => {
20
27
  if (typeof instrumentationModule.register === "function") await instrumentationModule.register();
28
+ extendTracerProviderForCacheComponents();
21
29
  if (isOnRequestErrorHandler(instrumentationModule.onRequestError)) globalThis.__VINEXT_onRequestErrorHandler__ = instrumentationModule.onRequestError;
22
30
  initialized = true;
23
31
  })();
@@ -0,0 +1,79 @@
1
+ import { CacheControlMetadata } from "../shims/cache.js";
2
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
3
+
4
+ //#region src/server/isr-decision.d.ts
5
+ type IsrDisposition = "HIT" | "STALE" | "MISS";
6
+ type IsrDecision = {
7
+ disposition: IsrDisposition; /** True when the caller must schedule a background regeneration. */
8
+ scheduleRegeneration: boolean; /** The `Cache-Control` string to stamp on the response. */
9
+ cacheControl: string;
10
+ };
11
+ /**
12
+ * Per-router special-case policies for `Cache-Control`.
13
+ *
14
+ * - `"app-page"` / `"pages"`: `buildCachedRevalidateCacheControl` for HIT/STALE.
15
+ * - `"app-route"`: same, but `revalidateSeconds=0` forces `NEVER_CACHE_CONTROL`
16
+ * and `revalidateSeconds=Infinity` forces `STATIC_CACHE_CONTROL`.
17
+ * - `"dev"`: like `"pages"`, but `revalidate=0`/`Infinity` guards are absent
18
+ * (dev never caches when revalidate=0 and never has Infinity entries in practice).
19
+ */
20
+ type IsrPolicyKind = "app-page" | "app-route" | "pages" | "dev";
21
+ type DecideIsrOptions = {
22
+ /**
23
+ * The cache state. Content guards (kind-mismatch, empty body,
24
+ * query-variant-unproven) must have already passed before passing
25
+ * `"HIT"` or `"STALE"` here.
26
+ */
27
+ cacheState: "HIT" | "STALE" | "MISS"; /** Which router is making the decision. */
28
+ kind: IsrPolicyKind;
29
+ /**
30
+ * The route's configured revalidate window in seconds. Used as the fallback
31
+ * when `cacheControlMeta` is absent.
32
+ *
33
+ * For `"dev"` call sites this is the only source of the revalidate value —
34
+ * dev never has metadata attached to a cache entry.
35
+ */
36
+ revalidateSeconds: number;
37
+ /**
38
+ * The expire ceiling (seconds from epoch) read from the route config.
39
+ * Absent when the route pre-dates expire metadata support.
40
+ */
41
+ expireSeconds?: number;
42
+ /**
43
+ * Optional per-entry metadata written alongside the cache value.
44
+ * When present its `revalidate`/`expire` fields override the route defaults,
45
+ * exactly as the call sites do today with `cacheControl?.revalidate ?? revalidateSeconds`.
46
+ */
47
+ cacheControlMeta?: CacheControlMetadata;
48
+ };
49
+ /**
50
+ * Derive the `Cache-Control` string for an ISR response.
51
+ *
52
+ * Content guards (kind mismatch, query-variant-unproven, empty body) are the
53
+ * caller's responsibility and must happen *before* this call. `cacheState`
54
+ * must only be `"HIT"` or `"STALE"` when those guards have already passed.
55
+ */
56
+ declare function decideIsr(options: DecideIsrOptions): IsrDecision;
57
+ /**
58
+ * Build the `Cache-Control` string for a fresh MISS response whose ISR policy
59
+ * is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
60
+ * no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
61
+ *
62
+ * Separate from `decideIsr` because a MISS doesn't read a cache entry and
63
+ * therefore never has `cacheControlMeta`. `expireSeconds` here is the route
64
+ * config ceiling passed directly from the caller (not a per-entry fallback).
65
+ */
66
+ declare function buildMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
67
+ /**
68
+ * Build the `Cache-Control` string for a fresh (MISS) app-route response.
69
+ *
70
+ * Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
71
+ * that `decideIsr` uses for app-route cached responses. `expireSeconds` is
72
+ * the route config ceiling passed directly (not per-entry metadata fallback).
73
+ *
74
+ * Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
75
+ * response that has no per-entry cache metadata.
76
+ */
77
+ declare function buildAppRouteMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
78
+ //#endregion
79
+ export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
@@ -0,0 +1,70 @@
1
+ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildCachedRevalidateCacheControl, buildRevalidateCacheControl } from "./cache-control.js";
2
+ //#region src/server/isr-decision.ts
3
+ /** Resolve effective revalidate/expire, preferring per-entry metadata. */
4
+ function resolveRevalidate(options) {
5
+ return {
6
+ effectiveRevalidate: options.cacheControlMeta?.revalidate ?? options.revalidateSeconds,
7
+ effectiveExpire: options.cacheControlMeta === void 0 ? void 0 : options.cacheControlMeta.expire ?? options.expireSeconds
8
+ };
9
+ }
10
+ function buildCacheControl(disposition, kind, revalidate, expire) {
11
+ if (kind === "app-route") {
12
+ if (revalidate === 0) return NEVER_CACHE_CONTROL;
13
+ if (revalidate === Infinity) return STATIC_CACHE_CONTROL;
14
+ }
15
+ return buildCachedRevalidateCacheControl(disposition, revalidate, expire);
16
+ }
17
+ /**
18
+ * Derive the `Cache-Control` string for an ISR response.
19
+ *
20
+ * Content guards (kind mismatch, query-variant-unproven, empty body) are the
21
+ * caller's responsibility and must happen *before* this call. `cacheState`
22
+ * must only be `"HIT"` or `"STALE"` when those guards have already passed.
23
+ */
24
+ function decideIsr(options) {
25
+ if (options.cacheState === "MISS") return {
26
+ disposition: "MISS",
27
+ scheduleRegeneration: false,
28
+ cacheControl: ""
29
+ };
30
+ const { effectiveRevalidate, effectiveExpire } = resolveRevalidate(options);
31
+ if (options.cacheState === "HIT") return {
32
+ disposition: "HIT",
33
+ scheduleRegeneration: false,
34
+ cacheControl: buildCacheControl("HIT", options.kind, effectiveRevalidate, effectiveExpire)
35
+ };
36
+ return {
37
+ disposition: "STALE",
38
+ scheduleRegeneration: true,
39
+ cacheControl: buildCacheControl("STALE", options.kind, effectiveRevalidate, effectiveExpire)
40
+ };
41
+ }
42
+ /**
43
+ * Build the `Cache-Control` string for a fresh MISS response whose ISR policy
44
+ * is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
45
+ * no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
46
+ *
47
+ * Separate from `decideIsr` because a MISS doesn't read a cache entry and
48
+ * therefore never has `cacheControlMeta`. `expireSeconds` here is the route
49
+ * config ceiling passed directly from the caller (not a per-entry fallback).
50
+ */
51
+ function buildMissIsrCacheControl(revalidateSeconds, expireSeconds) {
52
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
53
+ }
54
+ /**
55
+ * Build the `Cache-Control` string for a fresh (MISS) app-route response.
56
+ *
57
+ * Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
58
+ * that `decideIsr` uses for app-route cached responses. `expireSeconds` is
59
+ * the route config ceiling passed directly (not per-entry metadata fallback).
60
+ *
61
+ * Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
62
+ * response that has no per-entry cache metadata.
63
+ */
64
+ function buildAppRouteMissIsrCacheControl(revalidateSeconds, expireSeconds) {
65
+ if (revalidateSeconds === 0) return NEVER_CACHE_CONTROL;
66
+ if (revalidateSeconds === Infinity) return STATIC_CACHE_CONTROL;
67
+ return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
68
+ }
69
+ //#endregion
70
+ export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
@@ -53,9 +53,9 @@ function getMetadataRouteFunctions(route) {
53
53
  routeFunctionCache.set(route, functions);
54
54
  return functions;
55
55
  }
56
- function matchMetadataRoute(route, cleanPathname, functions) {
56
+ function matchMetadataRoute(route, cleanPathname, functions, getUrlParts) {
57
57
  if (route.patternParts) {
58
- const urlParts = cleanPathname.split("/").filter(Boolean);
58
+ const urlParts = getUrlParts();
59
59
  if (functions.hasGeneratedImageMetadata && urlParts.length > 0) {
60
60
  const params = matchMetadataRoutePattern(urlParts.slice(0, -1), route.patternParts);
61
61
  if (params) return {
@@ -184,6 +184,8 @@ function serveStaticMetadataRoute(route) {
184
184
  }
185
185
  }
186
186
  async function handleMetadataRouteRequest(options) {
187
+ let urlParts;
188
+ const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
187
189
  for (const route of options.metadataRoutes) {
188
190
  const functions = getMetadataRouteFunctions(route);
189
191
  if (route.type === "sitemap" && route.isDynamic) {
@@ -193,7 +195,7 @@ async function handleMetadataRouteRequest(options) {
193
195
  continue;
194
196
  }
195
197
  }
196
- const match = matchMetadataRoute(route, options.cleanPathname, functions);
198
+ const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
197
199
  if (!match) continue;
198
200
  return route.isDynamic ? callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route);
199
201
  }
@@ -19,6 +19,19 @@ type MiddlewareHandler = (request: NextRequest, event: NextFetchEvent) => Respon
19
19
  type ExecuteMiddlewareOptions = {
20
20
  basePath?: string;
21
21
  filePath?: string;
22
+ /**
23
+ * Whether the incoming request was inside the configured basePath. Drives
24
+ * the `nextUrl.basePath` the middleware observes: in-basePath requests are
25
+ * re-prefixed so NextURL reports the configured basePath, while
26
+ * out-of-basePath ("absolute path") requests stay un-prefixed so middleware
27
+ * sees `nextUrl.basePath === ""` (Next.js `getNextPathnameInfo` semantics —
28
+ * see test/e2e/middleware-base-path "should execute from absolute paths").
29
+ * When omitted it is derived from the request URL, which is correct for the
30
+ * Pages prod/deploy adapters because they pass the original (un-stripped)
31
+ * URL. Callers that pass an already-stripped URL (dev server, App Router)
32
+ * must set this explicitly.
33
+ */
34
+ hadBasePath?: boolean;
22
35
  i18nConfig?: NextI18nConfig | null;
23
36
  includeErrorDetails?: boolean;
24
37
  /**
@@ -1,4 +1,5 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
+ import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
2
3
  import "./server-globals.js";
3
4
  import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
4
5
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
@@ -99,12 +100,13 @@ function resolveMiddlewarePathname(request) {
99
100
  return badRequestResponse();
100
101
  }
101
102
  }
102
- function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash) {
103
+ function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash, hadBasePath) {
103
104
  const url = new URL(request.url);
104
105
  let mwRequest = request.body && !request.bodyUsed ? request.clone() : request;
105
- if (normalizedPathname !== url.pathname) {
106
+ const mwPathname = basePath && hadBasePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
107
+ if (mwPathname !== url.pathname) {
106
108
  const mwUrl = new URL(url);
107
- mwUrl.pathname = normalizedPathname;
109
+ mwUrl.pathname = mwPathname;
108
110
  mwRequest = new Request(mwUrl, mwRequest);
109
111
  }
110
112
  const nextConfig = basePath || i18nConfig || trailingSlash ? {
@@ -124,9 +126,11 @@ async function executeMiddleware(options) {
124
126
  continue: false,
125
127
  response: normalizedPathname
126
128
  };
127
- if (!matchesMiddleware(normalizedPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
128
- const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash);
129
- const fetchEvent = new NextFetchEvent({ page: normalizedPathname });
129
+ const hadBasePath = options.hadBasePath ?? (!options.basePath || hasBasePath(new URL(options.request.url).pathname, options.basePath));
130
+ const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
131
+ if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
132
+ const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
133
+ const fetchEvent = new NextFetchEvent({ page: matchPathname });
130
134
  let response;
131
135
  try {
132
136
  response = await middlewareFn(nextRequest, fetchEvent);
@@ -195,7 +199,7 @@ async function executeMiddleware(options) {
195
199
  try {
196
200
  const rewriteParsed = new URL(rewriteUrl, options.request.url);
197
201
  const requestOrigin = new URL(options.request.url).origin;
198
- if (rewriteParsed.origin === requestOrigin) rewritePath = rewriteParsed.pathname + rewriteParsed.search;
202
+ if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
199
203
  else rewritePath = rewriteParsed.href;
200
204
  } catch {
201
205
  rewritePath = rewriteUrl;