vinext 0.0.28 → 0.0.30

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 (173) hide show
  1. package/dist/build/report.d.ts +117 -0
  2. package/dist/build/report.d.ts.map +1 -0
  3. package/dist/build/report.js +303 -0
  4. package/dist/build/report.js.map +1 -0
  5. package/dist/check.d.ts.map +1 -1
  6. package/dist/check.js +11 -7
  7. package/dist/check.js.map +1 -1
  8. package/dist/cli.js +106 -9
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  11. package/dist/cloudflare/kv-cache-handler.js +58 -42
  12. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  13. package/dist/cloudflare/tpr.d.ts +10 -0
  14. package/dist/cloudflare/tpr.d.ts.map +1 -1
  15. package/dist/cloudflare/tpr.js +36 -41
  16. package/dist/cloudflare/tpr.js.map +1 -1
  17. package/dist/config/next-config.d.ts.map +1 -1
  18. package/dist/config/next-config.js +16 -0
  19. package/dist/config/next-config.js.map +1 -1
  20. package/dist/entries/app-rsc-entry.d.ts +1 -1
  21. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  22. package/dist/entries/app-rsc-entry.js +225 -186
  23. package/dist/entries/app-rsc-entry.js.map +1 -1
  24. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  25. package/dist/entries/pages-server-entry.js +192 -91
  26. package/dist/entries/pages-server-entry.js.map +1 -1
  27. package/dist/index.d.ts +17 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +121 -40
  30. package/dist/index.js.map +1 -1
  31. package/dist/routing/app-router.d.ts +2 -0
  32. package/dist/routing/app-router.d.ts.map +1 -1
  33. package/dist/routing/app-router.js +131 -104
  34. package/dist/routing/app-router.js.map +1 -1
  35. package/dist/routing/pages-router.d.ts.map +1 -1
  36. package/dist/routing/pages-router.js +17 -57
  37. package/dist/routing/pages-router.js.map +1 -1
  38. package/dist/routing/route-trie.d.ts +57 -0
  39. package/dist/routing/route-trie.d.ts.map +1 -0
  40. package/dist/routing/route-trie.js +160 -0
  41. package/dist/routing/route-trie.js.map +1 -0
  42. package/dist/routing/route-validation.d.ts.map +1 -1
  43. package/dist/routing/route-validation.js +13 -1
  44. package/dist/routing/route-validation.js.map +1 -1
  45. package/dist/routing/utils.d.ts +19 -0
  46. package/dist/routing/utils.d.ts.map +1 -1
  47. package/dist/routing/utils.js +47 -0
  48. package/dist/routing/utils.js.map +1 -1
  49. package/dist/server/api-handler.d.ts.map +1 -1
  50. package/dist/server/api-handler.js +28 -13
  51. package/dist/server/api-handler.js.map +1 -1
  52. package/dist/server/app-router-entry.js +1 -1
  53. package/dist/server/app-router-entry.js.map +1 -1
  54. package/dist/server/dev-server.d.ts +2 -1
  55. package/dist/server/dev-server.d.ts.map +1 -1
  56. package/dist/server/dev-server.js +167 -115
  57. package/dist/server/dev-server.js.map +1 -1
  58. package/dist/server/image-optimization.d.ts.map +1 -1
  59. package/dist/server/image-optimization.js +24 -12
  60. package/dist/server/image-optimization.js.map +1 -1
  61. package/dist/server/instrumentation.d.ts.map +1 -1
  62. package/dist/server/instrumentation.js +17 -8
  63. package/dist/server/instrumentation.js.map +1 -1
  64. package/dist/server/isr-cache.d.ts.map +1 -1
  65. package/dist/server/isr-cache.js +8 -3
  66. package/dist/server/isr-cache.js.map +1 -1
  67. package/dist/server/metadata-routes.d.ts.map +1 -1
  68. package/dist/server/metadata-routes.js +56 -18
  69. package/dist/server/metadata-routes.js.map +1 -1
  70. package/dist/server/middleware-codegen.d.ts +10 -0
  71. package/dist/server/middleware-codegen.d.ts.map +1 -1
  72. package/dist/server/middleware-codegen.js +76 -4
  73. package/dist/server/middleware-codegen.js.map +1 -1
  74. package/dist/server/middleware.d.ts.map +1 -1
  75. package/dist/server/middleware.js +52 -7
  76. package/dist/server/middleware.js.map +1 -1
  77. package/dist/server/pages-i18n.d.ts +50 -0
  78. package/dist/server/pages-i18n.d.ts.map +1 -0
  79. package/dist/server/pages-i18n.js +152 -0
  80. package/dist/server/pages-i18n.js.map +1 -0
  81. package/dist/server/prod-server.d.ts +8 -2
  82. package/dist/server/prod-server.d.ts.map +1 -1
  83. package/dist/server/prod-server.js +60 -20
  84. package/dist/server/prod-server.js.map +1 -1
  85. package/dist/shims/cache-runtime.d.ts +3 -0
  86. package/dist/shims/cache-runtime.d.ts.map +1 -1
  87. package/dist/shims/cache-runtime.js +22 -5
  88. package/dist/shims/cache-runtime.js.map +1 -1
  89. package/dist/shims/cache.d.ts +3 -0
  90. package/dist/shims/cache.d.ts.map +1 -1
  91. package/dist/shims/cache.js +21 -12
  92. package/dist/shims/cache.js.map +1 -1
  93. package/dist/shims/constants.d.ts.map +1 -1
  94. package/dist/shims/constants.js +0 -1
  95. package/dist/shims/constants.js.map +1 -1
  96. package/dist/shims/fetch-cache.d.ts +14 -0
  97. package/dist/shims/fetch-cache.d.ts.map +1 -1
  98. package/dist/shims/fetch-cache.js +102 -37
  99. package/dist/shims/fetch-cache.js.map +1 -1
  100. package/dist/shims/head-state.d.ts +4 -0
  101. package/dist/shims/head-state.d.ts.map +1 -1
  102. package/dist/shims/head-state.js +14 -11
  103. package/dist/shims/head-state.js.map +1 -1
  104. package/dist/shims/head.d.ts +4 -2
  105. package/dist/shims/head.d.ts.map +1 -1
  106. package/dist/shims/head.js +162 -52
  107. package/dist/shims/head.js.map +1 -1
  108. package/dist/shims/headers.d.ts +8 -1
  109. package/dist/shims/headers.d.ts.map +1 -1
  110. package/dist/shims/headers.js +23 -34
  111. package/dist/shims/headers.js.map +1 -1
  112. package/dist/shims/i18n-context.d.ts +27 -0
  113. package/dist/shims/i18n-context.d.ts.map +1 -0
  114. package/dist/shims/i18n-context.js +57 -0
  115. package/dist/shims/i18n-context.js.map +1 -0
  116. package/dist/shims/i18n-state.d.ts +20 -0
  117. package/dist/shims/i18n-state.d.ts.map +1 -0
  118. package/dist/shims/i18n-state.js +53 -0
  119. package/dist/shims/i18n-state.js.map +1 -0
  120. package/dist/shims/image.d.ts +2 -0
  121. package/dist/shims/image.d.ts.map +1 -1
  122. package/dist/shims/image.js +14 -6
  123. package/dist/shims/image.js.map +1 -1
  124. package/dist/shims/internal/utils.d.ts +1 -0
  125. package/dist/shims/internal/utils.d.ts.map +1 -1
  126. package/dist/shims/internal/utils.js.map +1 -1
  127. package/dist/shims/link.d.ts.map +1 -1
  128. package/dist/shims/link.js +38 -54
  129. package/dist/shims/link.js.map +1 -1
  130. package/dist/shims/metadata.d.ts +78 -22
  131. package/dist/shims/metadata.d.ts.map +1 -1
  132. package/dist/shims/metadata.js +96 -28
  133. package/dist/shims/metadata.js.map +1 -1
  134. package/dist/shims/navigation-state.d.ts +14 -0
  135. package/dist/shims/navigation-state.d.ts.map +1 -1
  136. package/dist/shims/navigation-state.js +33 -15
  137. package/dist/shims/navigation-state.js.map +1 -1
  138. package/dist/shims/navigation.d.ts +2 -0
  139. package/dist/shims/navigation.d.ts.map +1 -1
  140. package/dist/shims/navigation.js +80 -51
  141. package/dist/shims/navigation.js.map +1 -1
  142. package/dist/shims/request-context.d.ts.map +1 -1
  143. package/dist/shims/request-context.js +9 -0
  144. package/dist/shims/request-context.js.map +1 -1
  145. package/dist/shims/request-state-types.d.ts +11 -0
  146. package/dist/shims/request-state-types.d.ts.map +1 -0
  147. package/dist/shims/request-state-types.js +2 -0
  148. package/dist/shims/request-state-types.js.map +1 -0
  149. package/dist/shims/router-state.d.ts +11 -0
  150. package/dist/shims/router-state.d.ts.map +1 -1
  151. package/dist/shims/router-state.js +10 -8
  152. package/dist/shims/router-state.js.map +1 -1
  153. package/dist/shims/router.d.ts +4 -0
  154. package/dist/shims/router.d.ts.map +1 -1
  155. package/dist/shims/router.js +130 -40
  156. package/dist/shims/router.js.map +1 -1
  157. package/dist/shims/server.d.ts +8 -1
  158. package/dist/shims/server.d.ts.map +1 -1
  159. package/dist/shims/server.js +52 -6
  160. package/dist/shims/server.js.map +1 -1
  161. package/dist/shims/unified-request-context.d.ts +66 -0
  162. package/dist/shims/unified-request-context.d.ts.map +1 -0
  163. package/dist/shims/unified-request-context.js +116 -0
  164. package/dist/shims/unified-request-context.js.map +1 -0
  165. package/dist/shims/url-utils.d.ts +20 -6
  166. package/dist/shims/url-utils.d.ts.map +1 -1
  167. package/dist/shims/url-utils.js +79 -0
  168. package/dist/shims/url-utils.js.map +1 -1
  169. package/dist/utils/domain-locale.d.ts +18 -0
  170. package/dist/utils/domain-locale.d.ts.map +1 -0
  171. package/dist/utils/domain-locale.js +64 -0
  172. package/dist/utils/domain-locale.js.map +1 -0
  173. package/package.json +2 -2
