vinext 0.0.32 → 0.0.33

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 (56) hide show
  1. package/README.md +7 -6
  2. package/dist/config/next-config.d.ts +2 -0
  3. package/dist/config/next-config.js +4 -0
  4. package/dist/config/next-config.js.map +1 -1
  5. package/dist/entries/app-browser-entry.js +3 -330
  6. package/dist/entries/app-browser-entry.js.map +1 -1
  7. package/dist/entries/app-rsc-entry.js +284 -643
  8. package/dist/entries/app-rsc-entry.js.map +1 -1
  9. package/dist/entries/app-ssr-entry.js +4 -460
  10. package/dist/entries/app-ssr-entry.js.map +1 -1
  11. package/dist/entries/runtime-entry-module.d.ts +13 -0
  12. package/dist/entries/runtime-entry-module.js +27 -0
  13. package/dist/entries/runtime-entry-module.js.map +1 -0
  14. package/dist/index.js +3 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/plugins/optimize-imports.d.ts +38 -0
  17. package/dist/plugins/optimize-imports.js +557 -0
  18. package/dist/plugins/optimize-imports.js.map +1 -0
  19. package/dist/server/app-browser-entry.d.ts +1 -0
  20. package/dist/server/app-browser-entry.js +161 -0
  21. package/dist/server/app-browser-entry.js.map +1 -0
  22. package/dist/server/app-browser-stream.d.ts +33 -0
  23. package/dist/server/app-browser-stream.js +54 -0
  24. package/dist/server/app-browser-stream.js.map +1 -0
  25. package/dist/server/app-page-cache.d.ts +61 -0
  26. package/dist/server/app-page-cache.js +133 -0
  27. package/dist/server/app-page-cache.js.map +1 -0
  28. package/dist/server/app-page-response.d.ts +51 -0
  29. package/dist/server/app-page-response.js +90 -0
  30. package/dist/server/app-page-response.js.map +1 -0
  31. package/dist/server/app-route-handler-cache.d.ts +42 -0
  32. package/dist/server/app-route-handler-cache.js +69 -0
  33. package/dist/server/app-route-handler-cache.js.map +1 -0
  34. package/dist/server/app-route-handler-execution.d.ts +64 -0
  35. package/dist/server/app-route-handler-execution.js +100 -0
  36. package/dist/server/app-route-handler-execution.js.map +1 -0
  37. package/dist/server/app-route-handler-policy.d.ts +51 -0
  38. package/dist/server/app-route-handler-policy.js +57 -0
  39. package/dist/server/app-route-handler-policy.js.map +1 -0
  40. package/dist/server/app-route-handler-response.d.ts +26 -0
  41. package/dist/server/app-route-handler-response.js +61 -0
  42. package/dist/server/app-route-handler-response.js.map +1 -0
  43. package/dist/server/app-route-handler-runtime.d.ts +27 -0
  44. package/dist/server/app-route-handler-runtime.js +99 -0
  45. package/dist/server/app-route-handler-runtime.js.map +1 -0
  46. package/dist/server/app-ssr-entry.d.ts +19 -0
  47. package/dist/server/app-ssr-entry.js +105 -0
  48. package/dist/server/app-ssr-entry.js.map +1 -0
  49. package/dist/server/app-ssr-stream.d.ts +30 -0
  50. package/dist/server/app-ssr-stream.js +116 -0
  51. package/dist/server/app-ssr-stream.js.map +1 -0
  52. package/dist/shims/metadata.js +3 -3
  53. package/dist/shims/metadata.js.map +1 -1
  54. package/dist/shims/request-state-types.d.ts +2 -2
  55. package/dist/shims/unified-request-context.d.ts +1 -1
  56. package/package.json +1 -1
@@ -16,33 +16,14 @@ import { fileURLToPath } from "node:url";
16
16
  const configMatchersPath = fileURLToPath(new URL("../config/config-matchers.js", import.meta.url)).replace(/\\/g, "/");
17
17
  const requestPipelinePath = fileURLToPath(new URL("../server/request-pipeline.js", import.meta.url)).replace(/\\/g, "/");
18
18
  const requestContextShimPath = fileURLToPath(new URL("../shims/request-context.js", import.meta.url)).replace(/\\/g, "/");
19
+ const appRouteHandlerRuntimePath = fileURLToPath(new URL("../server/app-route-handler-runtime.js", import.meta.url)).replace(/\\/g, "/");
20
+ const appRouteHandlerPolicyPath = fileURLToPath(new URL("../server/app-route-handler-policy.js", import.meta.url)).replace(/\\/g, "/");
21
+ const appRouteHandlerExecutionPath = fileURLToPath(new URL("../server/app-route-handler-execution.js", import.meta.url)).replace(/\\/g, "/");
22
+ const appRouteHandlerCachePath = fileURLToPath(new URL("../server/app-route-handler-cache.js", import.meta.url)).replace(/\\/g, "/");
23
+ const appPageCachePath = fileURLToPath(new URL("../server/app-page-cache.js", import.meta.url)).replace(/\\/g, "/");
24
+ const appPageResponsePath = fileURLToPath(new URL("../server/app-page-response.js", import.meta.url)).replace(/\\/g, "/");
25
+ const appRouteHandlerResponsePath = fileURLToPath(new URL("../server/app-route-handler-response.js", import.meta.url)).replace(/\\/g, "/");
19
26
  const routeTriePath = fileURLToPath(new URL("../routing/route-trie.js", import.meta.url)).replace(/\\/g, "/");
