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