vinext 0.0.29 → 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 (128) hide show
  1. package/dist/check.d.ts.map +1 -1
  2. package/dist/check.js +11 -7
  3. package/dist/check.js.map +1 -1
  4. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  5. package/dist/cloudflare/kv-cache-handler.js +44 -30
  6. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  7. package/dist/entries/app-rsc-entry.d.ts +1 -1
  8. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  9. package/dist/entries/app-rsc-entry.js +208 -164
  10. package/dist/entries/app-rsc-entry.js.map +1 -1
  11. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  12. package/dist/entries/pages-server-entry.js +151 -100
  13. package/dist/entries/pages-server-entry.js.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +10 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/routing/app-router.d.ts.map +1 -1
  18. package/dist/routing/app-router.js +43 -29
  19. package/dist/routing/app-router.js.map +1 -1
  20. package/dist/server/app-router-entry.js +1 -1
  21. package/dist/server/app-router-entry.js.map +1 -1
  22. package/dist/server/dev-server.d.ts +2 -1
  23. package/dist/server/dev-server.d.ts.map +1 -1
  24. package/dist/server/dev-server.js +163 -163
  25. package/dist/server/dev-server.js.map +1 -1
  26. package/dist/server/image-optimization.d.ts.map +1 -1
  27. package/dist/server/image-optimization.js +23 -11
  28. package/dist/server/image-optimization.js.map +1 -1
  29. package/dist/server/isr-cache.d.ts.map +1 -1
  30. package/dist/server/isr-cache.js +8 -3
  31. package/dist/server/isr-cache.js.map +1 -1
  32. package/dist/server/metadata-routes.d.ts.map +1 -1
  33. package/dist/server/metadata-routes.js +56 -18
  34. package/dist/server/metadata-routes.js.map +1 -1
  35. package/dist/server/middleware-codegen.d.ts.map +1 -1
  36. package/dist/server/middleware-codegen.js +37 -4
  37. package/dist/server/middleware-codegen.js.map +1 -1
  38. package/dist/server/middleware.d.ts.map +1 -1
  39. package/dist/server/middleware.js +50 -6
  40. package/dist/server/middleware.js.map +1 -1
  41. package/dist/server/pages-i18n.d.ts +50 -0
  42. package/dist/server/pages-i18n.d.ts.map +1 -0
  43. package/dist/server/pages-i18n.js +152 -0
  44. package/dist/server/pages-i18n.js.map +1 -0
  45. package/dist/shims/cache-runtime.d.ts +3 -0
  46. package/dist/shims/cache-runtime.d.ts.map +1 -1
  47. package/dist/shims/cache-runtime.js +22 -5
  48. package/dist/shims/cache-runtime.js.map +1 -1
  49. package/dist/shims/cache.d.ts +3 -0
  50. package/dist/shims/cache.d.ts.map +1 -1
  51. package/dist/shims/cache.js +21 -12
  52. package/dist/shims/cache.js.map +1 -1
  53. package/dist/shims/constants.d.ts.map +1 -1
  54. package/dist/shims/constants.js +0 -1
  55. package/dist/shims/constants.js.map +1 -1
  56. package/dist/shims/fetch-cache.d.ts +14 -0
  57. package/dist/shims/fetch-cache.d.ts.map +1 -1
  58. package/dist/shims/fetch-cache.js +102 -37
  59. package/dist/shims/fetch-cache.js.map +1 -1
  60. package/dist/shims/head-state.d.ts +4 -0
  61. package/dist/shims/head-state.d.ts.map +1 -1
  62. package/dist/shims/head-state.js +14 -11
  63. package/dist/shims/head-state.js.map +1 -1
  64. package/dist/shims/head.d.ts +4 -2
  65. package/dist/shims/head.d.ts.map +1 -1
  66. package/dist/shims/head.js +162 -52
  67. package/dist/shims/head.js.map +1 -1
  68. package/dist/shims/headers.d.ts +8 -1
  69. package/dist/shims/headers.d.ts.map +1 -1
  70. package/dist/shims/headers.js +21 -29
  71. package/dist/shims/headers.js.map +1 -1
  72. package/dist/shims/i18n-context.d.ts +27 -0
  73. package/dist/shims/i18n-context.d.ts.map +1 -0
  74. package/dist/shims/i18n-context.js +57 -0
  75. package/dist/shims/i18n-context.js.map +1 -0
  76. package/dist/shims/i18n-state.d.ts +20 -0
  77. package/dist/shims/i18n-state.d.ts.map +1 -0
  78. package/dist/shims/i18n-state.js +53 -0
  79. package/dist/shims/i18n-state.js.map +1 -0
  80. package/dist/shims/image.d.ts +2 -0
  81. package/dist/shims/image.d.ts.map +1 -1
  82. package/dist/shims/image.js +14 -6
  83. package/dist/shims/image.js.map +1 -1
  84. package/dist/shims/internal/utils.d.ts +1 -0
  85. package/dist/shims/internal/utils.d.ts.map +1 -1
  86. package/dist/shims/internal/utils.js.map +1 -1
  87. package/dist/shims/link.d.ts.map +1 -1
  88. package/dist/shims/link.js +27 -11
  89. package/dist/shims/link.js.map +1 -1
  90. package/dist/shims/metadata.d.ts +22 -22
  91. package/dist/shims/metadata.d.ts.map +1 -1
  92. package/dist/shims/metadata.js +34 -32
  93. package/dist/shims/metadata.js.map +1 -1
  94. package/dist/shims/navigation-state.d.ts +14 -0
  95. package/dist/shims/navigation-state.d.ts.map +1 -1
  96. package/dist/shims/navigation-state.js +33 -15
  97. package/dist/shims/navigation-state.js.map +1 -1
  98. package/dist/shims/navigation.d.ts.map +1 -1
  99. package/dist/shims/navigation.js +39 -22
  100. package/dist/shims/navigation.js.map +1 -1
  101. package/dist/shims/request-context.d.ts.map +1 -1
  102. package/dist/shims/request-context.js +9 -0
  103. package/dist/shims/request-context.js.map +1 -1
  104. package/dist/shims/request-state-types.d.ts +11 -0
  105. package/dist/shims/request-state-types.d.ts.map +1 -0
  106. package/dist/shims/request-state-types.js +2 -0
  107. package/dist/shims/request-state-types.js.map +1 -0
  108. package/dist/shims/router-state.d.ts +11 -0
  109. package/dist/shims/router-state.d.ts.map +1 -1
  110. package/dist/shims/router-state.js +10 -8
  111. package/dist/shims/router-state.js.map +1 -1
  112. package/dist/shims/router.d.ts +4 -0
  113. package/dist/shims/router.d.ts.map +1 -1
  114. package/dist/shims/router.js +117 -21
  115. package/dist/shims/router.js.map +1 -1
  116. package/dist/shims/server.d.ts +8 -1
  117. package/dist/shims/server.d.ts.map +1 -1
  118. package/dist/shims/server.js +52 -6
  119. package/dist/shims/server.js.map +1 -1
  120. package/dist/shims/unified-request-context.d.ts +66 -0
  121. package/dist/shims/unified-request-context.d.ts.map +1 -0
  122. package/dist/shims/unified-request-context.js +116 -0
  123. package/dist/shims/unified-request-context.js.map +1 -0
  124. package/dist/utils/domain-locale.d.ts +18 -0
  125. package/dist/utils/domain-locale.d.ts.map +1 -0
  126. package/dist/utils/domain-locale.js +64 -0
  127. package/dist/utils/domain-locale.js.map +1 -0
  128. 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),