@@ -1,12 +1,14 @@
1
1
  import { matchRoute, patternToNextFormat } from "../routing/pages-router.js";
2
2
  import { isrGet, isrSet, isrCacheKey, buildPagesCacheValue, triggerBackgroundRegeneration, setRevalidateDuration, getRevalidateDuration, } from "./isr-cache.js";
3
- import { runWithFetchCache } from "../shims/fetch-cache.js";
4
3
  import { _runWithCacheState } from "../shims/cache.js";
5
4
  import { runWithPrivateCache } from "../shims/cache-runtime.js";
5
+ import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
6
+ import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
6
7
  // Import server-only state modules to register ALS-backed accessors.
7
8
  // These modules must be imported before any rendering occurs.
8
- import { runWithRouterState } from "../shims/router-state.js";
9
+ import "../shims/router-state.js";
9
10
  import { runWithHeadState } from "../shims/head-state.js";
11
+ import { runWithServerInsertedHTMLState } from "../shims/navigation-state.js";
10
12
  import { reportRequestError } from "./instrumentation.js";
11
13
  import { safeJsonStringify } from "./html.js";
12
14
  import { parseQueryString as parseQuery } from "../utils/query.js";
@@ -16,6 +18,7 @@ import React from "react";
16
18
  import { renderToReadableStream } from "react-dom/server.edge";
