vinext 0.2.0 → 1.0.0-beta.0

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 (168) hide show
  1. package/README.md +50 -29
  2. package/dist/build/client-build-config.d.ts +13 -92
  3. package/dist/build/client-build-config.js +17 -95
  4. package/dist/build/css-url-assets.d.ts +3 -1
  5. package/dist/build/css-url-assets.js +18 -1
  6. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  7. package/dist/build/inject-pregenerated-paths.js +4 -1
  8. package/dist/build/prerender-paths.d.ts +23 -0
  9. package/dist/build/prerender-paths.js +296 -0
  10. package/dist/build/prerender-server-entry.d.ts +1 -0
  11. package/dist/build/prerender-server-entry.js +49 -0
  12. package/dist/build/prerender-server-pool.d.ts +44 -0
  13. package/dist/build/prerender-server-pool.js +194 -0
  14. package/dist/build/prerender.d.ts +12 -1
  15. package/dist/build/prerender.js +87 -23
  16. package/dist/build/run-prerender.js +2 -1
  17. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  18. package/dist/cache/cache-adapters-virtual.js +26 -2
  19. package/dist/check.d.ts +4 -6
  20. package/dist/check.js +13 -9
  21. package/dist/cli.js +34 -54
  22. package/dist/client/vinext-next-data.d.ts +4 -1
  23. package/dist/config/config-matchers.js +33 -11
  24. package/dist/config/next-config.d.ts +36 -6
  25. package/dist/config/next-config.js +17 -4
  26. package/dist/config/prerender.d.ts +11 -1
  27. package/dist/config/prerender.js +19 -1
  28. package/dist/config/tsconfig-paths.js +5 -1
  29. package/dist/entries/app-browser-entry.js +5 -1
  30. package/dist/entries/app-rsc-entry.d.ts +3 -2
  31. package/dist/entries/app-rsc-entry.js +4 -0
  32. package/dist/entries/pages-client-entry.js +14 -3
  33. package/dist/entries/pages-server-entry.js +18 -4
  34. package/dist/image/image-adapters-virtual.js +1 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +549 -152
  37. package/dist/init-cloudflare.js +12 -3
  38. package/dist/init-platform.d.ts +10 -1
  39. package/dist/init-platform.js +78 -12
  40. package/dist/init.d.ts +11 -3
  41. package/dist/init.js +129 -31
  42. package/dist/plugins/fonts.js +1 -1
  43. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  44. package/dist/plugins/og-assets.js +2 -1
  45. package/dist/plugins/optimize-imports.js +1 -1
  46. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  47. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  48. package/dist/plugins/sass.d.ts +17 -1
  49. package/dist/plugins/sass.js +74 -1
  50. package/dist/plugins/styled-jsx.d.ts +16 -0
  51. package/dist/plugins/styled-jsx.js +149 -0
  52. package/dist/routing/app-route-graph.js +22 -16
  53. package/dist/routing/file-matcher.d.ts +8 -1
  54. package/dist/routing/file-matcher.js +15 -3
  55. package/dist/server/app-browser-entry.js +44 -14
  56. package/dist/server/app-browser-navigation-controller.js +5 -2
  57. package/dist/server/app-browser-server-action-client.js +5 -3
  58. package/dist/server/app-browser-state.d.ts +1 -0
  59. package/dist/server/app-browser-state.js +1 -1
  60. package/dist/server/app-layout-param-observation.d.ts +1 -1
  61. package/dist/server/app-page-boundary.js +2 -1
  62. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  63. package/dist/server/app-page-cache-finalizer.js +3 -3
  64. package/dist/server/app-page-cache-render.d.ts +1 -1
  65. package/dist/server/app-page-cache.js +23 -6
  66. package/dist/server/app-page-dispatch.d.ts +2 -1
  67. package/dist/server/app-page-dispatch.js +27 -10
  68. package/dist/server/app-page-element-builder.js +1 -0
  69. package/dist/server/app-page-execution.js +2 -1
  70. package/dist/server/app-page-render-identity.d.ts +1 -0
  71. package/dist/server/app-page-render-identity.js +2 -1
  72. package/dist/server/app-page-render.d.ts +3 -1
  73. package/dist/server/app-page-render.js +67 -16
  74. package/dist/server/app-page-response.d.ts +7 -0
  75. package/dist/server/app-page-response.js +16 -4
  76. package/dist/server/app-page-route-wiring.js +3 -3
  77. package/dist/server/app-page-stream.d.ts +2 -1
  78. package/dist/server/app-page-stream.js +1 -1
  79. package/dist/server/app-route-handler-execution.d.ts +1 -1
  80. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  81. package/dist/server/app-route-tree-prefetch.js +187 -0
  82. package/dist/server/app-router-entry.js +1 -1
  83. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  84. package/dist/server/app-rsc-cache-busting.js +9 -5
  85. package/dist/server/app-rsc-handler.d.ts +6 -0
  86. package/dist/server/app-rsc-handler.js +88 -10
  87. package/dist/server/app-rsc-render-mode.d.ts +3 -5
  88. package/dist/server/app-rsc-render-mode.js +5 -12
  89. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  90. package/dist/server/app-rsc-request-normalization.js +4 -5
  91. package/dist/server/app-server-action-execution.js +6 -5
  92. package/dist/server/app-ssr-entry.js +1 -1
  93. package/dist/server/cache-control.d.ts +3 -1
  94. package/dist/server/cache-control.js +13 -1
  95. package/dist/server/dev-module-runner.js +1 -1
  96. package/dist/server/dev-origin-check.d.ts +2 -2
  97. package/dist/server/dev-origin-check.js +2 -2
  98. package/dist/server/dev-server.d.ts +11 -1
  99. package/dist/server/dev-server.js +82 -24
  100. package/dist/server/headers.d.ts +6 -2
  101. package/dist/server/headers.js +11 -5
  102. package/dist/server/image-optimization.d.ts +12 -1
  103. package/dist/server/image-optimization.js +13 -1
  104. package/dist/server/isr-cache.d.ts +13 -4
  105. package/dist/server/isr-cache.js +8 -4
  106. package/dist/server/pages-data-route.d.ts +4 -2
  107. package/dist/server/pages-data-route.js +18 -4
  108. package/dist/server/pages-dev-module-url.d.ts +2 -1
  109. package/dist/server/pages-dev-module-url.js +6 -3
  110. package/dist/server/pages-node-compat.d.ts +12 -1
  111. package/dist/server/pages-node-compat.js +50 -1
  112. package/dist/server/pages-page-data.d.ts +9 -0
  113. package/dist/server/pages-page-data.js +18 -10
  114. package/dist/server/pages-page-handler.js +19 -7
  115. package/dist/server/pages-page-response.d.ts +1 -0
  116. package/dist/server/pages-page-response.js +5 -4
  117. package/dist/server/pages-request-pipeline.d.ts +8 -6
  118. package/dist/server/pages-request-pipeline.js +31 -7
  119. package/dist/server/pages-router-entry.js +1 -1
  120. package/dist/server/prerender-manifest.d.ts +15 -1
  121. package/dist/server/prerender-manifest.js +29 -1
  122. package/dist/server/prod-server.d.ts +10 -7
  123. package/dist/server/prod-server.js +43 -28
  124. package/dist/server/request-pipeline.js +1 -1
  125. package/dist/server/static-file-cache.js +1 -1
  126. package/dist/shims/cache-handler.js +8 -1
  127. package/dist/shims/cache.d.ts +1 -1
  128. package/dist/shims/cache.js +3 -0
  129. package/dist/shims/cdn-cache.d.ts +2 -7
  130. package/dist/shims/cdn-cache.js +2 -14
  131. package/dist/shims/fetch-cache.d.ts +3 -1
  132. package/dist/shims/fetch-cache.js +77 -52
  133. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  134. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  135. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  136. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  137. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  138. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  139. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  140. package/dist/shims/internal/pages-data-target.js +9 -4
  141. package/dist/shims/link.d.ts +1 -1
  142. package/dist/shims/link.js +179 -42
  143. package/dist/shims/navigation.d.ts +15 -3
  144. package/dist/shims/navigation.js +202 -39
  145. package/dist/shims/request-context.js +18 -0
  146. package/dist/shims/request-state-types.d.ts +2 -2
  147. package/dist/shims/router.js +31 -15
  148. package/dist/shims/unified-request-context.d.ts +1 -1
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/typegen.js +1 -1
  151. package/dist/utils/middleware-request-headers.js +1 -1
  152. package/dist/utils/project.d.ts +1 -1
  153. package/dist/utils/protocol-headers.d.ts +7 -1
  154. package/dist/utils/protocol-headers.js +7 -1
  155. package/dist/utils/vite-version.d.ts +4 -12
  156. package/dist/utils/vite-version.js +39 -21
  157. package/package.json +31 -13
  158. package/dist/cloudflare/index.d.ts +0 -3
  159. package/dist/cloudflare/index.js +0 -3
  160. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  161. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  162. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -435
  163. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  164. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  165. package/dist/packages/cloudflare/src/deploy.js +0 -276
  166. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  167. package/dist/packages/cloudflare/src/tpr.js +0 -561
  168. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