@@ -467,69 +432,106 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
467
432
  // Trigger background regeneration: re-run getStaticProps,
468
433
  // re-render the page, and cache the fresh HTML.
469
434
  triggerBackgroundRegeneration(cacheKey, async () => {
470
- const freshResult = await pageModule.getStaticProps({
471
- params,
472
- locale: locale ?? i18nConfig?.defaultLocale,
473
- locales: i18nConfig?.locales,
474
- defaultLocale: i18nConfig?.defaultLocale,
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,
475
440
  });
476
- if (freshResult && "props" in freshResult) {
477
- const revalidate = typeof freshResult.revalidate === "number"
478
- ? freshResult.revalidate
479
- : 0;
480
- if (revalidate > 0) {
481
- const freshProps = freshResult.props;
482
- // Re-render the page with fresh props
483
- let RegenApp = null;
484
- const appPath = path.join(pagesDir, "_app");
485
- if (findFileWithExtensions(appPath, matcher)) {
486
- try {
487
- const appMod = await server.ssrLoadModule(appPath);
488
- RegenApp = appMod.default ?? null;
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
+ });
489
463
  }
490
- catch {
491
- // _app failed to load
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
+ }
492
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);
493
532
  }
494
- let el = RegenApp
495
- ? React.createElement(RegenApp, {
496
- Component: pageModule.default,
497
- pageProps: freshProps,
498
- })
499
- : React.createElement(pageModule.default, freshProps);
500
- if (routerShim.wrapWithRouterContext) {
501
- el = routerShim.wrapWithRouterContext(el);
502
- }
503
- const freshBody = await renderToStringAsync(el);
504
- // Rebuild __NEXT_DATA__ with fresh props. The hydration
505
- // script (module URLs) is stable across regenerations —
506
- // extract it from the cached HTML to avoid duplication.
507
- const viteRoot = server.config.root;
508
- const regenPageUrl = "/" + path.relative(viteRoot, route.filePath);
509
- const regenAppUrl = RegenApp
510
- ? "/" + path.relative(viteRoot, 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 ?? i18nConfig?.defaultLocale,
519
- locales: i18nConfig?.locales,
520
- defaultLocale: i18nConfig?.defaultLocale,
521
- __vinext: {
522
- pageModuleUrl: regenPageUrl,
523
- appModuleUrl: regenAppUrl,
524
- },
525
- })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? i18nConfig.defaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(i18nConfig.defaultLocale)}` : ""}</script>`;
526
- const hydrationMatch = cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/);
527
- const hydrationScript = hydrationMatch?.[0] ?? "";
528
- const freshHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${freshNextData}\n ${hydrationScript}</body></html>`;
529
- await isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshProps), revalidate);
530
- setRevalidateDuration(cacheKey, revalidate);
531
533
  }
532
- }
534
+ });
533
535
  });
534
536
  const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
535
537
  const staleHeaders = {
@@ -546,9 +548,9 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
546
548
  // Cache miss — call getStaticProps normally
547
549
  const context = {
548
550
  params,
549
- locale: locale ?? i18nConfig?.defaultLocale,
551
+ locale: locale ?? currentDefaultLocale,
550
552
  locales: i18nConfig?.locales,
551
- defaultLocale: i18nConfig?.defaultLocale,
553
+ defaultLocale: currentDefaultLocale,
552
554
  };
553
555
  const result = await pageModule.getStaticProps(context);
554
556
  if (result && "props" in result) {
@@ -633,9 +635,7 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig, fileMatch
633
635
  if (typeof fontGoogle.getSSRFontLinks === "function") {
634
636
  const fontUrls = fontGoogle.getSSRFontLinks();
635
637
  for (const fontUrl of fontUrls) {
636
- const safeFontUrl = fontUrl
637
- .replace(/&/g, "&amp;")
638
- .replace(/"/g, "&quot;");
638
+ const safeFontUrl = fontUrl.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
639
639
  fontHeadHTML += `<link rel="stylesheet" href="${safeFontUrl}" />\n `;
640
640
  }
641
641
  }
@@ -717,15 +717,16 @@ hydrate();
717
717
  query: params,
718
718
  buildId: process.env.__VINEXT_BUILD_ID,
719
719
  isFallback: false,
720
- locale: locale ?? i18nConfig?.defaultLocale,
720
+ locale: locale ?? currentDefaultLocale,
721
721
  locales: i18nConfig?.locales,
722
- defaultLocale: i18nConfig?.defaultLocale,
722
+ defaultLocale: currentDefaultLocale,
723
+ domainLocales,
723
724
  // Include module URLs so client navigation can import pages directly
724
725
  __vinext: {
725
726
  pageModuleUrl,
726
727
  appModuleUrl,
727
728
  },
728
- })}${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>`;
729
730
  // Try to load custom _document.tsx