17
19
  import { logRequest, now } from "./request-log.js";
18
20
  import { createValidFileMatcher } from "../routing/file-matcher.js";
21
+ import { extractLocaleFromUrl as extractLocaleFromUrlShared, detectLocaleFromAcceptLanguage, parseCookieLocaleFromHeader, resolvePagesI18nRequest, } from "./pages-i18n.js";
19
22
  /**
20
23
  * Render a React element to a string using renderToReadableStream.
21
24
  *
@@ -28,6 +31,14 @@ async function renderToStringAsync(element) {
28
31
  await stream.allReady;
29
32
  return new Response(stream).text();
30
33
  }
34
+ async function renderIsrPassToStringAsync(element) {
35
+ // The cache-fill render is a second render pass for the same request.
36
+ // Reset render-scoped state so it cannot leak from the streamed response
37
+ // render or affect async work that is still draining from that stream.
38
+ // Keep request identity state (pathname/query/locale/executionContext)
39
+ // intact: this second pass still belongs to the same request.
40
+ return await runWithServerInsertedHTMLState(() => runWithHeadState(() => _runWithCacheState(() => runWithPrivateCache(() => runWithFetchCache(async () => renderToStringAsync(element))))));
41
+ }
31
42
  /** Body placeholder used to split the document shell for streaming. */
