vinext 0.0.8 → 0.0.10
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.
- package/README.md +30 -1
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/entry.js +1 -15
- package/dist/client/entry.js.map +1 -1
- package/dist/client/validate-module-path.d.ts +15 -0
- package/dist/client/validate-module-path.d.ts.map +1 -0
- package/dist/client/validate-module-path.js +31 -0
- package/dist/client/validate-module-path.js.map +1 -0
- package/dist/config/config-matchers.d.ts +12 -0
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +28 -0
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/dotenv.d.ts +40 -0
- package/dist/config/dotenv.d.ts.map +1 -0
- package/dist/config/dotenv.js +100 -0
- package/dist/config/dotenv.js.map +1 -0
- package/dist/config/next-config.d.ts +4 -0
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +16 -8
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -111
- package/dist/index.js.map +1 -1
- package/dist/server/api-handler.d.ts.map +1 -1
- package/dist/server/api-handler.js +2 -1
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-dev-server.d.ts +2 -0
- package/dist/server/app-dev-server.d.ts.map +1 -1
- package/dist/server/app-dev-server.js +292 -155
- package/dist/server/app-dev-server.js.map +1 -1
- package/dist/server/app-router-entry.d.ts.map +1 -1
- package/dist/server/app-router-entry.js +16 -3
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/dev-origin-check.d.ts +61 -0
- package/dist/server/dev-origin-check.d.ts.map +1 -0
- package/dist/server/dev-origin-check.js +164 -0
- package/dist/server/dev-origin-check.js.map +1 -0
- package/dist/server/dev-server.d.ts +0 -2
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +379 -372
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts +32 -2
- package/dist/server/image-optimization.d.ts.map +1 -1
- package/dist/server/image-optimization.js +110 -9
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts +41 -0
- package/dist/server/middleware-codegen.d.ts.map +1 -0
- package/dist/server/middleware-codegen.js +181 -0
- package/dist/server/middleware-codegen.js.map +1 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +12 -7
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/normalize-path.d.ts +22 -0
- package/dist/server/normalize-path.d.ts.map +1 -0
- package/dist/server/normalize-path.js +50 -0
- package/dist/server/normalize-path.js.map +1 -0
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +89 -25
- package/dist/server/prod-server.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +7 -0
- package/dist/shims/cache-runtime.d.ts.map +1 -1
- package/dist/shims/cache-runtime.js +19 -15
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +8 -0
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +20 -15
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +2 -3
- package/dist/shims/fetch-cache.d.ts.map +1 -1
- package/dist/shims/fetch-cache.js +74 -9
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/head-state.d.ts +6 -1
- package/dist/shims/head-state.d.ts.map +1 -1
- package/dist/shims/head-state.js +18 -15
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/headers.d.ts +9 -13
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +26 -47
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts.map +1 -1
- package/dist/shims/image.js +11 -2
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/navigation-state.d.ts +6 -1
- package/dist/shims/navigation-state.d.ts.map +1 -1
- package/dist/shims/navigation-state.js +20 -29
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.js +2 -2
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router-state.d.ts +6 -1
- package/dist/shims/router-state.d.ts.map +1 -1
- package/dist/shims/router-state.js +16 -21
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts.map +1 -1
- package/dist/shims/router.js +19 -6
- package/dist/shims/router.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { runWithFetchCache } from "../shims/fetch-cache.js";
|
|
4
|
+
import { _runWithCacheState } from "../shims/cache.js";
|
|
5
|
+
import { runWithPrivateCache } from "../shims/cache-runtime.js";
|
|
6
6
|
// Import server-only state modules to register ALS-backed accessors.
|
|
7
7
|
// These modules must be imported before any rendering occurs.
|
|
8
|
-
import "../shims/router-state.js";
|
|
9
|
-
import "../shims/head-state.js";
|
|
8
|
+
import { runWithRouterState } from "../shims/router-state.js";
|
|
9
|
+
import { runWithHeadState } from "../shims/head-state.js";
|
|
10
10
|
import { reportRequestError } from "./instrumentation.js";
|
|
11
11
|
import { safeJsonStringify } from "./html.js";
|
|
12
12
|
import { parseQueryString as parseQuery } from "../utils/query.js";
|
|
@@ -237,302 +237,301 @@ export function createSSRHandler(server, routes, pagesDir, i18nConfig) {
|
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
239
|
const { route, params } = match;
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
locales: i18nConfig?.locales,
|
|
256
|
-
defaultLocale: i18nConfig?.defaultLocale,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
// Set globalThis locale info for Link component locale prop support during SSR
|
|
260
|
-
if (i18nConfig) {
|
|
261
|
-
globalThis.__VINEXT_LOCALE__ = locale ?? i18nConfig.defaultLocale;
|
|
262
|
-
globalThis.__VINEXT_LOCALES__ = i18nConfig.locales;
|
|
263
|
-
globalThis.__VINEXT_DEFAULT_LOCALE__ = i18nConfig.defaultLocale;
|
|
264
|
-
}
|
|
265
|
-
// Load the page module through Vite's SSR pipeline
|
|
266
|
-
// This gives us HMR and transform support for free
|
|
267
|
-
const pageModule = await server.ssrLoadModule(route.filePath);
|
|
268
|
-
// Get the page component (default export)
|
|
269
|
-
const PageComponent = pageModule.default;
|
|
270
|
-
if (!PageComponent) {
|
|
271
|
-
res.statusCode = 500;
|
|
272
|
-
res.end(`Page ${route.filePath} has no default export`);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
// Collect page props via data fetching methods
|
|
276
|
-
let pageProps = {};
|
|
277
|
-
let isrRevalidateSeconds = null;
|
|
278
|
-
// Handle getStaticPaths for dynamic routes: validate the path
|
|
279
|
-
// and respect fallback: false (return 404 for unlisted paths).
|
|
280
|
-
if (typeof pageModule.getStaticPaths === "function" && route.isDynamic) {
|
|
281
|
-
const pathsResult = await pageModule.getStaticPaths({
|
|
282
|
-
locales: i18nConfig?.locales ?? [],
|
|
283
|
-
defaultLocale: i18nConfig?.defaultLocale ?? "",
|
|
284
|
-
});
|
|
285
|
-
const fallback = pathsResult?.fallback ?? false;
|
|
286
|
-
if (fallback === false) {
|
|
287
|
-
// Only allow paths explicitly listed in getStaticPaths
|
|
288
|
-
const paths = pathsResult?.paths ?? [];
|
|
289
|
-
const isValidPath = paths.some((p) => {
|
|
290
|
-
return Object.entries(p.params).every(([key, val]) => {
|
|
291
|
-
const actual = params[key];
|
|
292
|
-
if (Array.isArray(val)) {
|
|
293
|
-
return Array.isArray(actual) && val.join("/") === actual.join("/");
|
|
294
|
-
}
|
|
295
|
-
return String(val) === String(actual);
|
|
296
|
-
});
|
|
240
|
+
// Wrap the entire request in nested AsyncLocalStorage.run() scopes to
|
|
241
|
+
// ensure per-request isolation for all state modules.
|
|
242
|
+
return runWithRouterState(() => runWithHeadState(() => _runWithCacheState(() => runWithPrivateCache(() => runWithFetchCache(async () => {
|
|
243
|
+
try {
|
|
244
|
+
// Set SSR context for the router shim so useRouter() returns
|
|
245
|
+
// the correct URL and params during server-side rendering.
|
|
246
|
+
const routerShim = await server.ssrLoadModule("next/router");
|
|
247
|
+
if (typeof routerShim.setSSRContext === "function") {
|
|
248
|
+
routerShim.setSSRContext({
|
|
249
|
+
pathname: localeStrippedUrl.split("?")[0],
|
|
250
|
+
query: { ...params, ...parseQuery(url) },
|
|
251
|
+
asPath: url,
|
|
252
|
+
locale: locale ?? i18nConfig?.defaultLocale,
|
|
253
|
+
locales: i18nConfig?.locales,
|
|
254
|
+
defaultLocale: i18nConfig?.defaultLocale,
|
|
297
255
|
});
|
|
298
|
-
if (!isValidPath) {
|
|
299
|
-
await renderErrorPage(server, req, res, url, pagesDir, 404);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
256
|
}
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
if (typeof pageModule.getServerSideProps === "function") {
|
|
310
|
-
const context = {
|
|
311
|
-
params,
|
|
312
|
-
req,
|
|
313
|
-
res,
|
|
314
|
-
query: parseQuery(url),
|
|
315
|
-
resolvedUrl: localeStrippedUrl,
|
|
316
|
-
locale: locale ?? i18nConfig?.defaultLocale,
|
|
317
|
-
locales: i18nConfig?.locales,
|
|
318
|
-
defaultLocale: i18nConfig?.defaultLocale,
|
|
319
|
-
};
|
|
320
|
-
const result = await pageModule.getServerSideProps(context);
|
|
321
|
-
if (result && "props" in result) {
|
|
322
|
-
pageProps = result.props;
|
|
323
|
-
}
|
|
324
|
-
if (result && "redirect" in result) {
|
|
325
|
-
const { redirect } = result;
|
|
326
|
-
const status = redirect.statusCode ?? (redirect.permanent ? 308 : 307);
|
|
327
|
-
res.writeHead(status, {
|
|
328
|
-
Location: redirect.destination,
|
|
329
|
-
});
|
|
330
|
-
res.end();
|
|
331
|
-
return;
|
|
257
|
+
// Set globalThis locale info for Link component locale prop support during SSR
|
|
258
|
+
if (i18nConfig) {
|
|
259
|
+
globalThis.__VINEXT_LOCALE__ = locale ?? i18nConfig.defaultLocale;
|
|
260
|
+
globalThis.__VINEXT_LOCALES__ = i18nConfig.locales;
|
|
261
|
+
globalThis.__VINEXT_DEFAULT_LOCALE__ = i18nConfig.defaultLocale;
|
|
332
262
|
}
|
|
333
|
-
|
|
334
|
-
|
|
263
|
+
// Load the page module through Vite's SSR pipeline
|
|
264
|
+
// This gives us HMR and transform support for free
|
|
265
|
+
const pageModule = await server.ssrLoadModule(route.filePath);
|
|
266
|
+
// Get the page component (default export)
|
|
267
|
+
const PageComponent = pageModule.default;
|
|
268
|
+
if (!PageComponent) {
|
|
269
|
+
console.error(`[vinext] Page ${route.filePath} has no default export`);
|
|
270
|
+
res.statusCode = 500;
|
|
271
|
+
res.end("Page has no default export");
|
|
335
272
|
return;
|
|
336
273
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
274
|
+
// Collect page props via data fetching methods
|
|
275
|
+
let pageProps = {};
|
|
276
|
+
let isrRevalidateSeconds = null;
|
|
277
|
+
// Handle getStaticPaths for dynamic routes: validate the path
|
|
278
|
+
// and respect fallback: false (return 404 for unlisted paths).
|
|
279
|
+
if (typeof pageModule.getStaticPaths === "function" && route.isDynamic) {
|
|
280
|
+
const pathsResult = await pageModule.getStaticPaths({
|
|
281
|
+
locales: i18nConfig?.locales ?? [],
|
|
282
|
+
defaultLocale: i18nConfig?.defaultLocale ?? "",
|
|
283
|
+
});
|
|
284
|
+
const fallback = pathsResult?.fallback ?? false;
|
|
285
|
+
if (fallback === false) {
|
|
286
|
+
// Only allow paths explicitly listed in getStaticPaths
|
|
287
|
+
const paths = pathsResult?.paths ?? [];
|
|
288
|
+
const isValidPath = paths.some((p) => {
|
|
289
|
+
return Object.entries(p.params).every(([key, val]) => {
|
|
290
|
+
const actual = params[key];
|
|
291
|
+
if (Array.isArray(val)) {
|
|
292
|
+
return Array.isArray(actual) && val.join("/") === actual.join("/");
|
|
293
|
+
}
|
|
294
|
+
return String(val) === String(actual);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
if (!isValidPath) {
|
|
298
|
+
await renderErrorPage(server, req, res, url, pagesDir, 404);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// fallback: true or "blocking" — always SSR on-demand.
|
|
303
|
+
// In dev mode, Next.js does the same (no fallback shell).
|
|
304
|
+
// In production, both modes SSR on-demand with caching.
|
|
305
|
+
// The difference is that fallback:true could serve a shell first,
|
|
306
|
+
// but since we always have data available via SSR, we render fully.
|
|
347
307
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
308
|
+
if (typeof pageModule.getServerSideProps === "function") {
|
|
309
|
+
const context = {
|
|
310
|
+
params,
|
|
311
|
+
req,
|
|
312
|
+
res,
|
|
313
|
+
query: parseQuery(url),
|
|
314
|
+
resolvedUrl: localeStrippedUrl,
|
|
315
|
+
locale: locale ?? i18nConfig?.defaultLocale,
|
|
316
|
+
locales: i18nConfig?.locales,
|
|
317
|
+
defaultLocale: i18nConfig?.defaultLocale,
|
|
318
|
+
};
|
|
319
|
+
const result = await pageModule.getServerSideProps(context);
|
|
320
|
+
if (result && "props" in result) {
|
|
321
|
+
pageProps = result.props;
|
|
322
|
+
}
|
|
323
|
+
if (result && "redirect" in result) {
|
|
324
|
+
const { redirect } = result;
|
|
325
|
+
const status = redirect.statusCode ?? (redirect.permanent ? 308 : 307);
|
|
326
|
+
res.writeHead(status, {
|
|
327
|
+
Location: redirect.destination,
|
|
328
|
+
});
|
|
329
|
+
res.end();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (result && "notFound" in result && result.notFound) {
|
|
333
|
+
await renderErrorPage(server, req, res, url, pagesDir, 404);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
351
336
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
337
|
+
// Collect font preloads early so ISR cached responses can include
|
|
338
|
+
// the Link header (font preloads are module-level state that persists
|
|
339
|
+
// across requests after the font modules are first loaded).
|
|
340
|
+
let earlyFontLinkHeader = "";
|
|
341
|
+
try {
|
|
342
|
+
const earlyPreloads = [];
|
|
343
|
+
const fontGoogleEarly = await server.ssrLoadModule("next/font/google");
|
|
344
|
+
if (typeof fontGoogleEarly.getSSRFontPreloads === "function") {
|
|
345
|
+
earlyPreloads.push(...fontGoogleEarly.getSSRFontPreloads());
|
|
346
|
+
}
|
|
347
|
+
const fontLocalEarly = await server.ssrLoadModule("next/font/local");
|
|
348
|
+
if (typeof fontLocalEarly.getSSRFontPreloads === "function") {
|
|
349
|
+
earlyPreloads.push(...fontLocalEarly.getSSRFontPreloads());
|
|
350
|
+
}
|
|
351
|
+
if (earlyPreloads.length > 0) {
|
|
352
|
+
earlyFontLinkHeader = earlyPreloads
|
|
353
|
+
.map((p) => `<${p.href}>; rel=preload; as=font; type=${p.type}; crossorigin`)
|
|
354
|
+
.join(", ");
|
|
355
|
+
}
|
|
356
356
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
// Font modules not loaded yet — skip
|
|
360
|
-
}
|
|
361
|
-
if (typeof pageModule.getStaticProps === "function") {
|
|
362
|
-
// Check ISR cache before calling getStaticProps
|
|
363
|
-
const cacheKey = isrCacheKey("pages", url.split("?")[0]);
|
|
364
|
-
const cached = await isrGet(cacheKey);
|
|
365
|
-
if (cached && !cached.isStale && cached.value.value?.kind === "PAGES") {
|
|
366
|
-
// Fresh cache hit — serve directly
|
|
367
|
-
const cachedPage = cached.value.value;
|
|
368
|
-
const cachedHtml = cachedPage.html;
|
|
369
|
-
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
370
|
-
const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
|
|
371
|
-
const hitHeaders = {
|
|
372
|
-
"Content-Type": "text/html",
|
|
373
|
-
"X-Vinext-Cache": "HIT",
|
|
374
|
-
"Cache-Control": `s-maxage=${revalidateSecs}, stale-while-revalidate`,
|
|
375
|
-
};
|
|
376
|
-
if (earlyFontLinkHeader)
|
|
377
|
-
hitHeaders["Link"] = earlyFontLinkHeader;
|
|
378
|
-
res.writeHead(200, hitHeaders);
|
|
379
|
-
res.end(transformedHtml);
|
|
380
|
-
return;
|
|
357
|
+
catch {
|
|
358
|
+
// Font modules not loaded yet — skip
|
|
381
359
|
}
|
|
382
|
-
if (
|
|
383
|
-
//
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
360
|
+
if (typeof pageModule.getStaticProps === "function") {
|
|
361
|
+
// Check ISR cache before calling getStaticProps
|
|
362
|
+
const cacheKey = isrCacheKey("pages", url.split("?")[0]);
|
|
363
|
+
const cached = await isrGet(cacheKey);
|
|
364
|
+
if (cached && !cached.isStale && cached.value.value?.kind === "PAGES") {
|
|
365
|
+
// Fresh cache hit — serve directly
|
|
366
|
+
const cachedPage = cached.value.value;
|
|
367
|
+
const cachedHtml = cachedPage.html;
|
|
368
|
+
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
369
|
+
const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
|
|
370
|
+
const hitHeaders = {
|
|
371
|
+
"Content-Type": "text/html",
|
|
372
|
+
"X-Vinext-Cache": "HIT",
|
|
373
|
+
"Cache-Control": `s-maxage=${revalidateSecs}, stale-while-revalidate`,
|
|
374
|
+
};
|
|
375
|
+
if (earlyFontLinkHeader)
|
|
376
|
+
hitHeaders["Link"] = earlyFontLinkHeader;
|
|
377
|
+
res.writeHead(200, hitHeaders);
|
|
378
|
+
res.end(transformedHtml);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (cached && cached.isStale && cached.value.value?.kind === "PAGES") {
|
|
382
|
+
// Stale hit — serve stale immediately, trigger background regen
|
|
383
|
+
const cachedPage = cached.value.value;
|
|
384
|
+
const cachedHtml = cachedPage.html;
|
|
385
|
+
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
386
|
+
// Trigger background regeneration: re-run getStaticProps and
|
|
387
|
+
// update the cache so the next request is a HIT with fresh data.
|
|
388
|
+
triggerBackgroundRegeneration(cacheKey, async () => {
|
|
389
|
+
const freshResult = await pageModule.getStaticProps({ params });
|
|
390
|
+
if (freshResult && "props" in freshResult) {
|
|
391
|
+
const revalidate = typeof freshResult.revalidate === "number" ? freshResult.revalidate : 0;
|
|
392
|
+
if (revalidate > 0) {
|
|
393
|
+
await isrSet(cacheKey, buildPagesCacheValue(cachedHtml, freshResult.props), revalidate);
|
|
394
|
+
}
|
|
395
395
|
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
});
|
|
397
|
+
const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
|
|
398
|
+
const staleHeaders = {
|
|
399
|
+
"Content-Type": "text/html",
|
|
400
|
+
"X-Vinext-Cache": "STALE",
|
|
401
|
+
"Cache-Control": `s-maxage=${revalidateSecs}, stale-while-revalidate`,
|
|
402
|
+
};
|
|
403
|
+
if (earlyFontLinkHeader)
|
|
404
|
+
staleHeaders["Link"] = earlyFontLinkHeader;
|
|
405
|
+
res.writeHead(200, staleHeaders);
|
|
406
|
+
res.end(transformedHtml);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
// Cache miss — call getStaticProps normally
|
|
410
|
+
const context = {
|
|
411
|
+
params,
|
|
412
|
+
locale: locale ?? i18nConfig?.defaultLocale,
|
|
413
|
+
locales: i18nConfig?.locales,
|
|
414
|
+
defaultLocale: i18nConfig?.defaultLocale,
|
|
403
415
|
};
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
416
|
+
const result = await pageModule.getStaticProps(context);
|
|
417
|
+
if (result && "props" in result) {
|
|
418
|
+
pageProps = result.props;
|
|
419
|
+
}
|
|
420
|
+
if (result && "redirect" in result) {
|
|
421
|
+
const { redirect } = result;
|
|
422
|
+
const status = redirect.statusCode ?? (redirect.permanent ? 308 : 307);
|
|
423
|
+
res.writeHead(status, {
|
|
424
|
+
Location: redirect.destination,
|
|
425
|
+
});
|
|
426
|
+
res.end();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (result && "notFound" in result && result.notFound) {
|
|
430
|
+
await renderErrorPage(server, req, res, url, pagesDir, 404);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// Extract revalidate period for ISR caching after render
|
|
434
|
+
if (typeof result?.revalidate === "number" && result.revalidate > 0) {
|
|
435
|
+
isrRevalidateSeconds = result.revalidate;
|
|
436
|
+
}
|
|
409
437
|
}
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
// Try to load _app.tsx if it exists
|
|
439
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
440
|
+
let AppComponent = null;
|
|
441
|
+
const appPath = path.join(pagesDir, "_app");
|
|
442
|
+
if (findFileWithExtensions(appPath)) {
|
|
443
|
+
try {
|
|
444
|
+
const appModule = await server.ssrLoadModule(appPath);
|
|
445
|
+
AppComponent = appModule.default ?? null;
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// _app exists but failed to load
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// React and ReactDOMServer are imported at the top level as native Node
|
|
452
|
+
// modules. They must NOT go through Vite's SSR module runner because
|
|
453
|
+
// React is CJS and the ESModulesEvaluator doesn't define `module`.
|
|
454
|
+
const createElement = React.createElement;
|
|
455
|
+
let element;
|
|
456
|
+
if (AppComponent) {
|
|
457
|
+
element = createElement(AppComponent, {
|
|
458
|
+
Component: PageComponent,
|
|
459
|
+
pageProps,
|
|
426
460
|
});
|
|
427
|
-
res.end();
|
|
428
|
-
return;
|
|
429
461
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return;
|
|
462
|
+
else {
|
|
463
|
+
element = createElement(PageComponent, pageProps);
|
|
433
464
|
}
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
465
|
+
// Reset SSR head collector before rendering so <Head> tags are captured
|
|
466
|
+
const headShim = await server.ssrLoadModule("next/head");
|
|
467
|
+
if (typeof headShim.resetSSRHead === "function") {
|
|
468
|
+
headShim.resetSSRHead();
|
|
437
469
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
470
|
+
// Flush any pending dynamic() preloads so components are ready
|
|
471
|
+
const dynamicShim = await server.ssrLoadModule("next/dynamic");
|
|
472
|
+
if (typeof dynamicShim.flushPreloads === "function") {
|
|
473
|
+
await dynamicShim.flushPreloads();
|
|
474
|
+
}
|
|
475
|
+
// Collect any <Head> tags that were rendered during data fetching
|
|
476
|
+
// (shell head tags — Suspense children's head tags arrive late,
|
|
477
|
+
// matching Next.js behavior)
|
|
478
|
+
// Collect SSR font links (Google Fonts <link> tags) and font class styles
|
|
479
|
+
let fontHeadHTML = "";
|
|
480
|
+
const allFontStyles = [];
|
|
481
|
+
const allFontPreloads = [];
|
|
444
482
|
try {
|
|
445
|
-
const
|
|
446
|
-
|
|
483
|
+
const fontGoogle = await server.ssrLoadModule("next/font/google");
|
|
484
|
+
if (typeof fontGoogle.getSSRFontLinks === "function") {
|
|
485
|
+
const fontUrls = fontGoogle.getSSRFontLinks();
|
|
486
|
+
for (const fontUrl of fontUrls) {
|
|
487
|
+
const safeFontUrl = fontUrl.replace(/&/g, "&").replace(/"/g, """);
|
|
488
|
+
fontHeadHTML += `<link rel="stylesheet" href="${safeFontUrl}" />\n `;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (typeof fontGoogle.getSSRFontStyles === "function") {
|
|
492
|
+
allFontStyles.push(...fontGoogle.getSSRFontStyles());
|
|
493
|
+
}
|
|
494
|
+
// Collect preloads from self-hosted Google fonts
|
|
495
|
+
if (typeof fontGoogle.getSSRFontPreloads === "function") {
|
|
496
|
+
allFontPreloads.push(...fontGoogle.getSSRFontPreloads());
|
|
497
|
+
}
|
|
447
498
|
}
|
|
448
499
|
catch {
|
|
449
|
-
//
|
|
500
|
+
// next/font/google not used — skip
|
|
450
501
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
Component: PageComponent,
|
|
460
|
-
pageProps,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
element = createElement(PageComponent, pageProps);
|
|
465
|
-
}
|
|
466
|
-
// Reset SSR head collector before rendering so <Head> tags are captured
|
|
467
|
-
const headShim = await server.ssrLoadModule("next/head");
|
|
468
|
-
if (typeof headShim.resetSSRHead === "function") {
|
|
469
|
-
headShim.resetSSRHead();
|
|
470
|
-
}
|
|
471
|
-
// Flush any pending dynamic() preloads so components are ready
|
|
472
|
-
const dynamicShim = await server.ssrLoadModule("next/dynamic");
|
|
473
|
-
if (typeof dynamicShim.flushPreloads === "function") {
|
|
474
|
-
await dynamicShim.flushPreloads();
|
|
475
|
-
}
|
|
476
|
-
// Collect any <Head> tags that were rendered during data fetching
|
|
477
|
-
// (shell head tags — Suspense children's head tags arrive late,
|
|
478
|
-
// matching Next.js behavior)
|
|
479
|
-
// Collect SSR font links (Google Fonts <link> tags) and font class styles
|
|
480
|
-
let fontHeadHTML = "";
|
|
481
|
-
const allFontStyles = [];
|
|
482
|
-
const allFontPreloads = [];
|
|
483
|
-
try {
|
|
484
|
-
const fontGoogle = await server.ssrLoadModule("next/font/google");
|
|
485
|
-
if (typeof fontGoogle.getSSRFontLinks === "function") {
|
|
486
|
-
const fontUrls = fontGoogle.getSSRFontLinks();
|
|
487
|
-
for (const fontUrl of fontUrls) {
|
|
488
|
-
const safeFontUrl = fontUrl.replace(/&/g, "&").replace(/"/g, """);
|
|
489
|
-
fontHeadHTML += `<link rel="stylesheet" href="${safeFontUrl}" />\n `;
|
|
502
|
+
try {
|
|
503
|
+
const fontLocal = await server.ssrLoadModule("next/font/local");
|
|
504
|
+
if (typeof fontLocal.getSSRFontStyles === "function") {
|
|
505
|
+
allFontStyles.push(...fontLocal.getSSRFontStyles());
|
|
506
|
+
}
|
|
507
|
+
// Collect preloads from local font files
|
|
508
|
+
if (typeof fontLocal.getSSRFontPreloads === "function") {
|
|
509
|
+
allFontPreloads.push(...fontLocal.getSSRFontPreloads());
|
|
490
510
|
}
|
|
491
511
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
// Collect preloads from self-hosted Google fonts
|
|
496
|
-
if (typeof fontGoogle.getSSRFontPreloads === "function") {
|
|
497
|
-
allFontPreloads.push(...fontGoogle.getSSRFontPreloads());
|
|
512
|
+
catch {
|
|
513
|
+
// next/font/local not used — skip
|
|
498
514
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
allFontStyles.push(...fontLocal.getSSRFontStyles());
|
|
515
|
+
// Emit <link rel="preload"> for all collected font files (Google + local)
|
|
516
|
+
for (const { href, type } of allFontPreloads) {
|
|
517
|
+
// Escape href/type to prevent HTML attribute injection (defense-in-depth;
|
|
518
|
+
// Vite-resolved asset paths should never contain special chars).
|
|
519
|
+
const safeHref = href.replace(/&/g, "&").replace(/"/g, """);
|
|
520
|
+
const safeType = type.replace(/&/g, "&").replace(/"/g, """);
|
|
521
|
+
fontHeadHTML += `<link rel="preload" href="${safeHref}" as="font" type="${safeType}" crossorigin />\n `;
|
|
507
522
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
allFontPreloads.push(...fontLocal.getSSRFontPreloads());
|
|
523
|
+
if (allFontStyles.length > 0) {
|
|
524
|
+
fontHeadHTML += `<style data-vinext-fonts>${allFontStyles.join("\n")}</style>\n `;
|
|
511
525
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
const
|
|
521
|
-
const safeType = type.replace(/&/g, "&").replace(/"/g, """);
|
|
522
|
-
fontHeadHTML += `<link rel="preload" href="${safeHref}" as="font" type="${safeType}" crossorigin />\n `;
|
|
523
|
-
}
|
|
524
|
-
if (allFontStyles.length > 0) {
|
|
525
|
-
fontHeadHTML += `<style data-vinext-fonts>${allFontStyles.join("\n")}</style>\n `;
|
|
526
|
-
}
|
|
527
|
-
// Convert absolute file paths to Vite-servable URLs (relative to root)
|
|
528
|
-
const viteRoot = server.config.root;
|
|
529
|
-
const pageModuleUrl = "/" + path.relative(viteRoot, route.filePath);
|
|
530
|
-
const appModuleUrl = AppComponent
|
|
531
|
-
? "/" + path.relative(viteRoot, path.join(pagesDir, "_app"))
|
|
532
|
-
: null;
|
|
533
|
-
// Hydration entry: inline script that imports the page and hydrates.
|
|
534
|
-
// Stores the React root and page loader for client-side navigation.
|
|
535
|
-
const hydrationScript = `
|
|
526
|
+
// Convert absolute file paths to Vite-servable URLs (relative to root)
|
|
527
|
+
const viteRoot = server.config.root;
|
|
528
|
+
const pageModuleUrl = "/" + path.relative(viteRoot, route.filePath);
|
|
529
|
+
const appModuleUrl = AppComponent
|
|
530
|
+
? "/" + path.relative(viteRoot, path.join(pagesDir, "_app"))
|
|
531
|
+
: null;
|
|
532
|
+
// Hydration entry: inline script that imports the page and hydrates.
|
|
533
|
+
// Stores the React root and page loader for client-side navigation.
|
|
534
|
+
const hydrationScript = `
|
|
536
535
|
<script type="module">
|
|
537
536
|
import React from "react";
|
|
538
537
|
import { hydrateRoot } from "react-dom/client";
|
|
@@ -545,13 +544,13 @@ async function hydrate() {
|
|
|
545
544
|
const PageComponent = pageModule.default;
|
|
546
545
|
let element;
|
|
547
546
|
${appModuleUrl
|
|
548
|
-
|
|
547
|
+
? `
|
|
549
548
|
const appModule = await import("${appModuleUrl}");
|
|
550
549
|
const AppComponent = appModule.default;
|
|
551
550
|
window.__VINEXT_APP__ = AppComponent;
|
|
552
551
|
element = React.createElement(AppComponent, { Component: PageComponent, pageProps });
|
|
553
552
|
`
|
|
554
|
-
|
|
553
|
+
: `
|
|
555
554
|
element = React.createElement(PageComponent, pageProps);
|
|
556
555
|
`}
|
|
557
556
|
const root = hydrateRoot(document.getElementById("__next"), element);
|
|
@@ -559,105 +558,113 @@ async function hydrate() {
|
|
|
559
558
|
}
|
|
560
559
|
hydrate();
|
|
561
560
|
</script>`;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
561
|
+
const nextDataScript = `<script>window.__NEXT_DATA__ = ${safeJsonStringify({
|
|
562
|
+
props: { pageProps },
|
|
563
|
+
page: patternToNextFormat(route.pattern),
|
|
564
|
+
query: params,
|
|
565
|
+
isFallback: false,
|
|
566
|
+
locale: locale ?? i18nConfig?.defaultLocale,
|
|
567
|
+
locales: i18nConfig?.locales,
|
|
568
|
+
defaultLocale: i18nConfig?.defaultLocale,
|
|
569
|
+
// Include module URLs so client navigation can import pages directly
|
|
570
|
+
__vinext: {
|
|
571
|
+
pageModuleUrl,
|
|
572
|
+
appModuleUrl,
|
|
573
|
+
},
|
|
574
|
+
})}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? i18nConfig.defaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(i18nConfig.defaultLocale)}` : ""}</script>`;
|
|
575
|
+
// Try to load custom _document.tsx
|
|
576
|
+
const docPath = path.join(pagesDir, "_document");
|
|
577
|
+
let DocumentComponent = null;
|
|
578
|
+
if (findFileWithExtensions(docPath)) {
|
|
579
|
+
try {
|
|
580
|
+
const docModule = await server.ssrLoadModule(docPath);
|
|
581
|
+
DocumentComponent = docModule.default ?? null;
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
// _document exists but failed to load
|
|
585
|
+
}
|
|
583
586
|
}
|
|
584
|
-
|
|
585
|
-
|
|
587
|
+
const allScripts = `${nextDataScript}\n ${hydrationScript}`;
|
|
588
|
+
// Build cache headers for ISR responses
|
|
589
|
+
const extraHeaders = {};
|
|
590
|
+
if (isrRevalidateSeconds) {
|
|
591
|
+
extraHeaders["Cache-Control"] = `s-maxage=${isrRevalidateSeconds}, stale-while-revalidate`;
|
|
592
|
+
extraHeaders["X-Vinext-Cache"] = "MISS";
|
|
593
|
+
}
|
|
594
|
+
// Set HTTP Link header for font preloading.
|
|
595
|
+
// This lets the browser (and CDN) start fetching font files before parsing HTML.
|
|
596
|
+
if (allFontPreloads.length > 0) {
|
|
597
|
+
extraHeaders["Link"] = allFontPreloads
|
|
598
|
+
.map((p) => `<${p.href}>; rel=preload; as=font; type=${p.type}; crossorigin`)
|
|
599
|
+
.join(", ");
|
|
600
|
+
}
|
|
601
|
+
// Stream the page using progressive SSR.
|
|
602
|
+
// The shell (layouts, non-suspended content) arrives immediately.
|
|
603
|
+
// Suspense content streams in as it resolves.
|
|
604
|
+
await streamPageToResponse(res, element, {
|
|
605
|
+
url,
|
|
606
|
+
server,
|
|
607
|
+
fontHeadHTML,
|
|
608
|
+
scripts: allScripts,
|
|
609
|
+
DocumentComponent,
|
|
610
|
+
statusCode,
|
|
611
|
+
extraHeaders,
|
|
612
|
+
// Collect head HTML AFTER the shell renders (inside streamPageToResponse,
|
|
613
|
+
// after renderToReadableStream resolves). Head tags from Suspense
|
|
614
|
+
// children arrive late — this matches Next.js behavior.
|
|
615
|
+
getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function"
|
|
616
|
+
? headShim.getSSRHeadHTML()
|
|
617
|
+
: "",
|
|
618
|
+
});
|
|
619
|
+
// Clear SSR context after rendering
|
|
620
|
+
if (typeof routerShim.setSSRContext === "function") {
|
|
621
|
+
routerShim.setSSRContext(null);
|
|
622
|
+
}
|
|
623
|
+
// If ISR is enabled, we need the full HTML for caching.
|
|
624
|
+
// For ISR, re-render synchronously to get the complete HTML string.
|
|
625
|
+
// This runs after the stream is already sent, so it doesn't affect TTFB.
|
|
626
|
+
if (isrRevalidateSeconds !== null && isrRevalidateSeconds > 0) {
|
|
627
|
+
const isrElement = AppComponent
|
|
628
|
+
? createElement(AppComponent, { Component: pageModule.default, pageProps })
|
|
629
|
+
: createElement(pageModule.default, pageProps);
|
|
630
|
+
const isrBodyHtml = await renderToStringAsync(isrElement);
|
|
631
|
+
const isrHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${isrBodyHtml}</div>${allScripts}</body></html>`;
|
|
632
|
+
const cacheKey = isrCacheKey("pages", url.split("?")[0]);
|
|
633
|
+
await isrSet(cacheKey, buildPagesCacheValue(isrHtml, pageProps), isrRevalidateSeconds);
|
|
634
|
+
setRevalidateDuration(cacheKey, isrRevalidateSeconds);
|
|
586
635
|
}
|
|
587
636
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
scripts: allScripts,
|
|
610
|
-
DocumentComponent,
|
|
611
|
-
statusCode,
|
|
612
|
-
extraHeaders,
|
|
613
|
-
// Collect head HTML AFTER the shell renders (inside streamPageToResponse,
|
|
614
|
-
// after renderToReadableStream resolves). Head tags from Suspense
|
|
615
|
-
// children arrive late — this matches Next.js behavior.
|
|
616
|
-
getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function"
|
|
617
|
-
? headShim.getSSRHeadHTML()
|
|
618
|
-
: "",
|
|
619
|
-
});
|
|
620
|
-
// Clear SSR context after rendering
|
|
621
|
-
if (typeof routerShim.setSSRContext === "function") {
|
|
622
|
-
routerShim.setSSRContext(null);
|
|
623
|
-
}
|
|
624
|
-
// If ISR is enabled, we need the full HTML for caching.
|
|
625
|
-
// For ISR, re-render synchronously to get the complete HTML string.
|
|
626
|
-
// This runs after the stream is already sent, so it doesn't affect TTFB.
|
|
627
|
-
if (isrRevalidateSeconds !== null && isrRevalidateSeconds > 0) {
|
|
628
|
-
const isrElement = AppComponent
|
|
629
|
-
? createElement(AppComponent, { Component: pageModule.default, pageProps })
|
|
630
|
-
: createElement(pageModule.default, pageProps);
|
|
631
|
-
const isrBodyHtml = await renderToStringAsync(isrElement);
|
|
632
|
-
const isrHtml = `<!DOCTYPE html><html><head></head><body><div id="__next">${isrBodyHtml}</div>${allScripts}</body></html>`;
|
|
633
|
-
const cacheKey = isrCacheKey("pages", url.split("?")[0]);
|
|
634
|
-
await isrSet(cacheKey, buildPagesCacheValue(isrHtml, pageProps), isrRevalidateSeconds);
|
|
635
|
-
setRevalidateDuration(cacheKey, isrRevalidateSeconds);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
catch (e) {
|
|
639
|
-
// Let Vite fix the stack trace for better dev experience
|
|
640
|
-
server.ssrFixStacktrace(e);
|
|
641
|
-
console.error(e);
|
|
642
|
-
// Report error via instrumentation hook if registered
|
|
643
|
-
reportRequestError(e instanceof Error ? e : new Error(String(e)), {
|
|
644
|
-
path: url,
|
|
645
|
-
method: req.method ?? "GET",
|
|
646
|
-
headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v ?? "")])),
|
|
647
|
-
}, { routerKind: "Pages Router", routePath: route.pattern, routeType: "render" }).catch(() => { });
|
|
648
|
-
// Try to render custom 500 error page
|
|
649
|
-
try {
|
|
650
|
-
await renderErrorPage(server, req, res, url, pagesDir, 500);
|
|
637
|
+
catch (e) {
|
|
638
|
+
// Let Vite fix the stack trace for better dev experience
|
|
639
|
+
server.ssrFixStacktrace(e);
|
|
640
|
+
console.error(e);
|
|
641
|
+
// Report error via instrumentation hook if registered
|
|
642
|
+
reportRequestError(e instanceof Error ? e : new Error(String(e)), {
|
|
643
|
+
path: url,
|
|
644
|
+
method: req.method ?? "GET",
|
|
645
|
+
headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v ?? "")])),
|
|
646
|
+
}, { routerKind: "Pages Router", routePath: route.pattern, routeType: "render" }).catch(() => { });
|
|
647
|
+
// Try to render custom 500 error page
|
|
648
|
+
try {
|
|
649
|
+
await renderErrorPage(server, req, res, url, pagesDir, 500);
|
|
650
|
+
}
|
|
651
|
+
catch (fallbackErr) {
|
|
652
|
+
// If error page itself fails, fall back to plain text.
|
|
653
|
+
// This is a dev-only code path (prod uses prod-server.ts), so
|
|
654
|
+
// include the error message for debugging.
|
|
655
|
+
res.statusCode = 500;
|
|
656
|
+
res.end(`Internal Server Error: ${fallbackErr.message}`);
|
|
657
|
+
}
|
|
651
658
|
}
|
|
652
|
-
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
res.end(`Internal Server Error: ${e.message}`);
|
|
659
|
+
finally {
|
|
660
|
+
// Cleanup is handled by ALS scope unwinding —
|
|
661
|
+
// each runWith*() scope is automatically cleaned up when it exits.
|
|
656
662
|
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
663
|
+
}) // end runWithFetchCache
|
|
664
|
+
) // end runWithPrivateCache
|
|
665
|
+
) // end _runWithCacheState
|
|
666
|
+
) // end runWithHeadState
|
|
667
|
+
); // end runWithRouterState
|
|
661
668
|
};
|
|
662
669
|
}
|
|
663
670
|
/**
|