730
731
  const docPath = path.join(pagesDir, "_document");
731
732
  let DocumentComponent = null;
@@ -770,9 +771,7 @@ hydrate();
770
771
  // Collect head HTML AFTER the shell renders (inside streamPageToResponse,
771
772
  // after renderToReadableStream resolves). Head tags from Suspense
772
773
  // children arrive late — this matches Next.js behavior.
773
- getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function"
774
- ? headShim.getSSRHeadHTML()
775
- : "",
774
+ getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function" ? headShim.getSSRHeadHTML() : "",
776
775
  });
777
776
  _renderEnd = now();
778
777
  // Clear SSR context after rendering
@@ -792,7 +791,7 @@ hydrate();
792
791
  if (wrapWithRouterContext) {
793
792
  isrElement = wrapWithRouterContext(isrElement);
794
793
  }
795
- const isrBodyHtml = await renderToStringAsync(isrElement);
794
+ const isrBodyHtml = await renderIsrPassToStringAsync(isrElement);
796
795
  const isrHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${isrBodyHtml}</div>${allScripts}</body></html>`;
797
796
  const cacheKey = isrCacheKey("pages", url.split("?")[0],
798
797
  // __VINEXT_BUILD_ID is a compile-time define — undefined in dev,
@@ -818,6 +817,8 @@ hydrate();
818
817
  routerKind: "Pages Router",
819
818
  routePath: route.pattern,
820
819
  routeType: "render",
820
+ }).catch(() => {
821
+ /* ignore reporting errors */
821
822
  });
822
823
  // Try to render custom 500 error page
823
824
  try {
@@ -832,10 +833,9 @@ hydrate();
832
833
  }
833
834
  }
834
835
  finally {
835
- // Cleanup is handled by ALS scope unwinding
836
- // each runWith*() scope is automatically cleaned up when it exits.
836
+ // Cleanup is handled by unified ALS scope unwinding.
837
837
  }
838
- }))))); // end runWithRouterState
838
+ });
839
839
  };
840
840
  }
841
841
  /**