32
43
  const STREAM_BODY_MARKER = "<!--VINEXT_STREAM_BODY-->";
33
44
  /**
@@ -136,69 +147,21 @@ function findFileWithExtensions(basePath, matcher) {
136
147
  * /about -> { locale: "en", url: "/about", hadPrefix: false } (defaultLocale)
137
148
  */
138
149
  export function extractLocaleFromUrl(url, i18nConfig) {
139
- const pathname = url.split("?")[0];
140
- const parts = pathname.split("/").filter(Boolean);
141
- const query = url.includes("?") ? url.slice(url.indexOf("?")) : "";
142
- if (parts.length > 0 && i18nConfig.locales.includes(parts[0])) {
143
- const locale = parts[0];
144
- const rest = "/" + parts.slice(1).join("/");
145
- return { locale, url: (rest || "/") + query, hadPrefix: true };
146
- }
147
- return { locale: i18nConfig.defaultLocale, url, hadPrefix: false };
150
+ return extractLocaleFromUrlShared(url, i18nConfig);
148
151
  }
149
152
  /**
150
153
  * Detect the preferred locale from the Accept-Language header.
151
154
  * Returns the best matching locale or null.
152
155
  */
153
156
  export function detectLocaleFromHeaders(req, i18nConfig) {
154
- const acceptLang = req.headers["accept-language"];
155
- if (!acceptLang)
156
- return null;
157
- // Parse Accept-Language: en-US,en;q=0.9,fr;q=0.8
158
- const langs = acceptLang
159
- .split(",")
160
- .map((part) => {
161
- const [lang, qPart] = part.trim().split(";");
162
- const q = qPart ? parseFloat(qPart.replace("q=", "")) : 1;
163
- return { lang: lang.trim().toLowerCase(), q };
164
- })
165
- .sort((a, b) => b.q - a.q);
166
- for (const { lang } of langs) {
167
- // Exact match
168
- const exactMatch = i18nConfig.locales.find((l) => l.toLowerCase() === lang);
169
- if (exactMatch)
170
- return exactMatch;
171
- // Prefix match (e.g. "en-US" matches "en")
172
- const prefix = lang.split("-")[0];
173
- const prefixMatch = i18nConfig.locales.find((l) => l.toLowerCase() === prefix || l.toLowerCase().startsWith(prefix + "-"));
174
- if (prefixMatch)
175
- return prefixMatch;
176
- }
177
- return null;
157
+ return detectLocaleFromAcceptLanguage(req.headers["accept-language"], i18nConfig);
178
158
  }
179
159
  /**
180
160
  * Parse the NEXT_LOCALE cookie from a request.
181
161
  * Returns the cookie value if it matches a configured locale, otherwise null.
182
162
  */
183
163
  export function parseCookieLocale(req, i18nConfig) {
184
- const cookieHeader = req.headers.cookie;
185
- if (!cookieHeader)
186
- return null;
187
- // Simple cookie parsing — find NEXT_LOCALE=value
188
- const match = cookieHeader.match(/(?:^|;\s*)NEXT_LOCALE=([^;]*)/);
189
- if (!match)
190
- return null;
191
- let value;
192
- try {
193
- value = decodeURIComponent(match[1].trim());
194
- }
195
- catch {
196
- return null;
197
- }
198
- // Only return if it's a valid configured locale
199
- if (i18nConfig.locales.includes(value))
200
- return value;
201
- return null;
164
+ return parseCookieLocaleFromHeader(req.headers.cookie, i18nConfig);
202
165
  }