@@ -1,25 +1,18 @@
1
1
  //#region src/server/app-rsc-render-mode.ts
2
2
  const APP_RSC_RENDER_MODE_NAVIGATION = "navigation";
3
+ const APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL = "prefetch-dynamic-shell";
3
4
  const APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL = "prefetch-loading-shell";
4
- const APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI = "refresh-preserve-ui";
5
- const APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI = "action-rerender-preserve-ui";
6
- function shouldSuppressLoadingBoundaries(mode) {
7
- return mode === "refresh-preserve-ui" || mode === "action-rerender-preserve-ui";
8
- }
9
- function shouldUsePreserveUiCacheVariant(mode) {
10
- return shouldSuppressLoadingBoundaries(mode);
11
- }
12
5
  function getRscRenderModeCacheVariant(mode) {
6
+ if (mode === "prefetch-dynamic-shell") return "prefetch-dynamic-shell";
13
7
  if (mode === "prefetch-loading-shell") return "prefetch-loading-shell";
14
- return shouldUsePreserveUiCacheVariant(mode) ? "preserve-ui" : null;
8
+ return null;
15
9
  }
16
10
  function parseAppRscRenderMode(value) {
17
11
  switch (value) {
12
+ case APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL: return APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL;
18
13
  case APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL: return APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL;
19
- case APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI: return APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI;
20
- case APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI: return APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI;
21
14
  default: return APP_RSC_RENDER_MODE_NAVIGATION;
22
15
  }
23
16
  }
24
17
  //#endregion