20
- const routeHandlerHelperCode = String.raw`
21
- // Duplicated from the build-time constant above via JSON.stringify.
22
- const ROUTE_HANDLER_HTTP_METHODS = ${JSON.stringify([
23
- "GET",
24
- "HEAD",
25
- "POST",
26
- "PUT",
27
- "DELETE",
28
- "PATCH",
29
- "OPTIONS"
30
- ])};
31
-
32
- function collectRouteHandlerMethods(handler) {
33
- const methods = ROUTE_HANDLER_HTTP_METHODS.filter((method) => typeof handler[method] === "function");
34
- if (methods.includes("GET") && !methods.includes("HEAD")) {
35
- methods.push("HEAD");
36
- }
37
- return methods;
38
- }
39
-
40
- function buildRouteHandlerAllowHeader(exportedMethods) {
41
- const allow = new Set(exportedMethods);
42
- allow.add("OPTIONS");
43
- return Array.from(allow).sort().join(", ");
44
- }
45
- `;
46
27
  /**
47
28
  * Generate the virtual RSC entry module.
48
29
  *
@@ -232,6 +213,33 @@ ${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(inst
232
213
  ${effectiveMetaRoutes.length > 0 ? `import { sitemapToXml, robotsToText, manifestToJson } from ${JSON.stringify(fileURLToPath(new URL("../server/metadata-routes.js", import.meta.url)).replace(/\\/g, "/"))};` : ""}
233
214
  import { requestContextFromRequest, normalizeHost, matchRedirect, matchRewrite, matchHeaders, isExternalUrl, proxyExternalRequest, sanitizeDestination } from ${JSON.stringify(configMatchersPath)};
234
215
  import { validateCsrfOrigin, validateImageUrl, guardProtocolRelativeUrl, hasBasePath, stripBasePath, normalizeTrailingSlash, processMiddlewareHeaders } from ${JSON.stringify(requestPipelinePath)};
216
+ import {
217
+ isKnownDynamicAppRoute as __isKnownDynamicAppRoute,
218
+ } from ${JSON.stringify(appRouteHandlerRuntimePath)};
219
+ import {
220
+ getAppRouteHandlerRevalidateSeconds as __getAppRouteHandlerRevalidateSeconds,
221
+ hasAppRouteHandlerDefaultExport as __hasAppRouteHandlerDefaultExport,
222
+ resolveAppRouteHandlerMethod as __resolveAppRouteHandlerMethod,
223
+ shouldReadAppRouteHandlerCache as __shouldReadAppRouteHandlerCache,
224
+ } from ${JSON.stringify(appRouteHandlerPolicyPath)};
225
+ import {
226
+ executeAppRouteHandler as __executeAppRouteHandler,
227
+ } from ${JSON.stringify(appRouteHandlerExecutionPath)};
228
+ import { readAppRouteHandlerCacheResponse as __readAppRouteHandlerCacheResponse } from ${JSON.stringify(appRouteHandlerCachePath)};
229
+ import {
230
+ finalizeAppPageHtmlCacheResponse as __finalizeAppPageHtmlCacheResponse,
231
+ readAppPageCacheResponse as __readAppPageCacheResponse,
232
+ scheduleAppPageRscCacheWrite as __scheduleAppPageRscCacheWrite,
233
+ } from ${JSON.stringify(appPageCachePath)};
234
+ import {
235
+ buildAppPageHtmlResponse as __buildAppPageHtmlResponse,
236
+ buildAppPageRscResponse as __buildAppPageRscResponse,
237
+ resolveAppPageHtmlResponsePolicy as __resolveAppPageHtmlResponsePolicy,
238
+ resolveAppPageRscResponsePolicy as __resolveAppPageRscResponsePolicy,
239
+ } from ${JSON.stringify(appPageResponsePath)};
240
+ import {
241
+ applyRouteHandlerMiddlewareContext as __applyRouteHandlerMiddlewareContext,
242
+ } from ${JSON.stringify(appRouteHandlerResponsePath)};
235
243
  import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache";
236
244
  import { getRequestExecutionContext as _getRequestExecutionContext } from ${JSON.stringify(requestContextShimPath)};
237
245
  import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache";
@@ -245,7 +253,6 @@ import { getSSRFontStyles as _getSSRFontStylesLocal, getSSRFontPreloads as _getS
245
253
  function _getSSRFontStyles() { return [..._getSSRFontStylesGoogle(), ..._getSSRFontStylesLocal()]; }
246
254
  function _getSSRFontPreloads() { return [..._getSSRFontPreloadsGoogle(), ..._getSSRFontPreloadsLocal()]; }
247
255
  ${hasPagesDir ? `// Note: pageRoutes loaded lazily via SSR env in /__vinext/prerender/pages-static-paths handler` : ""}
248
- ${routeHandlerHelperCode}
249
256
 
250
257
  // ALS used to suppress the expected "Invalid hook call" dev warning when
251
258
  // layout/page components are probed outside React's render cycle. Patching
@@ -2037,266 +2044,132 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2037
2044
  if (route.routeHandler) {
2038
2045
  const handler = route.routeHandler;
2039
2046
  const method = request.method.toUpperCase();
2040
- const revalidateSeconds = typeof handler.revalidate === "number" && handler.revalidate > 0 && handler.revalidate !== Infinity ? handler.revalidate : null;
2041
- if (typeof handler["default"] === "function" && process.env.NODE_ENV === "development") {
2047
+ const revalidateSeconds = __getAppRouteHandlerRevalidateSeconds(handler);
2048
+ if (__hasAppRouteHandlerDefaultExport(handler) && process.env.NODE_ENV === "development") {
2042
2049
  console.error(
2043
2050
  "[vinext] Detected default export in route handler " + route.pattern + ". Export a named export for each HTTP method instead.",
2044
2051
  );
2045
2052
  }
2046
2053
 
2047
- // Collect exported HTTP methods for OPTIONS auto-response and Allow header
2048
- const exportedMethods = collectRouteHandlerMethods(handler);
2049
- const allowHeaderForOptions = buildRouteHandlerAllowHeader(exportedMethods);
2050
-
2051
- // Route handlers need the same middleware header/status merge behavior as
2052
- // page responses. This keeps middleware response headers visible on API
2053
- // routes in Workers/dev, and preserves custom rewrite status overrides.
2054
- function attachRouteHandlerMiddlewareContext(response) {
2055
- // _mwCtx.headers is only set (non-null) when middleware actually ran and
2056
- // produced a continue/rewrite response. An empty Headers object (middleware
2057
- // ran but produced no response headers) is a harmless edge case: the early
2058
- // return is skipped, but the copy loop below is a no-op, so no incorrect
2059
- // headers are added. The allocation cost in that case is acceptable.
2060
- if (!_mwCtx.headers && _mwCtx.status == null) return response;
2061
- const responseHeaders = new Headers(response.headers);
2062
- if (_mwCtx.headers) {
2063
- for (const [key, value] of _mwCtx.headers) {
2064
- responseHeaders.append(key, value);
2065
- }
2066
- }
2067
- return new Response(response.body, {
2068
- status: _mwCtx.status ?? response.status,
2069
- statusText: response.statusText,
2070
- headers: responseHeaders,
2071
- });
2072
- }
2054
+ const {
2055
+ allowHeaderForOptions,
2056
+ handlerFn,
2057
+ isAutoHead,
2058
+ shouldAutoRespondToOptions,
2059
+ } = __resolveAppRouteHandlerMethod(handler, method);
2073
2060
 
2074
- // OPTIONS auto-implementation: respond with Allow header and 204
2075
- if (method === "OPTIONS" && typeof handler["OPTIONS"] !== "function") {
2061
+ if (shouldAutoRespondToOptions) {
2076
2062
  setHeadersContext(null);
2077
2063
  setNavigationContext(null);
2078
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2079
- status: 204,
2080
- headers: { "Allow": allowHeaderForOptions },
2081
- }));
2082
- }
2083
-
2084
- // HEAD auto-implementation: run GET handler and strip body
2085
- let handlerFn = handler[method];
2086
- let isAutoHead = false;
2087
- if (method === "HEAD" && typeof handler["HEAD"] !== "function" && typeof handler["GET"] === "function") {
2088
- handlerFn = handler["GET"];
2089
- isAutoHead = true;
2064
+ return __applyRouteHandlerMiddlewareContext(
2065
+ new Response(null, {
2066
+ status: 204,
2067
+ headers: { "Allow": allowHeaderForOptions },
2068
+ }),
2069
+ _mwCtx,
2070
+ );
2090
2071
  }
2091
2072
 
2092
2073
  // ISR cache read for route handlers (production only).
2093
2074
  // Only GET/HEAD (auto-HEAD) with finite revalidate > 0 are ISR-eligible.
2094
- // This runs before handler execution so a cache HIT skips the handler entirely.
2075
+ // Known-dynamic handlers skip the read entirely so stale cache entries
2076
+ // from earlier requests do not replay once the process has learned they
2077
+ // access request-specific data.
2095
2078
  if (
2096
- process.env.NODE_ENV === "production" &&
2097
- revalidateSeconds !== null &&
2098
- handler.dynamic !== "force-dynamic" &&
2099
- (method === "GET" || isAutoHead) &&
2100
- typeof handlerFn === "function"
2079
+ __shouldReadAppRouteHandlerCache({
2080
+ dynamicConfig: handler.dynamic,
2081
+ handlerFn,
2082
+ isAutoHead,
2083
+ isKnownDynamic: __isKnownDynamicAppRoute(route.pattern),
2084
+ isProduction: process.env.NODE_ENV === "production",
2085
+ method,
2086
+ revalidateSeconds,
2087
+ })
2101
2088
  ) {
2102
- const __routeKey = __isrRouteKey(cleanPathname);
2103
- try {
2104
- const __cached = await __isrGet(__routeKey);
2105
- if (__cached && !__cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_ROUTE") {
2106
- // HIT — return cached response immediately
2107
- const __cv = __cached.value.value;
2108
- __isrDebug?.("HIT (route)", cleanPathname);
2089
+ const __cachedRouteResponse = await __readAppRouteHandlerCacheResponse({
2090
+ basePath: __basePath,
2091
+ buildPageCacheTags: __pageCacheTags,
2092
+ cleanPathname,
2093
+ clearRequestContext: function() {
2109
2094
  setHeadersContext(null);
2110
2095
  setNavigationContext(null);
2111
- const __hitHeaders = Object.assign({}, __cv.headers || {});
2112
- __hitHeaders["X-Vinext-Cache"] = "HIT";
2113
- __hitHeaders["Cache-Control"] = "s-maxage=" + revalidateSeconds + ", stale-while-revalidate";
2114
- if (isAutoHead) {
2115
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: __cv.status, headers: __hitHeaders }));
2116
- }
2117
- return attachRouteHandlerMiddlewareContext(new Response(__cv.body, { status: __cv.status, headers: __hitHeaders }));
2118
- }
2119
- if (__cached && __cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_ROUTE") {
2120
- // STALE — serve stale response, trigger background regeneration
2121
- const __sv = __cached.value.value;
2122
- const __revalSecs = revalidateSeconds;
2123
- const __revalHandlerFn = handlerFn;
2124
- const __revalParams = params;
2125
- const __revalUrl = request.url;
2126
- const __revalSearchParams = new URLSearchParams(url.searchParams);
2127
- __triggerBackgroundRegeneration(__routeKey, async function() {
2128
- const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2129
- const __revalUCtx = _createUnifiedCtx({
2130
- headersContext: __revalHeadCtx,
2131
- executionContext: _getRequestExecutionContext(),
2132
- });
2133
- await _runWithUnifiedCtx(__revalUCtx, async () => {
2134
- _ensureFetchPatch();
2135
- setNavigationContext({ pathname: cleanPathname, searchParams: __revalSearchParams, params: __revalParams });
2136
- const __syntheticReq = new Request(__revalUrl, { method: "GET" });
2137
- const __revalResponse = await __revalHandlerFn(__syntheticReq, { params: __revalParams });
2138
- const __regenDynamic = consumeDynamicUsage();
2139
- setNavigationContext(null);
2140
- if (__regenDynamic) {
2141
- __isrDebug?.("route regen skipped (dynamic usage)", cleanPathname);
2142
- return;
2143
- }
2144
- const __freshBody = await __revalResponse.arrayBuffer();
2145
- const __freshHeaders = {};
2146
- __revalResponse.headers.forEach(function(v, k) {
2147
- if (k !== "x-vinext-cache" && k !== "cache-control") __freshHeaders[k] = v;
2148
- });
2149
- const __routeTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2150
- await __isrSet(__routeKey, { kind: "APP_ROUTE", body: __freshBody, status: __revalResponse.status, headers: __freshHeaders }, __revalSecs, __routeTags);
2151
- __isrDebug?.("route regen complete", __routeKey);
2152
- });
2096
+ },
2097
+ consumeDynamicUsage,
2098
+ getCollectedFetchTags,
2099
+ handlerFn,
2100
+ i18n: __i18nConfig,
2101
+ isAutoHead,
2102
+ isrDebug: __isrDebug,
2103
+ isrGet: __isrGet,
2104
+ isrRouteKey: __isrRouteKey,
2105
+ isrSet: __isrSet,
2106
+ markDynamicUsage,
2107
+ middlewareContext: _mwCtx,
2108
+ params,
2109
+ requestUrl: request.url,
2110
+ revalidateSearchParams: url.searchParams,
2111
+ revalidateSeconds,
2112
+ routePattern: route.pattern,
2113
+ runInRevalidationContext: async function(renderFn) {
2114
+ const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2115
+ const __revalUCtx = _createUnifiedCtx({
2116
+ headersContext: __revalHeadCtx,
2117
+ executionContext: _getRequestExecutionContext(),
2153
2118
  });
2154
- __isrDebug?.("STALE (route)", cleanPathname);
2155
- setHeadersContext(null);
2156
- setNavigationContext(null);
2157
- const __staleHeaders = Object.assign({}, __sv.headers || {});
2158
- __staleHeaders["X-Vinext-Cache"] = "STALE";
2159
- __staleHeaders["Cache-Control"] = "s-maxage=0, stale-while-revalidate";
2160
- if (isAutoHead) {
2161
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: __sv.status, headers: __staleHeaders }));
2162
- }
2163
- return attachRouteHandlerMiddlewareContext(new Response(__sv.body, { status: __sv.status, headers: __staleHeaders }));
2164
- }
2165
- } catch (__routeCacheErr) {
2166
- // Cache read failure — fall through to normal handler execution
2167
- console.error("[vinext] ISR route cache read error:", __routeCacheErr);
2119
+ await _runWithUnifiedCtx(__revalUCtx, async () => {
2120
+ _ensureFetchPatch();
2121
+ await renderFn();
2122
+ });
2123
+ },
2124
+ scheduleBackgroundRegeneration: __triggerBackgroundRegeneration,
2125
+ setNavigationContext,
2126
+ });
2127
+ if (__cachedRouteResponse) {
2128
+ return __cachedRouteResponse;
2168
2129
  }
2169
2130
  }
2170
2131
 
2171
2132
  if (typeof handlerFn === "function") {
2172
- const previousHeadersPhase = setHeadersAccessPhase("route-handler");
2173
- try {
2174
- const response = await handlerFn(request, { params });
2175
- const dynamicUsedInHandler = consumeDynamicUsage();
2176
- const handlerSetCacheControl = response.headers.has("cache-control");
2177
-
2178
- // Apply Cache-Control from route segment config (export const revalidate = N).
2179
- // Runtime request APIs like headers() / cookies() make GET handlers dynamic,
2180
- // so only attach ISR headers when the handler stayed static.
2181
- if (
2182
- revalidateSeconds !== null &&
2183
- !dynamicUsedInHandler &&
2184
- (method === "GET" || isAutoHead) &&
2185
- !handlerSetCacheControl
2186
- ) {
2187
- response.headers.set("cache-control", "s-maxage=" + revalidateSeconds + ", stale-while-revalidate");
2188
- }
2189
-
2190
- // ISR cache write for route handlers (production, MISS).
2191
- // Store the raw handler response before cookie/middleware transforms
2192
- // (those are request-specific and shouldn't be cached).
2193
- if (
2194
- process.env.NODE_ENV === "production" &&
2195
- revalidateSeconds !== null &&
2196
- handler.dynamic !== "force-dynamic" &&
2197
- !dynamicUsedInHandler &&
2198
- (method === "GET" || isAutoHead) &&
2199
- !handlerSetCacheControl
2200
- ) {
2201
- response.headers.set("X-Vinext-Cache", "MISS");
2202
- const __routeClone = response.clone();
2203
- const __routeKey = __isrRouteKey(cleanPathname);
2204
- const __revalSecs = revalidateSeconds;
2205
- const __routeTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2206
- const __routeWritePromise = (async () => {
2207
- try {
2208
- const __buf = await __routeClone.arrayBuffer();
2209
- const __hdrs = {};
2210
- __routeClone.headers.forEach(function(v, k) {
2211
- if (k !== "x-vinext-cache" && k !== "cache-control") __hdrs[k] = v;
2212
- });
2213
- await __isrSet(__routeKey, { kind: "APP_ROUTE", body: __buf, status: __routeClone.status, headers: __hdrs }, __revalSecs, __routeTags);
2214
- __isrDebug?.("route cache written", __routeKey);
2215
- } catch (__cacheErr) {
2216
- console.error("[vinext] ISR route cache write error:", __cacheErr);
2217
- }
2218
- })();
2219
- _getRequestExecutionContext()?.waitUntil(__routeWritePromise);
2220
- }
2221
-
2222
- // Collect any Set-Cookie headers from cookies().set()/delete() calls
2223
- const pendingCookies = getAndClearPendingCookies();
2224
- const draftCookie = getDraftModeCookieHeader();
2225
- setHeadersContext(null);
2226
- setNavigationContext(null);
2227
-
2228
- // If we have pending cookies, create a new response with them attached
2229
- if (pendingCookies.length > 0 || draftCookie) {
2230
- const newHeaders = new Headers(response.headers);
2231
- for (const cookie of pendingCookies) {
2232
- newHeaders.append("Set-Cookie", cookie);
2233
- }
2234
- if (draftCookie) newHeaders.append("Set-Cookie", draftCookie);
2235
-
2236
- if (isAutoHead) {
2237
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2238
- status: response.status,
2239
- statusText: response.statusText,
2240
- headers: newHeaders,
2241
- }));
2242
- }
2243
- return attachRouteHandlerMiddlewareContext(new Response(response.body, {
2244
- status: response.status,
2245
- statusText: response.statusText,
2246
- headers: newHeaders,
2247
- }));
2248
- }
2249
-
2250
- if (isAutoHead) {
2251
- // Strip body for auto-HEAD, preserve headers and status
2252
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2253
- status: response.status,
2254
- statusText: response.statusText,
2255
- headers: response.headers,
2256
- }));
2257
- }
2258
- return attachRouteHandlerMiddlewareContext(response);
2259
- } catch (err) {
2260
- getAndClearPendingCookies(); // Clear any pending cookies on error
2261
- // Catch redirect() / notFound() thrown from route handlers
2262
- if (err && typeof err === "object" && "digest" in err) {
2263
- const digest = String(err.digest);
2264
- if (digest.startsWith("NEXT_REDIRECT;")) {
2265
- const parts = digest.split(";");
2266
- const redirectUrl = decodeURIComponent(parts[2]);
2267
- const statusCode = parts[3] ? parseInt(parts[3], 10) : 307;
2268
- setHeadersContext(null);
2269
- setNavigationContext(null);
2270
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2271
- status: statusCode,
2272
- headers: { Location: new URL(redirectUrl, request.url).toString() },
2273
- }));
2274
- }
2275
- if (digest === "NEXT_NOT_FOUND" || digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")) {
2276
- const statusCode = digest === "NEXT_NOT_FOUND" ? 404 : parseInt(digest.split(";")[1], 10);
2277
- setHeadersContext(null);
2278
- setNavigationContext(null);
2279
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: statusCode }));
2280
- }
2281
- }
2282
- setHeadersContext(null);
2283
- setNavigationContext(null);
2284
- console.error("[vinext] Route handler error:", err);
2285
- _reportRequestError(
2286
- err instanceof Error ? err : new Error(String(err)),
2287
- { path: cleanPathname, method: request.method, headers: Object.fromEntries(request.headers.entries()) },
2288
- { routerKind: "App Router", routePath: route.pattern, routeType: "route" },
2289
- );
2290
- return attachRouteHandlerMiddlewareContext(new Response(null, { status: 500 }));
2291
- } finally {
2292
- setHeadersAccessPhase(previousHeadersPhase);
2293
- }
2133
+ return __executeAppRouteHandler({
2134
+ basePath: __basePath,
2135
+ buildPageCacheTags: __pageCacheTags,
2136
+ cleanPathname,
2137
+ clearRequestContext: function() {
2138
+ setHeadersContext(null);
2139
+ setNavigationContext(null);
2140
+ },
2141
+ consumeDynamicUsage,
2142
+ executionContext: _getRequestExecutionContext(),
2143
+ getAndClearPendingCookies,
2144
+ getCollectedFetchTags,
2145
+ getDraftModeCookieHeader,
2146
+ handler,
2147
+ handlerFn,
2148
+ i18n: __i18nConfig,
2149
+ isAutoHead,
2150
+ isProduction: process.env.NODE_ENV === "production",
2151
+ isrDebug: __isrDebug,
2152
+ isrRouteKey: __isrRouteKey,
2153
+ isrSet: __isrSet,
2154
+ markDynamicUsage,
2155
+ method,
2156
+ middlewareContext: _mwCtx,
2157
+ params,
2158
+ reportRequestError: _reportRequestError,
2159
+ request,
2160
+ revalidateSeconds,
2161
+ routePattern: route.pattern,
2162
+ setHeadersAccessPhase,
2163
+ });
2294
2164
  }
2295
2165
  setHeadersContext(null);
2296
2166
  setNavigationContext(null);
2297
- return attachRouteHandlerMiddlewareContext(new Response(null, {
2298
- status: 405,
2299
- }));
2167
+ return __applyRouteHandlerMiddlewareContext(
2168
+ new Response(null, {
2169
+ status: 405,
2170
+ }),
2171
+ _mwCtx,
2172
+ );
2300
2173
  }
2301
2174
 
2302
2175
  // Build the component tree: layouts wrapping the page
@@ -2366,147 +2239,78 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2366
2239
  !isForceDynamic &&
2367
2240
  revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity
2368
2241
  ) {
2369
- const __isrKey = isRscRequest ? __isrRscKey(cleanPathname) : __isrHtmlKey(cleanPathname);
2370
- try {
2371
- const __cached = await __isrGet(__isrKey);
2372
- if (__cached && !__cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_PAGE") {
2373
- const __cachedValue = __cached.value.value;
2374
- const __hasRsc = !!__cachedValue.rscData;
2375
- const __hasHtml = typeof __cachedValue.html === "string" && __cachedValue.html.length > 0;
2376
- if (isRscRequest && __hasRsc) {
2377
- __isrDebug?.("HIT (RSC)", cleanPathname);
2378
- setHeadersContext(null);
2379
- setNavigationContext(null);
2380
- return new Response(__cachedValue.rscData, {
2381
- status: __cachedValue.status || 200,
2382
- headers: {
2383
- "Content-Type": "text/x-component; charset=utf-8",
2384
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
2385
- "Vary": "RSC, Accept",
2386
- "X-Vinext-Cache": "HIT",
2387
- },
2388
- });
2389
- }
2390
- if (!isRscRequest && __hasHtml) {
2391
- __isrDebug?.("HIT (HTML)", cleanPathname);
2392
- setHeadersContext(null);
2393
- setNavigationContext(null);
2394
- return new Response(__cachedValue.html, {
2395
- status: __cachedValue.status || 200,
2396
- headers: {
2397
- "Content-Type": "text/html; charset=utf-8",
2398
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
2399
- "Vary": "RSC, Accept",
2400
- "X-Vinext-Cache": "HIT",
2401
- },
2402
- });
2403
- }
2404
- __isrDebug?.("MISS (empty cached entry)", cleanPathname);
2405
- }
2406
- if (__cached && __cached.isStale && __cached.value.value && __cached.value.value.kind === "APP_PAGE") {
2407
- // Stale cache hit — serve stale immediately, trigger background regeneration.
2408
- // Regen writes both keys independently so neither path blocks on the other.
2409
- const __staleValue = __cached.value.value;
2410
- const __staleStatus = __staleValue.status || 200;
2411
- const __revalSecs = revalidateSeconds;
2412
- __triggerBackgroundRegeneration(cleanPathname, async function() {
2413
- // Re-render the page to produce fresh HTML + RSC data for the cache
2414
- // Use an empty headers context for background regeneration — not the original
2415
- // user request — to prevent user-specific cookies/auth headers from leaking
2416
- // into content that is cached and served to all subsequent users.
2417
- const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2418
- const __revalUCtx = _createUnifiedCtx({
2419
- headersContext: __revalHeadCtx,
2420
- executionContext: _getRequestExecutionContext(),
2421
- });
2422
- const __revalResult = await _runWithUnifiedCtx(__revalUCtx, async () => {
2423
- _ensureFetchPatch();
2424
- setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params });
2425
- const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams());
2426
- const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2427
- const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2428
- // Tee RSC stream: one for SSR, one to capture rscData
2429
- const [__revalRscForSsr, __revalRscForCapture] = __revalRscStream.tee();
2430
- // Capture rscData bytes in parallel with SSR
2431
- const __rscDataPromise = (async () => {
2432
- const __rscReader = __revalRscForCapture.getReader();
2433
- const __rscChunks = [];
2434
- let __rscTotal = 0;
2435
- for (;;) {
2436
- const { done, value } = await __rscReader.read();
2437
- if (done) break;
2438
- __rscChunks.push(value);
2439
- __rscTotal += value.byteLength;
2440
- }
2441
- const __rscBuf = new Uint8Array(__rscTotal);
2442
- let __rscOff = 0;
2443
- for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2444
- return __rscBuf.buffer;
2445
- })();
2446
- const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2447
- const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2448
- const __revalHtmlStream = await __revalSsrEntry.handleSsr(__revalRscForSsr, _getNavigationContext(), __revalFontData);
2449
- setHeadersContext(null);
2450
- setNavigationContext(null);
2451
- // Collect the full HTML string from the stream
2452
- const __revalReader = __revalHtmlStream.getReader();
2453
- const __revalDecoder = new TextDecoder();
2454
- const __revalChunks = [];
2242
+ const __cachedPageResponse = await __readAppPageCacheResponse({
2243
+ cleanPathname,
2244
+ clearRequestContext: function() {
2245
+ setHeadersContext(null);
2246
+ setNavigationContext(null);
2247
+ },
2248
+ isRscRequest,
2249
+ isrDebug: __isrDebug,
2250
+ isrGet: __isrGet,
2251
+ isrHtmlKey: __isrHtmlKey,
2252
+ isrRscKey: __isrRscKey,
2253
+ isrSet: __isrSet,
2254
+ revalidateSeconds,
2255
+ renderFreshPageForCache: async function() {
2256
+ // Re-render the page to produce fresh HTML + RSC data for the cache
2257
+ // Use an empty headers context for background regeneration — not the original
2258
+ // user request — to prevent user-specific cookies/auth headers from leaking
2259
+ // into content that is cached and served to all subsequent users.
2260
+ const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2261
+ const __revalUCtx = _createUnifiedCtx({
2262
+ headersContext: __revalHeadCtx,
2263
+ executionContext: _getRequestExecutionContext(),
2264
+ });
2265
+ return _runWithUnifiedCtx(__revalUCtx, async () => {
2266
+ _ensureFetchPatch();
2267
+ setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params });
2268
+ const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams());
2269
+ const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2270
+ const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2271
+ // Tee RSC stream: one for SSR, one to capture rscData
2272
+ const [__revalRscForSsr, __revalRscForCapture] = __revalRscStream.tee();
2273
+ // Capture rscData bytes in parallel with SSR
2274
+ const __rscDataPromise = (async () => {
2275
+ const __rscReader = __revalRscForCapture.getReader();
2276
+ const __rscChunks = [];
2277
+ let __rscTotal = 0;
2455
2278
  for (;;) {
2456
- const { done, value } = await __revalReader.read();
2279
+ const { done, value } = await __rscReader.read();
2457
2280
  if (done) break;
2458
- __revalChunks.push(__revalDecoder.decode(value, { stream: true }));
2281
+ __rscChunks.push(value);
2282
+ __rscTotal += value.byteLength;
2459
2283
  }
2460
- __revalChunks.push(__revalDecoder.decode());
2461
- const __freshHtml = __revalChunks.join("");
2462
- const __freshRscData = await __rscDataPromise;
2463
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2464
- return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2465
- });
2466
- // Write HTML and RSC to their own keys independently — no races
2467
- await Promise.all([
2468
- __isrSet(__isrHtmlKey(cleanPathname), { kind: "APP_PAGE", html: __revalResult.html, rscData: undefined, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __revalResult.tags),
2469
- __isrSet(__isrRscKey(cleanPathname), { kind: "APP_PAGE", html: "", rscData: __revalResult.rscData, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __revalResult.tags),
2470
- ]);
2471
- __isrDebug?.("regen complete", cleanPathname);
2472
- });
2473
- if (isRscRequest && __staleValue.rscData) {
2474
- __isrDebug?.("STALE (RSC)", cleanPathname);
2475
- setHeadersContext(null);
2476
- setNavigationContext(null);
2477
- return new Response(__staleValue.rscData, {
2478
- status: __staleStatus,
2479
- headers: {
2480
- "Content-Type": "text/x-component; charset=utf-8",
2481
- "Cache-Control": "s-maxage=0, stale-while-revalidate",
2482
- "Vary": "RSC, Accept",
2483
- "X-Vinext-Cache": "STALE",
2484
- },
2485
- });
2486
- }
2487
- if (!isRscRequest && typeof __staleValue.html === "string" && __staleValue.html.length > 0) {
2488
- __isrDebug?.("STALE (HTML)", cleanPathname);
2284
+ const __rscBuf = new Uint8Array(__rscTotal);
2285
+ let __rscOff = 0;
2286
+ for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2287
+ return __rscBuf.buffer;
2288
+ })();
2289
+ const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2290
+ const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2291
+ const __revalHtmlStream = await __revalSsrEntry.handleSsr(__revalRscForSsr, _getNavigationContext(), __revalFontData);
2489
2292
  setHeadersContext(null);
2490
2293
  setNavigationContext(null);
2491
- return new Response(__staleValue.html, {
2492
- status: __staleStatus,
2493
- headers: {
2494
- "Content-Type": "text/html; charset=utf-8",
2495
- "Cache-Control": "s-maxage=0, stale-while-revalidate",
2496
- "Vary": "RSC, Accept",
2497
- "X-Vinext-Cache": "STALE",
2498
- },
2499
- });
2500
- }
2501
- // Stale entry exists but is empty for this request type — fall through to render
2502
- __isrDebug?.("STALE MISS (empty stale entry)", cleanPathname);
2503
- }
2504
- if (!__cached) {
2505
- __isrDebug?.("MISS (no cache entry)", cleanPathname);
2506
- }
2507
- } catch (__isrReadErr) {
2508
- // Cache read failure — fall through to normal rendering
2509
- console.error("[vinext] ISR cache read error:", __isrReadErr);
2294
+ // Collect the full HTML string from the stream
2295
+ const __revalReader = __revalHtmlStream.getReader();
2296
+ const __revalDecoder = new TextDecoder();
2297
+ const __revalChunks = [];
2298
+ for (;;) {
2299
+ const { done, value } = await __revalReader.read();
2300
+ if (done) break;
2301
+ __revalChunks.push(__revalDecoder.decode(value, { stream: true }));
2302
+ }
2303
+ __revalChunks.push(__revalDecoder.decode());
2304
+ const __freshHtml = __revalChunks.join("");
2305
+ const __freshRscData = await __rscDataPromise;
2306
+ const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2307
+ return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2308
+ });
2309
+ },
2310
+ scheduleBackgroundRegeneration: __triggerBackgroundRegeneration,
2311
+ });
2312
+ if (__cachedPageResponse) {
2313
+ return __cachedPageResponse;
2510
2314
  }
2511
2315
  }
2512
2316
 
@@ -2807,88 +2611,47 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2807
2611
  // The RSC stream is consumed lazily - components render when chunks are read.
2808
2612
  // If we clear context now, headers()/cookies() will fail during rendering.
2809
2613
  // Context will be cleared when the next request starts (via runWithRequestContext).
2810
- const responseHeaders = { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" };
2811
- // Include matched route params so the client can hydrate useParams()
2812
- if (params && Object.keys(params).length > 0) {
2813
- responseHeaders["X-Vinext-Params"] = JSON.stringify(params);
2814
- }
2815
- if (isForceDynamic) {
2816
- responseHeaders["Cache-Control"] = "no-store, must-revalidate";
2817
- } else if ((isForceStatic || isDynamicError) && !revalidateSeconds) {
2818
- responseHeaders["Cache-Control"] = "s-maxage=31536000, stale-while-revalidate";
2819
- responseHeaders["X-Vinext-Cache"] = "STATIC";
2820
- } else if (revalidateSeconds === Infinity) {
2821
- responseHeaders["Cache-Control"] = "s-maxage=31536000, stale-while-revalidate";
2822
- responseHeaders["X-Vinext-Cache"] = "STATIC";
2823
- } else if (revalidateSeconds) {
2824
- responseHeaders["Cache-Control"] = "s-maxage=" + revalidateSeconds + ", stale-while-revalidate";
2825
- }
2826
- // Merge middleware response headers into the RSC response.
2827
- // set-cookie and vary are accumulated to preserve existing values
2828
- // (e.g. "Vary: RSC, Accept" set above); all other keys use plain
2829
- // assignment so middleware headers win over config headers, which
2830
- // the outer handler applies afterward and skips keys already present.
2831
- if (_mwCtx.headers) {
2832
- for (const [key, value] of _mwCtx.headers) {
2833
- const lk = key.toLowerCase();
2834
- if (lk === "set-cookie") {
2835
- const existing = responseHeaders[lk];
2836
- if (Array.isArray(existing)) {
2837
- existing.push(value);
2838
- } else if (existing) {
2839
- responseHeaders[lk] = [existing, value];
2840
- } else {
2841
- responseHeaders[lk] = [value];
2842
- }
2843
- } else if (lk === "vary") {
2844
- // Accumulate Vary values to preserve the existing "RSC, Accept" entry.
2845
- const existing = responseHeaders["Vary"] ?? responseHeaders["vary"];
2846
- if (existing) {
2847
- responseHeaders["Vary"] = existing + ", " + value;
2848
- if (responseHeaders["vary"] !== undefined) delete responseHeaders["vary"];
2849
- } else {
2850
- responseHeaders[key] = value;
2614
+ const __dynamicUsedInRsc = consumeDynamicUsage();
2615
+ const __rscResponsePolicy = __resolveAppPageRscResponsePolicy({
2616
+ dynamicUsedDuringBuild: __dynamicUsedInRsc,
2617
+ isDynamicError,
2618
+ isForceDynamic,
2619
+ isForceStatic,
2620
+ isProduction: process.env.NODE_ENV === "production",
2621
+ revalidateSeconds,
2622
+ });
2623
+ const __rscResponse = __buildAppPageRscResponse(__rscForResponse, {
2624
+ middlewareContext: _mwCtx,
2625
+ params,
2626
+ policy: __rscResponsePolicy,
2627
+ timing: process.env.NODE_ENV !== "production"
2628
+ ? {
2629
+ compileEnd: __compileEnd,
2630
+ handlerStart: __reqStart,
2631
+ responseKind: "rsc",
2851
2632
  }
2852
- } else {
2853
- responseHeaders[key] = value;
2854
- }
2855
- }
2856
- }
2857
- // Attach internal timing header so the dev server middleware can log it.
2858
- // Format: "handlerStart,compileMs,renderMs"
2859
- // handlerStart - absolute performance.now() when _handleRequest began,
2860
- // used by the logging middleware to compute true compile
2861
- // time as (handlerStart - middlewareReqStart).
2862
- // compileMs - time inside the handler before renderToReadableStream.
2863
- // -1 sentinel means compile time is not measured.
2864
- // renderMs - -1 sentinel for RSC-only (soft-nav) responses, since
2865
- // rendering is handled asynchronously by the client. The
2866
- // logging middleware computes render time as totalMs - compileMs.
2867
- if (process.env.NODE_ENV !== "production") {
2868
- const handlerStart = Math.round(__reqStart);
2869
- const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
2870
- responseHeaders["x-vinext-timing"] = handlerStart + "," + compileMs + ",-1";
2871
- }
2633
+ : undefined,
2634
+ });
2872
2635
  // For ISR-eligible RSC requests in production: write rscData to its own key.
2873
2636
  // HTML is stored under a separate key (written by the HTML path below) so
2874
2637
  // these writes never race or clobber each other.
2875
- if (process.env.NODE_ENV === "production" && __isrRscDataPromise) {
2876
- responseHeaders["X-Vinext-Cache"] = "MISS";
2877
- const __isrKeyRsc = __isrRscKey(cleanPathname);
2878
- const __revalSecsRsc = revalidateSeconds;
2879
- const __rscWritePromise = (async () => {
2880
- try {
2881
- const __rscDataForCache = await __isrRscDataPromise;
2882
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2883
- await __isrSet(__isrKeyRsc, { kind: "APP_PAGE", html: "", rscData: __rscDataForCache, headers: undefined, postponed: undefined, status: 200 }, __revalSecsRsc, __pageTags);
2884
- __isrDebug?.("RSC cache written", __isrKeyRsc);
2885
- } catch (__rscWriteErr) {
2886
- console.error("[vinext] ISR RSC cache write error:", __rscWriteErr);
2887
- }
2888
- })();
2889
- _getRequestExecutionContext()?.waitUntil(__rscWritePromise);
2890
- }
2891
- return new Response(__rscForResponse, { status: _mwCtx.status || 200, headers: responseHeaders });
2638
+ __scheduleAppPageRscCacheWrite({
2639
+ capturedRscDataPromise: process.env.NODE_ENV === "production" ? __isrRscDataPromise : null,
2640
+ cleanPathname,
2641
+ consumeDynamicUsage,
2642
+ dynamicUsedDuringBuild: __dynamicUsedInRsc,
2643
+ getPageTags() {
2644
+ return __pageCacheTags(cleanPathname, getCollectedFetchTags());
2645
+ },
2646
+ isrDebug: __isrDebug,
2647
+ isrRscKey: __isrRscKey,
2648
+ isrSet: __isrSet,
2649
+ revalidateSeconds,
2650
+ waitUntil(promise) {
2651
+ _getRequestExecutionContext()?.waitUntil(promise);
2652
+ },
2653
+ });
2654
+ return __rscResponse;
2892
2655
  }
2893
2656
 
2894
2657
  // Collect font data from RSC environment before passing to SSR
@@ -2950,51 +2713,6 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2950
2713
  setHeadersContext(null);
2951
2714
  setNavigationContext(null);
2952
2715
 
2953
- // Helper to attach draftMode cookie, middleware headers, font Link header, and rewrite status to a response
2954
- function attachMiddlewareContext(response) {
2955
- if (draftCookie) {
2956
- response.headers.append("Set-Cookie", draftCookie);
2957
- }
2958
- // Set HTTP Link header for font preloading
2959
- if (fontLinkHeader) {
2960
- response.headers.set("Link", fontLinkHeader);
2961
- }
2962
- // Merge middleware response headers into the final response.
2963
- // The response is freshly constructed above (new Response(htmlStream, {...})),
2964
- // so set() and append() are equivalent — there are no same-key conflicts yet.
2965
- // Precedence over config headers is handled by the outer handler, which
2966
- // skips config keys that middleware already placed on the response.
2967
- if (_mwCtx.headers) {
2968
- for (const [key, value] of _mwCtx.headers) {
2969
- response.headers.append(key, value);
2970
- }
2971
- }
2972
- // Attach internal timing header so the dev server middleware can log it.
2973
- // Format: "handlerStart,compileMs,renderMs"
2974
- // handlerStart - absolute performance.now() when _handleRequest began,
2975
- // used by the logging middleware to compute true compile
2976
- // time as (handlerStart - middlewareReqStart).
2977
- // compileMs - time inside the handler before renderToReadableStream.
2978
- // renderMs - time from renderToReadableStream to handleSsr completion,
2979
- // or -1 sentinel if not measured (falls back to totalMs - compileMs).
2980
- if (process.env.NODE_ENV !== "production") {
2981
- const handlerStart = Math.round(__reqStart);
2982
- const compileMs = __compileEnd !== undefined ? Math.round(__compileEnd - __reqStart) : -1;
2983
- const renderMs = __renderEnd !== undefined && __compileEnd !== undefined
2984
- ? Math.round(__renderEnd - __compileEnd)
2985
- : -1;
2986
- response.headers.set("x-vinext-timing", handlerStart + "," + compileMs + "," + renderMs);
2987
- }
2988
- // Apply custom status code from middleware rewrite
2989
- if (_mwCtx.status) {
2990
- return new Response(response.body, {
2991
- status: _mwCtx.status,
2992
- headers: response.headers,
2993
- });
2994
- }
2995
- return response;
2996
- }
2997
-
2998
2716
  // Check if any component called connection(), cookies(), headers(), or noStore()
2999
2717
  // during rendering. If so, treat as dynamic (skip ISR, set no-store).
3000
2718
  const dynamicUsedDuringRender = consumeDynamicUsage();
@@ -3005,137 +2723,60 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
3005
2723
  if (requestCacheLife && requestCacheLife.revalidate !== undefined && revalidateSeconds === null) {
3006
2724
  revalidateSeconds = requestCacheLife.revalidate;
3007
2725
  }
3008
-
3009
- // force-dynamic: always return no-store (highest priority)
3010
- if (isForceDynamic) {
3011
- return attachMiddlewareContext(new Response(htmlStream, {
3012
- headers: {
3013
- "Content-Type": "text/html; charset=utf-8",
3014
- "Cache-Control": "no-store, must-revalidate",
3015
- "Vary": "RSC, Accept",
3016
- },
3017
- }));
3018
- }
3019
-
3020
- // force-static / error: treat as static regardless of dynamic usage.
3021
- // force-static intentionally provides empty headers/cookies context so
3022
- // dynamic APIs return safe defaults; we ignore the dynamic usage signal.
3023
- // dynamic='error' should have already thrown via the request API accessError
3024
- // trap if user code touched a dynamic API, so reaching here means rendering succeeded.
3025
- if ((isForceStatic || isDynamicError) && (revalidateSeconds === null || revalidateSeconds === 0)) {
3026
- return attachMiddlewareContext(new Response(htmlStream, {
3027
- headers: {
3028
- "Content-Type": "text/html; charset=utf-8",
3029
- "Cache-Control": "s-maxage=31536000, stale-while-revalidate",
3030
- "X-Vinext-Cache": "STATIC",
3031
- "Vary": "RSC, Accept",
3032
- },
3033
- }));
3034
- }
3035
-
3036
- // auto mode: dynamic API usage (headers(), cookies(), connection(), noStore(),
3037
- // searchParams access) opts the page into dynamic rendering with no-store.
3038
- if (dynamicUsedDuringRender) {
3039
- return attachMiddlewareContext(new Response(htmlStream, {
3040
- headers: {
3041
- "Content-Type": "text/html; charset=utf-8",
3042
- "Cache-Control": "no-store, must-revalidate",
3043
- "Vary": "RSC, Accept",
3044
- },
3045
- }));
3046
- }
2726
+ const __htmlResponsePolicy = __resolveAppPageHtmlResponsePolicy({
2727
+ dynamicUsedDuringRender,
2728
+ isDynamicError,
2729
+ isForceDynamic,
2730
+ isForceStatic,
2731
+ isProduction: process.env.NODE_ENV === "production",
2732
+ revalidateSeconds,
2733
+ });
2734
+ const __htmlResponseTiming = process.env.NODE_ENV !== "production"
2735
+ ? {
2736
+ compileEnd: __compileEnd,
2737
+ handlerStart: __reqStart,
2738
+ renderEnd: __renderEnd,
2739
+ responseKind: "html",
2740
+ }
2741
+ : undefined;
3047
2742
 
3048
2743
  // Emit Cache-Control for ISR pages and write to ISR cache on MISS (production only).
3049
2744
  // revalidate=Infinity means "cache forever" (no periodic revalidation) — treated as
3050
2745
  // static here so we emit s-maxage=31536000 but skip ISR cache management.
3051
- if (revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity) {
3052
- // In production, tee the HTML response body to simultaneously stream to the
3053
- // client and collect the full HTML string for the ISR cache. rscData was
3054
- // already captured above by teeing the RSC stream before SSR.
3055
- // In dev, skip the tee and the X-Vinext-Cache header — every request renders
3056
- // fresh (no cache reads or writes in dev mode).
3057
- if (process.env.NODE_ENV === "production") {
3058
- const __isrResponseProd = attachMiddlewareContext(new Response(htmlStream, {
3059
- headers: {
3060
- "Content-Type": "text/html; charset=utf-8",
3061
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
3062
- "Vary": "RSC, Accept",
3063
- "X-Vinext-Cache": "MISS",
3064
- },
3065
- }));
3066
- if (__isrResponseProd.body) {
3067
- const [__streamForClient, __streamForCache] = __isrResponseProd.body.tee();
3068
- const __isrKey = __isrHtmlKey(cleanPathname);
3069
- const __isrKeyRscFromHtml = __isrRscKey(cleanPathname);
3070
- const __revalSecs = revalidateSeconds;
3071
- const __capturedRscDataPromise = __isrRscDataPromise;
3072
- const __cachePromise = (async () => {
3073
- try {
3074
- const __reader = __streamForCache.getReader();
3075
- const __decoder = new TextDecoder();
3076
- const __chunks = [];
3077
- for (;;) {
3078
- const { done, value } = await __reader.read();
3079
- if (done) break;
3080
- __chunks.push(__decoder.decode(value, { stream: true }));
3081
- }
3082
- __chunks.push(__decoder.decode());
3083
- const __fullHtml = __chunks.join("");
3084
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
3085
- // Write HTML and RSC to their own keys independently.
3086
- // RSC data was captured by the tee above (before isRscRequest branch)
3087
- // so an initial browser visit (HTML request) also populates the RSC key,
3088
- // ensuring the first client-side navigation after a direct visit is a
3089
- // cache hit rather than a miss.
3090
- const __writes = [
3091
- __isrSet(__isrKey, { kind: "APP_PAGE", html: __fullHtml, rscData: undefined, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __pageTags),
3092
- ];
3093
- if (__capturedRscDataPromise) {
3094
- __writes.push(
3095
- __capturedRscDataPromise.then((__rscBuf) =>
3096
- __isrSet(__isrKeyRscFromHtml, { kind: "APP_PAGE", html: "", rscData: __rscBuf, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __pageTags)
3097
- )
3098
- );
3099
- }
3100
- await Promise.all(__writes);
3101
- __isrDebug?.("HTML cache written", __isrKey);
3102
- } catch (__cacheErr) {
3103
- console.error("[vinext] ISR cache write error:", __cacheErr);
3104
- }
3105
- })();
2746
+ if (__htmlResponsePolicy.shouldWriteToCache) {
2747
+ const __isrResponseProd = __buildAppPageHtmlResponse(htmlStream, {
2748
+ draftCookie,
2749
+ fontLinkHeader,
2750
+ middlewareContext: _mwCtx,
2751
+ policy: __htmlResponsePolicy,
2752
+ timing: __htmlResponseTiming,
2753
+ });
2754
+ return __finalizeAppPageHtmlCacheResponse(__isrResponseProd, {
2755
+ capturedRscDataPromise: __isrRscDataPromise,
2756
+ cleanPathname,
2757
+ getPageTags: function() {
2758
+ return __pageCacheTags(cleanPathname, getCollectedFetchTags());
2759
+ },
2760
+ isrDebug: __isrDebug,
2761
+ isrHtmlKey: __isrHtmlKey,
2762
+ isrRscKey: __isrRscKey,
2763
+ isrSet: __isrSet,
2764
+ revalidateSeconds,
2765
+ waitUntil: function(__cachePromise) {
3106
2766
  // Register with ExecutionContext (from ALS) so the Workers runtime keeps
3107
2767
  // the isolate alive until the cache write finishes, even after the response is sent.
3108
2768
  _getRequestExecutionContext()?.waitUntil(__cachePromise);
3109
- return new Response(__streamForClient, { status: __isrResponseProd.status, headers: __isrResponseProd.headers });
3110
- }
3111
- return __isrResponseProd;
3112
- }
3113
- // Dev mode: return Cache-Control header but no X-Vinext-Cache (no cache read/write)
3114
- return attachMiddlewareContext(new Response(htmlStream, {
3115
- headers: {
3116
- "Content-Type": "text/html; charset=utf-8",
3117
- "Cache-Control": "s-maxage=" + revalidateSeconds + ", stale-while-revalidate",
3118
- "Vary": "RSC, Accept",
3119
- },
3120
- }));
3121
- }
3122
-
3123
- // revalidate=Infinity (or false, which Next.js normalises to false/0): treat as
3124
- // permanent static — emit the longest safe s-maxage but skip ISR cache management.
3125
- if (revalidateSeconds === Infinity) {
3126
- return attachMiddlewareContext(new Response(htmlStream, {
3127
- headers: {
3128
- "Content-Type": "text/html; charset=utf-8",
3129
- "Cache-Control": "s-maxage=31536000, stale-while-revalidate",
3130
- "X-Vinext-Cache": "STATIC",
3131
- "Vary": "RSC, Accept",
3132
2769
  },
3133
- }));
2770
+ });
3134
2771
  }
3135
2772
 
3136
- return attachMiddlewareContext(new Response(htmlStream, {
3137
- headers: { "Content-Type": "text/html; charset=utf-8", "Vary": "RSC, Accept" },
3138
- }));
2773
+ return __buildAppPageHtmlResponse(htmlStream, {
2774
+ draftCookie,
2775
+ fontLinkHeader,
2776
+ middlewareContext: _mwCtx,
2777
+ policy: __htmlResponsePolicy,
2778
+ timing: __htmlResponseTiming,
2779
+ });
3139
2780
  }
3140
2781
 
3141
2782
  if (import.meta.hot) {