203
166
  /**
204
167
  * Create an SSR request handler for the Pages Router.
@@ -210,7 +173,7 @@ export function parseCookieLocale(req, i18nConfig) {
210
173
  * 4. Render the component to HTML
211
174
  * 5. Wrap in _document shell and send response
212
175
  */
213
- export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatcher) {
176
+ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false) {
214
177
  const matcher = fileMatcher ?? createValidFileMatcher();
215
178
  return async (req, res, url,
216
179
  /** Status code override — propagated from middleware rewrite status. */
@@ -238,30 +201,17 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
238
201
  // --- i18n: extract locale from URL prefix ---
239
202
  let locale;
240
203
  let localeStrippedUrl = url;
204
+ let currentDefaultLocale;
205
+ const domainLocales = i18nConfig?.domains;
241
206
  if (i18nConfig) {
242
- const parsed = extractLocaleFromUrl(url, i18nConfig);
243
- locale = parsed.locale;
244
- localeStrippedUrl = parsed.url;
245
- // If no locale prefix, check NEXT_LOCALE cookie first, then Accept-Language
246
- if (!parsed.hadPrefix) {
247
- const cookieLocale = parseCookieLocale(req, i18nConfig);
248
- if (cookieLocale && cookieLocale !== i18nConfig.defaultLocale) {
249
- // NEXT_LOCALE cookie overrides Accept-Language — redirect to cookie locale
250
- const redirectUrl = `/${cookieLocale}${url === "/" ? "" : url}`;
251
- res.writeHead(307, { Location: redirectUrl });
252
- res.end();
253
- return;
254
- }
255
- // If no cookie or cookie matches default, fall through to Accept-Language detection
256
- if (!cookieLocale && i18nConfig.localeDetection !== false) {
257
- const detectedLocale = detectLocaleFromHeaders(req, i18nConfig);
258
- if (detectedLocale && detectedLocale !== i18nConfig.defaultLocale) {
259
- const redirectUrl = `/${detectedLocale}${url === "/" ? "" : url}`;
260
- res.writeHead(307, { Location: redirectUrl });
261
- res.end();
262
- return;
263
- }
264
- }
207
+ const resolved = resolvePagesI18nRequest(url, i18nConfig, req.headers, req.headers.host, basePath, trailingSlash);
208
+ locale = resolved.locale;
209
+ localeStrippedUrl = resolved.url;
210
+ currentDefaultLocale = resolved.domainLocale?.defaultLocale ?? i18nConfig.defaultLocale;
211
+ if (resolved.redirectUrl) {
212
+ res.writeHead(307, { Location: resolved.redirectUrl });
213
+ res.end();
214
+ return;
265
215
  }
266
216
  }
267
217
  const match = matchRoute(localeStrippedUrl, routes);
@@ -271,9 +221,10 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
271
221
  return;
272
222
  }
273
223
  const { route, params } = match;
274
- // Wrap the entire request in nested AsyncLocalStorage.run() scopes to
275
- // ensure per-request isolation for all state modules.
276
- return runWithRouterState(() => runWithHeadState(() => _runWithCacheState(() => runWithPrivateCache(() => runWithFetchCache(async () => {
224
+ // Wrap the entire request in a single unified AsyncLocalStorage scope.
225
+ const requestContext = createRequestContext();
226
+ return runWithRequestContext(requestContext, async () => {
227
+ ensureFetchPatch();
277
228
  try {
278
229
  // Set SSR context for the router shim so useRouter() returns
279
230
  // the correct URL and params during server-side rendering.
@@ -283,16 +234,30 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
283
234
  pathname: patternToNextFormat(route.pattern),
284
235
  query: { ...params, ...parseQuery(url) },
285
236
  asPath: url,
286
- locale: locale ?? i18nConfig?.defaultLocale,
237
+ locale: locale ?? currentDefaultLocale,
287
238
  locales: i18nConfig?.locales,
288
- defaultLocale: i18nConfig?.defaultLocale,
239
+ defaultLocale: currentDefaultLocale,
240
+ domainLocales,
289
241
  });
290
242
  }
291
- // Set globalThis locale info for Link component locale prop support during SSR
243
+ // Set per-request i18n context for Link component locale
244
+ // prop support during SSR. Use ssrLoadModule to set it on
245
+ // the SSR environment's module instance (same pattern as
246
+ // setSSRContext above).
292
247
  if (i18nConfig) {
293
- globalThis.__VINEXT_LOCALE__ = locale ?? i18nConfig.defaultLocale;
294
- globalThis.__VINEXT_LOCALES__ = i18nConfig.locales;
295
- globalThis.__VINEXT_DEFAULT_LOCALE__ = i18nConfig.defaultLocale;
248
+ // Register ALS-backed i18n accessors in the SSR module graph so
249
+ // next/link and other SSR imports read from the unified store.
250
+ await server.ssrLoadModule("vinext/i18n-state");
251
+ const i18nCtx = await server.ssrLoadModule("vinext/i18n-context");
252
+ if (typeof i18nCtx.setI18nContext === "function") {
253
+ i18nCtx.setI18nContext({
254
+ locale: locale ?? currentDefaultLocale,
255
+ locales: i18nConfig.locales,
256
+ defaultLocale: currentDefaultLocale,
257
+ domainLocales,
258
+ hostname: req.headers.host?.split(":", 1)[0],
259
+ });
260
+ }
296
261
  }
297
262
  // Load the page module through Vite's SSR pipeline
298
263
  // This gives us HMR and transform support for free
@@ -315,7 +280,7 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
315
280
  if (typeof pageModule.getStaticPaths === "function" && route.isDynamic) {
316
281
  const pathsResult = await pageModule.getStaticPaths({
317
282
  locales: i18nConfig?.locales ?? [],
318
- defaultLocale: i18nConfig?.defaultLocale ?? "",
283
+ defaultLocale: currentDefaultLocale ?? "",
319
284
  });
320
285
  const fallback = pathsResult?.fallback ?? false;
321
286
  if (fallback === false) {
@@ -325,7 +290,7 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
325
290
  return Object.entries(p.params).every(([key, val]) => {
326
291
  const actual = params[key];
327
292
  if (Array.isArray(val)) {
328
- return (Array.isArray(actual) && val.join("/") === actual.join("/"));
293
+ return Array.isArray(actual) && val.join("/") === actual.join("/");
329
294
  }
330
295
  return String(val) === String(actual);
331
296
  });
@@ -355,9 +320,9 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
355
320
  res,
356
321
  query: parseQuery(url),
357
322
  resolvedUrl: localeStrippedUrl,
358
- locale: locale ?? i18nConfig?.defaultLocale,
323
+ locale: locale ?? currentDefaultLocale,
359
324
  locales: i18nConfig?.locales,
360
- defaultLocale: i18nConfig?.defaultLocale,
325
+ defaultLocale: currentDefaultLocale,
361
326
  };
362
327
  const result = await pageModule.getServerSideProps(context);
363
328
  // If gSSP called res.end() directly (short-circuit pattern),
@@ -464,18 +429,109 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
464
429
  const cachedPage = cached.value.value;
465
430
  const cachedHtml = cachedPage.html;
466
431
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
467
- // Trigger background regeneration: re-run getStaticProps and
468
- // update the cache so the next request is a HIT with fresh data.
432
+ // Trigger background regeneration: re-run getStaticProps,
433
+ // re-render the page, and cache the fresh HTML.
469
434
  triggerBackgroundRegeneration(cacheKey, async () => {
470
- const freshResult = await pageModule.getStaticProps({ params });
471
- if (freshResult && "props" in freshResult) {
472
- const revalidate = typeof freshResult.revalidate === "number"
473
- ? freshResult.revalidate
474
- : 0;
475
- if (revalidate > 0) {
476
- await isrSet(cacheKey, buildPagesCacheValue(cachedHtml, freshResult.props), revalidate);
435
+ const regenContext = createRequestContext({
436
+ // Dev never has a Workers ExecutionContext. Set it
437
+ // explicitly so background regeneration cannot inherit
438
+ // a standalone execution-context scope from the caller.
439
+ executionContext: null,
440
+ });
441
+ return runWithRequestContext(regenContext, async () => {
442
+ ensureFetchPatch();
443
+ const freshResult = await pageModule.getStaticProps({
444
+ params,
445
+ locale: locale ?? currentDefaultLocale,
446
+ locales: i18nConfig?.locales,
447
+ defaultLocale: currentDefaultLocale,
448
+ });
449
+ if (freshResult && "props" in freshResult) {
450
+ const revalidate = typeof freshResult.revalidate === "number" ? freshResult.revalidate : 0;
451
+ if (revalidate > 0) {
452
+ const freshProps = freshResult.props;
453
+ if (typeof routerShim.setSSRContext === "function") {
454
+ routerShim.setSSRContext({
455
+ pathname: patternToNextFormat(route.pattern),
456
+ query: { ...params, ...parseQuery(url) },
457
+ asPath: url,
458
+ locale: locale ?? currentDefaultLocale,
459
+ locales: i18nConfig?.locales,
460
+ defaultLocale: currentDefaultLocale,
461
+ domainLocales,
462
+ });
463
+ }
464
+ if (i18nConfig) {
465
+ await server.ssrLoadModule("vinext/i18n-state");
466
+ const i18nCtx = await server.ssrLoadModule("vinext/i18n-context");
467
+ if (typeof i18nCtx.setI18nContext === "function") {
468
+ i18nCtx.setI18nContext({
469
+ locale: locale ?? currentDefaultLocale,
470
+ locales: i18nConfig.locales,
471
+ defaultLocale: currentDefaultLocale,
472
+ domainLocales,
473
+ hostname: req.headers.host?.split(":", 1)[0],
474
+ });
475
+ }
476
+ }
477
+ // Re-render the page with fresh props inside fresh
478
+ // render sub-scopes so head/cache state cannot leak.
479
+ let RegenApp = null;
480
+ const appPath = path.join(pagesDir, "_app");
481
+ if (findFileWithExtensions(appPath, matcher)) {
482
+ try {
483
+ const appMod = await server.ssrLoadModule(appPath);
484
+ RegenApp = appMod.default ?? null;
485
+ }
486
+ catch {
487
+ // _app failed to load
488
+ }
489
+ }
490
+ let el = RegenApp
491
+ ? React.createElement(RegenApp, {
492
+ Component: pageModule.default,
493
+ pageProps: freshProps,
494
+ })
495
+ : React.createElement(pageModule.default, freshProps);
496
+ if (routerShim.wrapWithRouterContext) {
497
+ el = routerShim.wrapWithRouterContext(el);
498
+ }
499
+ const freshBody = await renderIsrPassToStringAsync(el);
500
+ // Rebuild __NEXT_DATA__ with fresh props. The hydration
501
+ // script (module URLs) is stable across regenerations —
502
+ // extract it from the cached HTML to avoid duplication.
503
+ const viteRoot = server.config?.root;
504
+ const regenPageUrl = viteRoot
505
+ ? "/" + path.relative(viteRoot, route.filePath)
506
+ : route.filePath;
507
+ const regenAppUrl = RegenApp
508
+ ? viteRoot
509
+ ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app"))
510
+ : path.join(pagesDir, "_app")
511
+ : null;
512
+ const freshNextData = `<script>window.__NEXT_DATA__ = ${safeJsonStringify({
513
+ props: { pageProps: freshProps },
514
+ page: patternToNextFormat(route.pattern),
515
+ query: params,
516
+ buildId: process.env.__VINEXT_BUILD_ID,
517
+ isFallback: false,
518
+ locale: locale ?? currentDefaultLocale,
519
+ locales: i18nConfig?.locales,
520
+ defaultLocale: currentDefaultLocale,
521
+ domainLocales,
522
+ __vinext: {
523
+ pageModuleUrl: regenPageUrl,
524
+ appModuleUrl: regenAppUrl,
525
+ },
526
+ })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}</script>`;
527
+ const hydrationMatch = cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/);
528
+ const hydrationScript = hydrationMatch?.[0] ?? "";
529
+ const freshHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${freshNextData}\n ${hydrationScript}</body></html>`;
530
+ await isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshProps), revalidate);
531
+ setRevalidateDuration(cacheKey, revalidate);
532
+ }
477
533
  }
478
- }
534
+ });
479
535
  });
480
536
  const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
481
537
  const staleHeaders = {
@@ -492,9 +548,9 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
492
548
  // Cache miss — call getStaticProps normally
493
549
  const context = {
494
550
  params,
495
- locale: locale ?? i18nConfig?.defaultLocale,
551
+ locale: locale ?? currentDefaultLocale,
496
552
  locales: i18nConfig?.locales,
497
- defaultLocale: i18nConfig?.defaultLocale,
553
+ defaultLocale: currentDefaultLocale,
498
554
  };
499
555
  const result = await pageModule.getStaticProps(context);
500
556
  if (result && "props" in result) {
@@ -579,9 +635,7 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
579
635
  if (typeof fontGoogle.getSSRFontLinks === "function") {
580
636
  const fontUrls = fontGoogle.getSSRFontLinks();
581
637
  for (const fontUrl of fontUrls) {
582
- const safeFontUrl = fontUrl
583
- .replace(/&/g, "&amp;")
584
- .replace(/"/g, "&quot;");
638
+ const safeFontUrl = fontUrl.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
585
639
  fontHeadHTML += `<link rel="stylesheet" href="${safeFontUrl}" />\n `;
586
640
  }
587
641
  }
@@ -663,15 +717,16 @@ hydrate();
663
717
  query: params,
664
718
  buildId: process.env.__VINEXT_BUILD_ID,
665
719
  isFallback: false,
666
- locale: locale ?? i18nConfig?.defaultLocale,
720
+ locale: locale ?? currentDefaultLocale,
667
721
  locales: i18nConfig?.locales,
668
- defaultLocale: i18nConfig?.defaultLocale,
722
+ defaultLocale: currentDefaultLocale,
723
+ domainLocales,
669
724
  // Include module URLs so client navigation can import pages directly
670
725
  __vinext: {
671
726
  pageModuleUrl,
672
727
  appModuleUrl,
673
728
  },
674
- })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? i18nConfig.defaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(i18nConfig.defaultLocale)}` : ""}</script>`;
729
+ })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}</script>`;
675
730
  // Try to load custom _document.tsx
676
731
  const docPath = path.join(pagesDir, "_document");
677
732
  let DocumentComponent = null;
@@ -716,9 +771,7 @@ hydrate();
716
771
  // Collect head HTML AFTER the shell renders (inside streamPageToResponse,
717
772
  // after renderToReadableStream resolves). Head tags from Suspense
718
773
  // children arrive late — this matches Next.js behavior.
719
- getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function"
720
- ? headShim.getSSRHeadHTML()
721
- : "",
774
+ getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function" ? headShim.getSSRHeadHTML() : "",
722
775
  });
723
776
  _renderEnd = now();
724
777
  // Clear SSR context after rendering
@@ -738,7 +791,7 @@ hydrate();
738
791
  if (wrapWithRouterContext) {
739
792
  isrElement = wrapWithRouterContext(isrElement);
740
793
  }
741
- const isrBodyHtml = await renderToStringAsync(isrElement);
794
+ const isrBodyHtml = await renderIsrPassToStringAsync(isrElement);
742
795
  const isrHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${isrBodyHtml}</div>${allScripts}</body></html>`;
743
796
  const cacheKey = isrCacheKey("pages", url.split("?")[0],
744
797
  // __VINEXT_BUILD_ID is a compile-time define — undefined in dev,
@@ -780,10 +833,9 @@ hydrate();
780
833
  }
781
834
  }
782
835
  finally {
783
- // Cleanup is handled by ALS scope unwinding
784
- // each runWith*() scope is automatically cleaned up when it exits.
836
+ // Cleanup is handled by unified ALS scope unwinding.
785
837
  }
786
- }))))); // end runWithRouterState
838
+ });
787
839
  };
788
840
  }
789
841
  /**