25
- export { APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI, APP_RSC_RENDER_MODE_NAVIGATION, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI, getRscRenderModeCacheVariant, parseAppRscRenderMode, shouldSuppressLoadingBoundaries };
18
+ export { APP_RSC_RENDER_MODE_NAVIGATION, APP_RSC_RENDER_MODE_PREFETCH_DYNAMIC_SHELL, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, getRscRenderModeCacheVariant, parseAppRscRenderMode };
@@ -31,10 +31,9 @@ type NormalizedRscRequest = {
31
31
  * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
32
32
  * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
33
33
  * `/__vinext/` bypasses this for internal prerender endpoints.
34
- * 6. RSC detection: `.rsc` suffix, or Next-style `RSC: 1` plus the internal
35
- * `_rsc` cache-busting query. The header alone does not select payload
36
- * rendering at the canonical HTML URL, so caches that ignore Vary cannot
37
- * store Flight responses under HTML URLs.
34
+ * 6. RSC detection: `.rsc` suffix or Next-style `RSC: 1`. The internal
35
+ * `_rsc` cache-busting query is validated separately so full-route Flight
36
+ * responses do not share the canonical HTML URL in caches that ignore Vary.
38
37
  * 7. cleanPathname — pathname with `.rsc` suffix stripped
39
38
  * 8. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
40
39
  * 9. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
@@ -27,10 +27,9 @@ import { normalizeInterceptionContextHeader } from "./app-interception-context-h
27
27
  * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
28
28
  * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
29
29
  * `/__vinext/` bypasses this for internal prerender endpoints.
30
- * 6. RSC detection: `.rsc` suffix, or Next-style `RSC: 1` plus the internal
31
- * `_rsc` cache-busting query. The header alone does not select payload
32
- * rendering at the canonical HTML URL, so caches that ignore Vary cannot
33
- * store Flight responses under HTML URLs.
30
+ * 6. RSC detection: `.rsc` suffix or Next-style `RSC: 1`. The internal
31
+ * `_rsc` cache-busting query is validated separately so full-route Flight
32
+ * responses do not share the canonical HTML URL in caches that ignore Vary.
34
33
  * 7. cleanPathname — pathname with `.rsc` suffix stripped
35
34
  * 8. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
36
35
  * 9. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
@@ -57,7 +56,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false) {
57
56
  if (!hadBasePath && !pathname.startsWith("/__vinext/") && !allowOutsideBasePath) return notFoundResponse();
58
57
  if (hadBasePath) pathname = stripBasePath(pathname, basePath);
59
58
  }
60
- const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("RSC") === "1" && url.searchParams.has("_rsc");
59
+ const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("RSC") === "1";
61
60
  const cleanPathname = stripRscSuffix(pathname);
62
61
  const interceptionContextHeader = normalizeInterceptionContextHeader(request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER));
63
62
  const mountedSlotsHeader = normalizeMountedSlotsHeader(request.headers.get(VINEXT_MOUNTED_SLOTS_HEADER));
@@ -4,7 +4,7 @@ import { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS
4
4
  import { isExternalUrl } from "../config/config-matchers.js";
5
5
  import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-error-responses.js";
6
6
  import { validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
7
- import { APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI } from "./app-rsc-render-mode.js";
7
+ import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
8
8
  import { headersContextFromRequest, isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
9
9
  import { getAndClearActionRevalidationKind } from "../shims/cache-request-state.js";
10
10
  import { setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault } from "../shims/fetch-cache.js";
@@ -684,7 +684,7 @@ async function handleServerActionRscRequest(options) {
684
684
  request: redirectRenderRequest,
685
685
  route: targetMatch.route,
686
686
  searchParams: redirectSearchParams,
687
- renderMode: APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI,
687
+ renderMode: APP_RSC_RENDER_MODE_NAVIGATION,
688
688
  observeMetadataSearchParamsAccess: redirectDynamicConfig !== "force-static",
689
689
  observePageSearchParamsAccess: redirectDynamicConfig !== "force-static"
690
690
  });
@@ -737,6 +737,7 @@ async function handleServerActionRscRequest(options) {
737
737
  const match = options.matchRoute(options.cleanPathname);
738
738
  let element;
739
739
  let errorPattern = match ? match.route.pattern : options.cleanPathname;
740
+ const actionRerenderIsRscRequest = true;
740
741
  if (match) {
741
742
  const { route: actionRoute, params: actionParams } = match;
742
743
  const actionRerenderTarget = await resolveAppPageActionRerenderTarget({
@@ -746,7 +747,7 @@ async function handleServerActionRscRequest(options) {
746
747
  findIntercept: options.findIntercept,
747
748
  getRouteParamNames: options.getRouteParamNames,
748
749
  getSourceRoute: options.getSourceRoute,
749
- isRscRequest: options.isRscRequest,
750
+ isRscRequest: actionRerenderIsRscRequest,
750
751
  toInterceptOpts: options.toInterceptOpts
751
752
  });
752
753
  const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
@@ -771,13 +772,13 @@ async function handleServerActionRscRequest(options) {
771
772
  const buildActionRerenderElement = () => options.buildPageElement({
772
773
  cleanPathname: options.cleanPathname,
773
774
  interceptOpts: actionRerenderTarget.interceptOpts,
774
- isRscRequest: options.isRscRequest,
775
+ isRscRequest: actionRerenderIsRscRequest,
775
776
  mountedSlotsHeader: options.mountedSlotsHeader,
776
777
  params: actionRerenderTarget.params,
777
778
  request: options.request,
778
779
  route: actionRerenderTarget.route,
779
780
  searchParams: actionRerenderSearchParams,
780
- renderMode: APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI,
781
+ renderMode: APP_RSC_RENDER_MODE_NAVIGATION,
781
782
  observeMetadataSearchParamsAccess: actionRerenderDynamicConfig !== "force-static",
782
783
  observePageSearchParamsAccess: actionRerenderDynamicConfig !== "force-static"
783
784
  });
@@ -12,11 +12,11 @@ import { runWithNavigationContext } from "../shims/navigation-state.js";
12
12
  import { withScriptNonce } from "../shims/script-nonce-context.js";
13
13
  import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr, safeJsonStringify } from "./html.js";
14
14
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
15
+ import { setPagesClientAssets } from "./pages-client-assets.js";
15
16
  import DefaultGlobalError from "../shims/default-global-error.js";
16
17
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
17
18
  import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
18
19
  import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
19
- import { setPagesClientAssets } from "./pages-client-assets.js";
20
20
  import { createInitialBfcacheMaps } from "./app-bfcache-identity.js";
21
21
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
22
22
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
@@ -2,8 +2,10 @@ import { CdnCacheableHeaderInput } from "../shims/cdn-cache.js";
2
2
 
3
3
  //#region src/server/cache-control.d.ts
4
4
  declare const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
5
+ declare const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
5
6
  declare const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
6
7
  declare const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
8
+ declare function shouldUseNextDeployCacheControl(): boolean;
7
9
  /**
8
10
  * Route a cacheable response's headers through the active CDN cache adapter and
9
11
  * apply the result to `headers`. The default adapter yields a single
@@ -34,4 +36,4 @@ declare function buildRevalidateCacheControl(revalidateSeconds: number, expireSe
34
36
  */
35
37
  declare function buildCachedRevalidateCacheControl(cacheState: "HIT" | "STALE", revalidateSeconds: number, expireSeconds?: number): string;
36
38
  //#endregion
37
- export { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl };
39
+ export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, shouldUseNextDeployCacheControl };
@@ -1,9 +1,17 @@
1
1
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
2
2
  //#region src/server/cache-control.ts
3
3
  const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
4
+ const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
4
5
  const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
5
6
  const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
6
7
  const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
8
+ const SHARED_CACHE_DIRECTIVE_RE = /(?:^|,)\s*s-maxage\s*=/i;
9
+ function shouldUseNextDeployCacheControl() {
10
+ return process.env.VINEXT_NEXT_DEPLOY_CACHE_CONTROL === "1";
11
+ }
12
+ function isSharedCacheControl(cacheControl) {
13
+ return SHARED_CACHE_DIRECTIVE_RE.test(cacheControl);
14
+ }
7
15
  /**
8
16
  * Route a cacheable response's headers through the active CDN cache adapter and
9
17
  * apply the result to `headers`. The default adapter yields a single
@@ -17,6 +25,10 @@ const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
17
25
  */
18
26
  function applyCdnResponseHeaders(headers, input) {
19
27
  headers.delete("Cache-Control");
28
+ if (shouldUseNextDeployCacheControl() && isSharedCacheControl(input.cacheControl)) {
29
+ headers.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
30
+ return;
31
+ }
20
32
  const map = getCdnCacheAdapter().buildResponseHeaders(input);
21
33
  for (const [name, value] of Object.entries(map)) {
22
34
  if (value === null) {
@@ -53,4 +65,4 @@ function buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expire
53
65
  return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
54
66
  }
55
67
  //#endregion
56
- export { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl };
68
+ export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, shouldUseNextDeployCacheControl };
@@ -8,7 +8,7 @@ import { ESModulesEvaluator, ModuleRunner, createNodeImportMeta } from "vite/mod
8
8
  *
9
9
  * ## Why this exists
10
10
  *
11
- * Vite 7's `server.ssrLoadModule()` and the lazy `RunnableDevEnvironment.runner`
11
+ * Vite's `server.ssrLoadModule()` and the lazy `RunnableDevEnvironment.runner`
12
12
  * getter both use `SSRCompatModuleRunner`, which constructs a
13
13
  * `createServerModuleRunnerTransport` synchronously. That transport calls
14
14
  * `connect()` immediately, which reads `environment.hot.api.outsideEmitter` —
@@ -5,8 +5,8 @@
5
5
  * Prevents external websites from making cross-origin requests to the
6
6
  * local dev server and reading the responses (data exfiltration).
7
7
  *
8
- * Vite 7 provides built-in CORS and WebSocket origin protection, but
9
- * vinext overrides Vite's CORS config to allow OPTIONS passthrough.
8
+ * Vite provides built-in CORS and WebSocket origin protection, but vinext
9
+ * overrides Vite's CORS config to allow OPTIONS passthrough.
10
10
  * This module adds origin verification to vinext's own request handlers.
11
11
  */
12
12
  /**
@@ -5,8 +5,8 @@
5
5
  * Prevents external websites from making cross-origin requests to the
6
6
  * local dev server and reading the responses (data exfiltration).
7
7
  *
8
- * Vite 7 provides built-in CORS and WebSocket origin protection, but
9
- * vinext overrides Vite's CORS config to allow OPTIONS passthrough.
8
+ * Vite provides built-in CORS and WebSocket origin protection, but vinext
9
+ * overrides Vite's CORS config to allow OPTIONS passthrough.
10
10
  * This module adds origin verification to vinext's own request handlers.
11
11
  */
12
12
  /**
@@ -43,7 +43,17 @@ declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter,
43
43
  * `next.config`. When undefined or empty, no meta tags are emitted.
44
44
  */
45
45
 
46
- clientTraceMetadata?: readonly string[], htmlLimitedBots?: string): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
46
+ clientTraceMetadata?: readonly string[], htmlLimitedBots?: string,
47
+ /**
48
+ * Whether `reactStrictMode: true` is set in next.config. When true, the dev
49
+ * hydration script sets `window.__VINEXT_REACT_STRICT_MODE__` so
50
+ * `wrapWithRouterContext` wraps the tree in `<React.StrictMode>` on the
51
+ * initial hydration and every navigation. Pages Router default is OFF
52
+ * (Next.js: `reactStrictMode === null ? false`), so callers pass
53
+ * `nextConfig?.reactStrictMode === true`.
54
+ */
55
+
56
+ reactStrictMode?: boolean): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
47
57
 
48
58
  statusCode?: number,
49
59
  /**
@@ -1,5 +1,5 @@
1
1
  import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
- import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
2
+ import { createValidFileMatcher, findFileWithExtensions, findFileWithExts } from "../routing/file-matcher.js";
3
3
  import { patternToNextFormat } from "../routing/route-validation.js";
4
4
  import { matchRoute } from "../routing/pages-router.js";
5
5
  import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
@@ -17,7 +17,7 @@ import "../shims/router-state.js";
17
17
  import { runWithHeadState } from "../shims/head-state.js";
18
18
  import { runWithServerInsertedHTMLState } from "../shims/navigation-state.js";
19
19
  import { withScriptNonce } from "../shims/script-nonce-context.js";
20
- import { createInlineScriptTag, createNonceAttribute, safeJsonStringify } from "./html.js";
20
+ import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr, safeJsonStringify } from "./html.js";
21
21
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
22
22
  import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
23
23
  import { logRequest, now } from "./request-log.js";
@@ -31,8 +31,9 @@ import { hasPagesGetInitialProps, loadDevAppInitialProps, loadPagesGetInitialPro
31
31
  import { isSerializableProps } from "./pages-serializable-props.js";
32
32
  import { isBotUserAgent } from "../utils/html-limited-bots.js";
33
33
  import { getPagesRouteParams, matchesPagesStaticPath } from "./pages-page-data.js";
34
- import { createPagesDevModuleUrl } from "./pages-dev-module-url.js";
35
- import { attachPagesRequestCookies } from "./pages-node-compat.js";
34
+ import { createPagesDevAssetUrl, createPagesDevModuleUrl } from "./pages-dev-module-url.js";
35
+ import { getManifestFilesForModule } from "./pages-asset-tags.js";
36
+ import { attachPagesRequestCookies, getPagesPreviewDataFromCookieHeader } from "./pages-node-compat.js";
36
37
  import path from "node:path";
37
38
  import React from "react";
38
39
  import { renderToReadableStream } from "react-dom/server.edge";
@@ -52,6 +53,35 @@ async function renderToStringAsync(element) {
52
53
  async function renderIsrPassToStringAsync(element) {
53
54
  return await runWithServerInsertedHTMLState(() => runWithHeadState(() => _runWithCacheState(() => runWithPrivateCache(() => runWithFetchCache(async () => renderToStringAsync(element))))));
54
55
  }
56
+ const DEV_STYLESHEET_ASSET_RE = /\.(?:css|scss|sass)$/i;
57
+ function createDevInitialStylesheetHeadHTML(options) {
58
+ const { ssrManifest, moduleIds, nonceAttr } = options;
59
+ if (!ssrManifest || moduleIds.length === 0) return "";
60
+ const seen = /* @__PURE__ */ new Set();
61
+ let html = "";
62
+ for (const moduleId of moduleIds) {
63
+ const files = getManifestFilesForModule(ssrManifest, moduleId);
64
+ if (!files) continue;
65
+ for (const file of files) {
66
+ if (!DEV_STYLESHEET_ASSET_RE.test(file) || seen.has(file)) continue;
67
+ seen.add(file);
68
+ const href = createPagesDevAssetUrl(file);
69
+ html += `<link rel="stylesheet"${nonceAttr} href="${escapeHtmlAttr(href)}" />\n `;
70
+ }
71
+ }
72
+ return html;
73
+ }
74
+ async function collectDevInitialStylesheetHeadHTML(runner, moduleIds, nonceAttr) {
75
+ try {
76
+ return createDevInitialStylesheetHeadHTML({
77
+ ssrManifest: (await runner.import("virtual:vinext-pages-client-assets")).default?.ssrManifest,
78
+ moduleIds,
79
+ nonceAttr
80
+ });
81
+ } catch {
82
+ return "";
83
+ }
84
+ }
55
85
  /**
56
86
  * Emit a `getServerSideProps` / `getStaticProps` `{ redirect }` result.
57
87
  *
@@ -102,7 +132,7 @@ const STREAM_BODY_MARKER = "<!--VINEXT_STREAM_BODY-->";
102
132
  * shell sooner).
103
133
  */
104
134
  async function streamPageToResponse(res, element, options) {
105
- const { url, server, fontHeadHTML, scripts, DocumentComponent, statusCode, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, bufferBodyBeforeHeaders = false } = options;
135
+ const { url, server, fontHeadHTML, assetHeadHTML = "", scripts, DocumentComponent, statusCode, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, bufferBodyBeforeHeaders = false } = options;
106
136
  const documentRenderPage = await runDocumentRenderPage({
107
137
  DocumentComponent,
108
138
  enhancePageElement,
@@ -129,7 +159,7 @@ async function streamPageToResponse(res, element, options) {
129
159
  const docProps = documentRenderPage.status === "skipped" ? await loadUserDocumentInitialProps(DocumentComponent) : documentRenderPage.docProps;
130
160
  let docHtml = await renderToStringAsync(docProps ? React.createElement(DocumentComponent, docProps) : React.createElement(DocumentComponent));
131
161
  docHtml = docHtml.replace("__NEXT_MAIN__", STREAM_BODY_MARKER);
132
- if (headHTML || fontHeadHTML) docHtml = docHtml.replace("</head>", ` ${fontHeadHTML}${headHTML}\n</head>`);
162
+ if (headHTML || fontHeadHTML || assetHeadHTML) docHtml = docHtml.replace("</head>", ` ${fontHeadHTML}${headHTML}\n ${assetHeadHTML}\n</head>`);
133
163
  docHtml = docHtml.replace("<!-- __NEXT_SCRIPTS__ -->", scripts);
134
164
  if (!docHtml.includes("__NEXT_DATA__")) docHtml = docHtml.replace("</body>", ` ${scripts}\n</body>`);
135
165
  shellTemplate = docHtml;
@@ -137,6 +167,7 @@ async function streamPageToResponse(res, element, options) {
137
167
  <html>
138
168
  <head>
139
169
  ${fontHeadHTML}${headHTML}
170
+ ${assetHeadHTML}
140
171
  </head>
141
172
  <body>
142
173
  <div id="__next">${STREAM_BODY_MARKER}</div>
@@ -149,7 +180,7 @@ async function streamPageToResponse(res, element, options) {
149
180
  const suffix = transformedShell.slice(markerIdx + 25);
150
181
  const bufferedBody = bufferBodyBeforeHeaders ? await new Response(bodyStream).text() : null;
151
182
  const headers = {
152
- "Content-Type": "text/html",
183
+ "Content-Type": "text/html; charset=utf-8",
153
184
  "Transfer-Encoding": "chunked"
154
185
  };
155
186
  if (extraHeaders) for (const [key, val] of Object.entries(extraHeaders)) if (Array.isArray(val)) res.setHeader(key, val);
@@ -204,7 +235,7 @@ function parseCookieLocale(req, i18nConfig) {
204
235
  * 4. Render the component to HTML
205
236
  * 5. Wrap in _document shell and send response
206
237
  */
207
- function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots) {
238
+ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots, reactStrictMode = false) {
208
239
  const matcher = fileMatcher ?? createValidFileMatcher();
209
240
  const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
210
241
  const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
@@ -337,6 +368,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
337
368
  let isrRevalidateSeconds = null;
338
369
  let isFallbackRender = false;
339
370
  let shouldPersistFallbackData = false;
371
+ let staticPropsPreviewData = false;
340
372
  if (typeof pageModule.getStaticPaths === "function" && route.isDynamic) {
341
373
  const pathsResult = await pageModule.getStaticPaths({
342
374
  locales: i18nConfig?.locales ?? [],
@@ -416,6 +448,12 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
416
448
  __N_SSP: true
417
449
  };
418
450
  const headersBeforeGSSP = new Set(Object.keys(res.getHeaders()));
451
+ const previewData = getPagesPreviewDataFromCookieHeader(req.headers.cookie);
452
+ const previewContext = previewData === false ? {} : {
453
+ draftMode: true,
454
+ preview: true,
455
+ previewData
456
+ };
419
457
  const context = {
420
458
  params: userFacingParams,
421
459
  req,
@@ -424,7 +462,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
424
462
  resolvedUrl: gsspResolvedUrl,
425
463
  locale: locale ?? currentDefaultLocale,
426
464
  locales: i18nConfig?.locales,
427
- defaultLocale: currentDefaultLocale
465
+ defaultLocale: currentDefaultLocale,
466
+ ...previewContext
428
467
  };
429
468
  const result = await pageModule.getServerSideProps(context);
430
469
  if (res.writableEnded) return;
@@ -480,7 +519,14 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
480
519
  const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
481
520
  const cached = await isrGet(cacheKey);
482
521
  const isOnDemandRevalidate = isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]);
483
- if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !cached.value.value.generatedFromDataRequest && !scriptNonce && !isDataReq) {
522
+ const previewData = getPagesPreviewDataFromCookieHeader(req.headers.cookie, { isOnDemandRevalidate });
523
+ staticPropsPreviewData = previewData;
524
+ const previewContext = previewData === false ? {} : {
525
+ draftMode: true,
526
+ preview: true,
527
+ previewData
528
+ };
529
+ if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !cached.value.value.generatedFromDataRequest && !scriptNonce && !isDataReq && previewData === false) {
484
530
  const cachedHtml = cached.value.value.html;
485
531
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
486
532
  const { cacheControl: hitCacheControl } = decideIsr({
@@ -489,7 +535,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
489
535
  revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
490
536
  });
491
537
  const hitHeaders = {
492
- "Content-Type": "text/html",
538
+ "Content-Type": "text/html; charset=utf-8",
493
539
  ...buildCacheStateHeaders("HIT"),
494
540
  "Cache-Control": hitCacheControl
495
541
  };
@@ -498,7 +544,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
498
544
  res.end(transformedHtml);
499
545
  return;
500
546
  }
501
- if (!isOnDemandRevalidate && cached && cached.isStale && cached.value.value?.kind === "PAGES" && !cached.value.value.generatedFromDataRequest && !scriptNonce && !isDataReq) {
547
+ if (!isOnDemandRevalidate && cached && cached.isStale && cached.value.value?.kind === "PAGES" && !cached.value.value.generatedFromDataRequest && !scriptNonce && !isDataReq && previewData === false) {
502
548
  const cachedHtml = cached.value.value.html;
503
549
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
504
550
  triggerBackgroundRegeneration(cacheKey, async () => {
@@ -663,7 +709,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
663
709
  revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
664
710
  });
665
711
  const staleHeaders = {
666
- "Content-Type": "text/html",
712
+ "Content-Type": "text/html; charset=utf-8",
667
713
  ...buildCacheStateHeaders("STALE"),
668
714
  "Cache-Control": staleCacheControl
669
715
  };
@@ -677,9 +723,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
677
723
  locale: locale ?? currentDefaultLocale,
678
724
  locales: i18nConfig?.locales,
679
725
  defaultLocale: currentDefaultLocale,
680
- revalidateReason: isOnDemandRevalidate ? "on-demand" : "stale"
726
+ revalidateReason: isOnDemandRevalidate ? "on-demand" : "stale",
727
+ ...previewContext
681
728
  };
682
- const generatedPageData = !isOnDemandRevalidate && cached?.isStale === false && cached?.value.value?.kind === "PAGES" && cached.value.value.generatedFromDataRequest && isUnknownRecord(cached.value.value.pageData) ? cached.value.value.pageData : null;
729
+ const generatedPageData = !isOnDemandRevalidate && previewData === false && cached?.isStale === false && cached?.value.value?.kind === "PAGES" && cached.value.value.generatedFromDataRequest && isUnknownRecord(cached.value.value.pageData) ? cached.value.value.pageData : null;
683
730
  if (!generatedPageData && await loadAppInitialProps()) return;
684
731
  const result = generatedPageData ? null : await pageModule.getStaticProps(context);
685
732
  if (generatedPageData) {
@@ -713,8 +760,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
713
760
  return;
714
761
  }
715
762
  if (result && "props" in result) isSerializableProps(patternToNextFormat(route.pattern), "getStaticProps", pageProps);
716
- if (typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
717
- else if (cached?.value.value?.kind === "PAGES" && cached.value.value.generatedFromDataRequest) isrRevalidateSeconds = cached.value.cacheControl?.revalidate ?? 31536e3;
763
+ if (previewData === false && typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
764
+ else if (previewData === false && cached?.value.value?.kind === "PAGES" && cached.value.value.generatedFromDataRequest) isrRevalidateSeconds = cached.value.cacheControl?.revalidate ?? 31536e3;
718
765
  }
719
766
  if (typeof pageModule.getServerSideProps !== "function" && typeof pageModule.getStaticProps !== "function" && hasAppGetInitialProps) {
720
767
  if (await loadAppInitialProps()) return;
@@ -743,7 +790,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
743
790
  }
744
791
  }
745
792
  if (isDataReq) {
746
- if (shouldPersistFallbackData) {
793
+ if (shouldPersistFallbackData && staticPropsPreviewData === false) {
747
794
  const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
748
795
  const revalidateSeconds = isrRevalidateSeconds ?? 31536e3;
749
796
  await isrSet(cacheKey, {
@@ -786,6 +833,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
786
833
  if (typeof dynamicShim.flushPreloads === "function") await dynamicShim.flushPreloads();
787
834
  const nonceAttr = createNonceAttribute(scriptNonce);
788
835
  let fontHeadHTML = "";
836
+ const assetHeadHTML = await collectDevInitialStylesheetHeadHTML(runner, [AppComponent ? findFileWithExts(pagesDir, "_app", matcher) : null, route.filePath], nonceAttr);
789
837
  const allFontStyles = [];
790
838
  const allFontPreloads = [];
791
839
  try {
@@ -847,6 +895,9 @@ const rawPageProps = props.pageProps;
847
895
  const pageProps = rawPageProps && typeof rawPageProps === "object" ? rawPageProps : {};
848
896
  window.__VINEXT_PAGE_LOADERS__ = { [nextData.page]: () => import("${pageModuleSource}") };
849
897
  window.__VINEXT_APP_LOADER__ = ${appModuleSource ? `() => import("${appModuleSource}")` : "undefined"};
898
+ // reactStrictMode flag — read by wrapWithRouterContext so the <React.StrictMode>
899
+ // wrap is applied on initial hydration and every navigation (matches Next.js).
900
+ window.__VINEXT_REACT_STRICT_MODE__ = ${JSON.stringify(reactStrictMode === true)};
850
901
 
851
902
  async function hydrate() {
852
903
  let hydrateRootOptions;
@@ -923,6 +974,7 @@ hydrate();
923
974
  url,
924
975
  server,
925
976
  fontHeadHTML,
977
+ assetHeadHTML,
926
978
  scripts: allScripts,
927
979
  DocumentComponent,
928
980
  statusCode,
@@ -969,7 +1021,7 @@ hydrate();
969
1021
  pageProps
970
1022
  }) : createElement(pageModule.default, pageProps);
971
1023
  if (wrapWithRouterContext) isrElement = wrapWithRouterContext(isrElement);
972
- const isrHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${await renderIsrPassToStringAsync(withScriptNonce(isrElement, scriptNonce))}</div>${allScripts}</body></html>`;
1024
+ const isrHtml = `<!DOCTYPE html><html><head>${assetHeadHTML}</head><body><div id="__next">${await renderIsrPassToStringAsync(withScriptNonce(isrElement, scriptNonce))}</div>${allScripts}</body></html>`;
973
1025
  const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
974
1026
  await isrSet(cacheKey, buildPagesCacheValue(isrHtml, pageProps), isrRevalidateSeconds);
975
1027
  setRevalidateDuration(cacheKey, isrRevalidateSeconds);
@@ -1008,14 +1060,15 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1008
1060
  const matcher = fileMatcher ?? createValidFileMatcher();
1009
1061
  const candidates = statusCode === 404 ? ["404", "_error"] : statusCode === 500 ? ["500", "_error"] : ["_error"];
1010
1062
  for (const candidate of candidates) try {
1011
- const candidatePath = path.join(pagesDir, candidate);
1012
- if (!findFileWithExtensions(candidatePath, matcher)) continue;
1013
- const ErrorComponent = (await importModule(runner, candidatePath)).default;
1063
+ const errorAssetPath = findFileWithExts(pagesDir, candidate, matcher);
1064
+ if (!errorAssetPath) continue;
1065
+ const ErrorComponent = (await importModule(runner, errorAssetPath)).default;
1014
1066
  if (!ErrorComponent) continue;
1015
1067
  let AppComponent = null;
1016
1068
  const appPathErr = path.join(pagesDir, "_app");
1069
+ const appAssetPath = findFileWithExts(pagesDir, "_app", matcher);
1017
1070
  if (findFileWithExtensions(appPathErr, matcher)) try {
1018
- AppComponent = (await importModule(runner, appPathErr)).default ?? null;
1071
+ AppComponent = (await importModule(runner, appAssetPath ?? appPathErr)).default ?? null;
1019
1072
  } catch {}
1020
1073
  const createElement = React.createElement;
1021
1074
  const initialErrorProps = await loadPagesGetInitialProps(ErrorComponent, {
@@ -1051,12 +1104,16 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1051
1104
  const element = createErrorElement(AppComponent, ErrorComponent);
1052
1105
  const headShim = await importModule(runner, "next/head");
1053
1106
  if (typeof headShim.resetSSRHead === "function") headShim.resetSSRHead();
1107
+ const responseHeaders = typeof res.getHeaders === "function" ? res.getHeaders() : void 0;
1108
+ const nonceAttr = createNonceAttribute(getScriptNonceFromNodeHeaderSources(req.headers, responseHeaders));
1109
+ const assetHeadHTML = await collectDevInitialStylesheetHeadHTML(runner, [appAssetPath, errorAssetPath], nonceAttr);
1054
1110
  if (DocumentComponent) {
1055
1111
  const errorPathname = candidate === "_error" ? "/_error" : `/${candidate}`;
1056
1112
  await streamPageToResponse(res, element, {
1057
1113
  url,
1058
1114
  server,
1059
1115
  fontHeadHTML: "",
1116
+ assetHeadHTML,
1060
1117
  scripts: "",
1061
1118
  DocumentComponent,
1062
1119
  statusCode,
@@ -1084,13 +1141,14 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1084
1141
  <head>
1085
1142
  <meta charset="utf-8" />
1086
1143
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1144
+ ${assetHeadHTML}
1087
1145
  </head>
1088
1146
  <body>
1089
1147
  <div id="__next">${await renderToStringAsync(element)}</div>
1090
1148
  </body>
1091
1149
  </html>`;
1092
1150
  const transformedHtml = await server.transformIndexHtml(url, html);
1093
- res.writeHead(statusCode, { "Content-Type": "text/html" });
1151
+ res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
1094
1152
  res.end(transformedHtml);
1095
1153
  }
1096
1154
  return;
@@ -1,4 +1,4 @@
1
- import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
1
+ import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
2
2
 
3
3
  //#region src/server/headers.d.ts
4
4
  /**
@@ -33,6 +33,10 @@ declare const VINEXT_PARAMS_HEADER = "X-Vinext-Params";
33
33
  declare const VINEXT_MOUNTED_SLOTS_HEADER = "X-Vinext-Mounted-Slots";
34
34
  /** Per-page dynamic stale time in seconds for App Router RSC responses. */
35
35
  declare const VINEXT_DYNAMIC_STALE_TIME_HEADER = "X-Vinext-Dynamic-Stale-Time";
36
+ /** URL-encoded rendered path and search after middleware/config rewrites. */
37
+ declare const VINEXT_RENDERED_PATH_AND_SEARCH_HEADER = "X-Vinext-Rendered-Path-And-Search";
38
+ /** Prerender-only JSON side channel carrying request cacheLife metadata. */
39
+ declare const VINEXT_PRERENDER_CACHE_LIFE_HEADER = "x-vinext-prerender-cache-life";
36
40
  /** Route interception context for parallel/intercepting routes. */
37
41
  declare const VINEXT_INTERCEPTION_CONTEXT_HEADER = "X-Vinext-Interception-Context";
38
42
  /** RSC render mode (e.g. "navigation", "prefetch"). */
@@ -101,4 +105,4 @@ declare const INTERNAL_HEADERS: string[];
101
105
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
102
106
  declare const VINEXT_INTERNAL_HEADERS: string[];
103
107
  //#endregion
104
- 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_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_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_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
108
+ 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_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_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_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -1,4 +1,4 @@
1
- import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_SET_COOKIE_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
1
+ import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
2
2
  //#region src/server/headers.ts
3
3
  /**
4
4
  * Internal HTTP header name constants used throughout vinext.
@@ -32,6 +32,10 @@ const VINEXT_PARAMS_HEADER = "X-Vinext-Params";
32
32
  const VINEXT_MOUNTED_SLOTS_HEADER = "X-Vinext-Mounted-Slots";
33
33
  /** Per-page dynamic stale time in seconds for App Router RSC responses. */
34
34
  const VINEXT_DYNAMIC_STALE_TIME_HEADER = "X-Vinext-Dynamic-Stale-Time";
35
+ /** URL-encoded rendered path and search after middleware/config rewrites. */
36
+ const VINEXT_RENDERED_PATH_AND_SEARCH_HEADER = "X-Vinext-Rendered-Path-And-Search";
37
+ /** Prerender-only JSON side channel carrying request cacheLife metadata. */
38
+ const VINEXT_PRERENDER_CACHE_LIFE_HEADER = "x-vinext-prerender-cache-life";
35
39
  /** Route interception context for parallel/intercepting routes. */
36
40
  const VINEXT_INTERCEPTION_CONTEXT_HEADER = "X-Vinext-Interception-Context";
37
41
  /** RSC render mode (e.g. "navigation", "prefetch"). */
@@ -82,8 +86,6 @@ const MIDDLEWARE_NEXT_HEADER = "x-middleware-next";
82
86
  const MIDDLEWARE_REWRITE_HEADER = "x-middleware-rewrite";
83
87
  /** Redirect URL set by middleware. */
84
88
  const MIDDLEWARE_REDIRECT_HEADER = "x-middleware-redirect";
85
- /** Skip-middleware signal. */
86
- const MIDDLEWARE_SKIP_HEADER = "x-middleware-skip";
87
89
  const NEXT_ROUTER_STATE_TREE_HEADER = "Next-Router-State-Tree";
88
90
  const NEXT_ROUTER_PREFETCH_HEADER = "Next-Router-Prefetch";
89
91
  const NEXT_ROUTER_SEGMENT_PREFETCH_HEADER = "Next-Router-Segment-Prefetch";
@@ -120,6 +122,10 @@ const INTERNAL_HEADERS = [
120
122
  ACTION_FORWARDED_HEADER
121
123
  ];
122
124
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
123
- const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
125
+ const VINEXT_INTERNAL_HEADERS = [
126
+ VINEXT_PRERENDER_ROUTE_PARAMS_HEADER,
127
+ VINEXT_PRERENDER_SPECULATIVE_HEADER,
128
+ VINEXT_PRERENDER_CACHE_LIFE_HEADER
129
+ ];
124
130
  //#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_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_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_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
131
+ 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_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_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_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -26,7 +26,18 @@ declare const IMAGE_OPTIMIZATION_PATH = "/_next/image";
26
26
  * for any newly generated URLs.
27
27
  */
28
28
  declare const VINEXT_IMAGE_OPTIMIZATION_PATH = "/_vinext/image";
29
- /** Returns true when `pathname` is either supported image optimization endpoint. */
29
+ /**
30
+ * Returns true when `pathname` is either supported image optimization
31
+ * endpoint.
32
+ *
33
+ * A single trailing slash is accepted (`/_next/image/`): with
34
+ * `trailingSlash: true`, Next.js 308-redirects `/_next/image?url=...` to
35
+ * `/_next/image/?url=...` and then serves the slashed form — its route
36
+ * matching strips a trailing slash before matching internal paths (see
37
+ * getItem in packages/next/src/server/lib/router-utils/filesystem.ts).
38
+ * Rejecting the slashed form 404'd every dev-mode next/image request under
39
+ * `trailingSlash: true`.
40
+ */
30
41
  declare function isImageOptimizationPath(pathname: string): boolean;
31
42
  /**
32
43
  * Image security configuration from next.config.js `images` section.