vinext 0.1.3 → 0.1.5

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 (185) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/css-url-assets.d.ts +1 -1
  4. package/dist/build/css-url-assets.js +9 -7
  5. package/dist/build/prerender.js +3 -1
  6. package/dist/cache/cache-adapters-virtual.js +1 -1
  7. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  8. package/dist/client/pages-router-link-navigation.js +32 -2
  9. package/dist/client/vinext-next-data.js +2 -0
  10. package/dist/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +1 -1
  11. package/dist/config/config-matchers.d.ts +11 -1
  12. package/dist/config/config-matchers.js +14 -2
  13. package/dist/config/tsconfig-paths.js +14 -1
  14. package/dist/deploy.js +20 -13
  15. package/dist/entries/app-rsc-entry.js +27 -22
  16. package/dist/entries/pages-client-entry.js +14 -13
  17. package/dist/entries/pages-server-entry.js +8 -27
  18. package/dist/index.js +365 -147
  19. package/dist/plugins/css-data-url.js +30 -26
  20. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  21. package/dist/plugins/extensionless-dynamic-import.js +27 -24
  22. package/dist/plugins/fonts.js +5 -4
  23. package/dist/plugins/import-meta-url.js +21 -15
  24. package/dist/plugins/instrumentation-client.js +1 -1
  25. package/dist/plugins/middleware-server-only.js +7 -6
  26. package/dist/plugins/og-assets.js +48 -46
  27. package/dist/plugins/optimize-imports.js +9 -3
  28. package/dist/plugins/remove-console.d.ts +7 -1
  29. package/dist/plugins/remove-console.js +4 -1
  30. package/dist/plugins/require-context.js +21 -20
  31. package/dist/plugins/strip-server-exports.d.ts +16 -8
  32. package/dist/plugins/strip-server-exports.js +496 -46
  33. package/dist/routing/app-route-graph.js +2 -2
  34. package/dist/server/app-bfcache-identity.d.ts +26 -0
  35. package/dist/server/app-bfcache-identity.js +127 -0
  36. package/dist/server/app-browser-action-result.js +1 -1
  37. package/dist/server/app-browser-entry.js +22 -12
  38. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  39. package/dist/server/app-browser-navigation-controller.js +1 -1
  40. package/dist/server/app-browser-state.d.ts +3 -22
  41. package/dist/server/app-browser-state.js +23 -139
  42. package/dist/server/app-browser-stream.js +1 -1
  43. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  44. package/dist/server/app-browser-visible-commit.js +3 -2
  45. package/dist/server/app-fallback-renderer.d.ts +1 -1
  46. package/dist/server/app-layout-param-observation.d.ts +1 -1
  47. package/dist/server/app-layout-param-observation.js +1 -1
  48. package/dist/server/app-middleware.js +2 -1
  49. package/dist/server/app-page-boundary-render.d.ts +1 -1
  50. package/dist/server/app-page-boundary.js +1 -1
  51. package/dist/server/app-page-cache-finalizer.d.ts +62 -0
  52. package/dist/server/app-page-cache-finalizer.js +122 -0
  53. package/dist/server/app-page-cache-render.d.ts +2 -2
  54. package/dist/server/app-page-cache-render.js +1 -1
  55. package/dist/server/app-page-cache.d.ts +2 -53
  56. package/dist/server/app-page-cache.js +5 -131
  57. package/dist/server/app-page-dispatch.d.ts +2 -2
  58. package/dist/server/app-page-dispatch.js +10 -8
  59. package/dist/server/app-page-probe.js +3 -2
  60. package/dist/server/app-page-render-observation.js +2 -2
  61. package/dist/server/app-page-render.d.ts +3 -3
  62. package/dist/server/app-page-render.js +3 -2
  63. package/dist/server/app-page-stream.d.ts +2 -9
  64. package/dist/server/app-page-stream.js +1 -35
  65. package/dist/server/app-pages-bridge.d.ts +5 -1
  66. package/dist/server/app-pages-bridge.js +5 -13
  67. package/dist/server/app-request-context.d.ts +1 -2
  68. package/dist/server/app-request-context.js +2 -1
  69. package/dist/server/app-route-handler-dispatch.js +3 -2
  70. package/dist/server/app-route-handler-execution.d.ts +1 -1
  71. package/dist/server/app-route-handler-execution.js +1 -1
  72. package/dist/server/app-route-handler-response.d.ts +1 -1
  73. package/dist/server/app-router-entry.js +2 -1
  74. package/dist/server/app-rsc-handler.d.ts +3 -0
  75. package/dist/server/app-rsc-handler.js +73 -31
  76. package/dist/server/app-rsc-response-finalizer.js +1 -1
  77. package/dist/server/app-rsc-route-matching.js +6 -2
  78. package/dist/server/app-server-action-execution.d.ts +1 -1
  79. package/dist/server/app-server-action-execution.js +10 -6
  80. package/dist/server/app-ssr-entry.d.ts +1 -1
  81. package/dist/server/app-ssr-entry.js +12 -38
  82. package/dist/server/app-ssr-router-instance.d.ts +6 -0
  83. package/dist/server/app-ssr-router-instance.js +24 -0
  84. package/dist/server/app-ssr-stream.js +1 -1
  85. package/dist/server/artifact-compatibility.js +1 -1
  86. package/dist/server/before-interactive-head.d.ts +17 -0
  87. package/dist/server/before-interactive-head.js +35 -0
  88. package/dist/server/client-reuse-manifest.js +1 -1
  89. package/dist/server/csp.js +1 -4
  90. package/dist/server/defer-until-stream-consumed.d.ts +7 -0
  91. package/dist/server/defer-until-stream-consumed.js +34 -0
  92. package/dist/server/dev-server.js +82 -37
  93. package/dist/server/instrumentation.js +1 -1
  94. package/dist/server/isr-cache.d.ts +1 -1
  95. package/dist/server/isr-cache.js +1 -1
  96. package/dist/server/isr-decision.d.ts +1 -1
  97. package/dist/server/middleware-matcher.js +20 -9
  98. package/dist/server/middleware-runtime.d.ts +3 -4
  99. package/dist/server/middleware-runtime.js +4 -2
  100. package/dist/server/navigation-planner.d.ts +3 -12
  101. package/dist/server/navigation-planner.js +24 -0
  102. package/dist/server/navigation-trace.d.ts +2 -1
  103. package/dist/server/navigation-trace.js +1 -0
  104. package/dist/server/open-redirect.d.ts +12 -0
  105. package/dist/server/open-redirect.js +21 -0
  106. package/dist/server/operation-token.d.ts +40 -0
  107. package/dist/server/operation-token.js +85 -0
  108. package/dist/server/pages-data-route.d.ts +1 -1
  109. package/dist/server/pages-data-route.js +7 -4
  110. package/dist/server/pages-dev-module-url.d.ts +4 -0
  111. package/dist/server/pages-dev-module-url.js +15 -0
  112. package/dist/server/pages-document-initial-props.d.ts +4 -15
  113. package/dist/server/pages-document-initial-props.js +27 -56
  114. package/dist/server/pages-i18n.js +2 -2
  115. package/dist/server/pages-page-data.d.ts +1 -1
  116. package/dist/server/pages-page-data.js +3 -1
  117. package/dist/server/pages-page-handler.js +3 -1
  118. package/dist/server/pages-page-response.d.ts +3 -1
  119. package/dist/server/pages-page-response.js +6 -6
  120. package/dist/server/pages-readiness.js +1 -1
  121. package/dist/server/pages-request-pipeline.d.ts +7 -7
  122. package/dist/server/pages-request-pipeline.js +63 -21
  123. package/dist/server/prod-server.d.ts +3 -1
  124. package/dist/server/prod-server.js +43 -11
  125. package/dist/server/request-pipeline.d.ts +1 -24
  126. package/dist/server/request-pipeline.js +1 -33
  127. package/dist/server/seed-cache.d.ts +1 -1
  128. package/dist/server/static-file-cache.js +16 -4
  129. package/dist/shims/before-interactive-context.d.ts +14 -3
  130. package/dist/shims/cache-handler.d.ts +106 -0
  131. package/dist/shims/cache-handler.js +176 -0
  132. package/dist/shims/cache-request-state.d.ts +47 -0
  133. package/dist/shims/cache-request-state.js +126 -0
  134. package/dist/shims/cache-runtime.d.ts +2 -2
  135. package/dist/shims/cache-runtime.js +3 -14
  136. package/dist/shims/cache.d.ts +3 -231
  137. package/dist/shims/cache.js +17 -383
  138. package/dist/shims/cdn-cache.d.ts +1 -1
  139. package/dist/shims/cdn-cache.js +1 -1
  140. package/dist/shims/document.d.ts +15 -20
  141. package/dist/shims/document.js +5 -8
  142. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  143. package/dist/shims/error-boundary-navigation.js +44 -0
  144. package/dist/shims/error-boundary.js +10 -8
  145. package/dist/shims/error.js +2 -1
  146. package/dist/shims/fetch-cache.js +1 -1
  147. package/dist/shims/form.js +1 -1
  148. package/dist/shims/image.js +74 -9
  149. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -0
  150. package/dist/shims/internal/app-page-props-cache-key.js +16 -0
  151. package/dist/shims/internal/navigation-untracked.js +2 -1
  152. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  153. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  154. package/dist/shims/internal/pages-data-target.js +1 -1
  155. package/dist/shims/layout-segment-context.d.ts +1 -1
  156. package/dist/shims/layout-segment-context.js +2 -1
  157. package/dist/shims/link.js +38 -17
  158. package/dist/shims/metadata.js +4 -4
  159. package/dist/shims/navigation-context-state.d.ts +40 -0
  160. package/dist/shims/navigation-context-state.js +116 -0
  161. package/dist/shims/navigation-errors.d.ts +55 -0
  162. package/dist/shims/navigation-errors.js +110 -0
  163. package/dist/shims/navigation-server.d.ts +3 -0
  164. package/dist/shims/navigation-server.js +3 -0
  165. package/dist/shims/navigation-state.d.ts +1 -2
  166. package/dist/shims/navigation-state.js +2 -1
  167. package/dist/shims/navigation.d.ts +3 -291
  168. package/dist/shims/navigation.js +16 -445
  169. package/dist/shims/navigation.react-server.d.ts +2 -2
  170. package/dist/shims/navigation.react-server.js +3 -1
  171. package/dist/shims/request-state-types.d.ts +3 -3
  172. package/dist/shims/router.d.ts +6 -2
  173. package/dist/shims/router.js +99 -20
  174. package/dist/shims/script.js +9 -5
  175. package/dist/shims/slot.js +3 -1
  176. package/dist/shims/unified-request-context.d.ts +2 -2
  177. package/dist/utils/has-trailing-comma.d.ts +24 -0
  178. package/dist/utils/has-trailing-comma.js +62 -0
  179. package/dist/utils/text-stream.d.ts +1 -1
  180. package/dist/utils/text-stream.js +2 -2
  181. package/dist/utils/virtual-module.d.ts +5 -0
  182. package/dist/utils/virtual-module.js +0 -0
  183. package/dist/utils/vite-version.d.ts +12 -1
  184. package/dist/utils/vite-version.js +9 -1
  185. package/package.json +5 -1
@@ -5,6 +5,14 @@ import { mergeRewriteQuery } from "../utils/query.js";
5
5
  import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
6
6
  import { mergeHeaders } from "./worker-utils.js";
7
7
  //#region src/server/pages-request-pipeline.ts
8
+ async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset) {
9
+ if (phase === "direct" || request.method !== "GET" && request.method !== "HEAD" || requestPathname === "/api" || requestPathname.startsWith("/api/")) return false;
10
+ const assetUrl = new URL(request.url);
11
+ assetUrl.pathname = requestPathname;
12
+ assetUrl.search = "";
13
+ const response = await fetchAsset(new Request(assetUrl, request));
14
+ return response.status === 404 ? false : response;
15
+ }
8
16
  /**
9
17
  * Wrap an adapter's `runMiddleware` callback so middleware receives the original
10
18
  * (pre-basePath-stripping) URL. Adapters strip the basePath before handing the
@@ -72,6 +80,15 @@ async function runPagesRequest(request, deps) {
72
80
  let resolvedUrl = originalResolvedUrl;
73
81
  const middlewareHeaders = {};
74
82
  let middlewareStatus;
83
+ const serveFilesystemRoute = async (requestPathname, phase) => {
84
+ if (!deps.serveFilesystemRoute) return null;
85
+ const served = await deps.serveFilesystemRoute(requestPathname, middlewareHeaders, phase);
86
+ if (served instanceof Response) return {
87
+ type: "response",
88
+ response: mergeHeaders(served, middlewareHeaders, middlewareStatus)
89
+ };
90
+ return served ? { type: "handled" } : null;
91
+ };
75
92
  if (typeof deps.runMiddleware === "function") {
76
93
  const result = await deps.runMiddleware(request, deps.ctx ?? null, { isDataRequest });
77
94
  if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
@@ -132,9 +149,8 @@ async function runPagesRequest(request, deps) {
132
149
  type: "response",
133
150
  response: mergeHeaders(await proxyExternal(request, resolvedUrl), middlewareHeaders, void 0)
134
151
  };
135
- if (deps.serveStaticFile) {
136
- if (await deps.serveStaticFile(pathname, middlewareHeaders)) return { type: "handled" };
137
- }
152
+ const directFilesystemResult = await serveFilesystemRoute(pathname, "direct");
153
+ if (directFilesystemResult) return directFilesystemResult;
138
154
  let configRewriteFired = false;
139
155
  for (const rewrite of configRewrites.beforeFiles ?? []) {
140
156
  const rewritten = matchRewrite(matchResolvedPathname(resolvedPathname), [rewrite], rewriteRequestContext(), basePathState);
@@ -148,6 +164,10 @@ async function runPagesRequest(request, deps) {
148
164
  configRewriteFired = true;
149
165
  }
150
166
  }
167
+ if (configRewriteFired) {
168
+ const beforeFilesResult = await serveFilesystemRoute(resolvedPathname, "beforeFiles");
169
+ if (beforeFilesResult) return beforeFilesResult;
170
+ }
151
171
  if (basePath && !hadBasePath && !configRewriteFired) return {
152
172
  type: "response",
153
173
  response: new Response("This page could not be found", {
@@ -155,27 +175,33 @@ async function runPagesRequest(request, deps) {
155
175
  headers: { "Content-Type": "text/html; charset=utf-8" }
156
176
  })
157
177
  };
158
- const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, i18nConfig);
159
- const apiLookupPathname = apiLookupUrl.split("?")[0];
160
- if (apiLookupPathname.startsWith("/api/") || apiLookupPathname === "/api") if (typeof deps.handleApi === "function") {
161
- let apiRequest = request;
162
- if (basePath && hadBasePath) {
163
- const apiRequestUrl = new URL(request.url);
164
- apiRequestUrl.pathname = addBasePathToPathname(apiRequestUrl.pathname, basePath);
165
- apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
178
+ const handleResolvedApiRoute = async () => {
179
+ const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, i18nConfig);
180
+ const apiLookupPathname = apiLookupUrl.split("?")[0];
181
+ if (!apiLookupPathname.startsWith("/api/") && apiLookupPathname !== "/api") return null;
182
+ if (typeof deps.handleApi === "function") {
183
+ let apiRequest = request;
184
+ if (basePath && hadBasePath) {
185
+ const apiRequestUrl = new URL(request.url);
186
+ apiRequestUrl.pathname = addBasePathToPathname(apiRequestUrl.pathname, basePath);
187
+ apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
188
+ }
189
+ return {
190
+ type: "response",
191
+ defaultContentType: "application/octet-stream",
192
+ response: mergeHeaders(await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null), middlewareHeaders, middlewareStatus)
193
+ };
166
194
  }
167
195
  return {
168
- type: "response",
169
- defaultContentType: "application/octet-stream",
170
- response: mergeHeaders(await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null), middlewareHeaders, middlewareStatus)
196
+ type: "api",
197
+ apiUrl: apiLookupUrl,
198
+ stagedHeaders: middlewareHeaders,
199
+ requestHeaders: request.headers,
200
+ middlewareStatus
171
201
  };
172
- } else return {
173
- type: "api",
174
- apiUrl: apiLookupUrl,
175
- stagedHeaders: middlewareHeaders,
176
- requestHeaders: request.headers,
177
- middlewareStatus
178
202
  };
203
+ const apiResult = await handleResolvedApiRoute();
204
+ if (apiResult) return apiResult;
179
205
  let pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
180
206
  let resolvedPathnameChanged = false;
181
207
  if (!pageMatch || pageMatch.route.isDynamic) for (const rewrite of configRewrites.afterFiles ?? []) {
@@ -188,6 +214,10 @@ async function runPagesRequest(request, deps) {
188
214
  resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
189
215
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
190
216
  resolvedPathnameChanged = true;
217
+ const afterFilesFilesystemResult = await serveFilesystemRoute(resolvedPathname, "afterFiles");
218
+ if (afterFilesFilesystemResult) return afterFilesFilesystemResult;
219
+ const afterFilesApiResult = await handleResolvedApiRoute();
220
+ if (afterFilesApiResult) return afterFilesApiResult;
191
221
  pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
192
222
  if (pageMatch) break;
193
223
  }
@@ -208,6 +238,10 @@ async function runPagesRequest(request, deps) {
208
238
  };
209
239
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
210
240
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
241
+ const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
242
+ if (fallbackFilesystemResult) return fallbackFilesystemResult;
243
+ const fallbackApiResult = await handleResolvedApiRoute();
244
+ if (fallbackApiResult) return fallbackApiResult;
211
245
  renderPageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
212
246
  refreshDataRewriteHeader();
213
247
  if (renderPageMatch) break;
@@ -228,6 +262,10 @@ async function runPagesRequest(request, deps) {
228
262
  };
229
263
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
230
264
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
265
+ const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
266
+ if (fallbackFilesystemResult) return fallbackFilesystemResult;
267
+ const fallbackApiResult = await handleResolvedApiRoute();
268
+ if (fallbackApiResult) return fallbackApiResult;
231
269
  response = await deps.renderPage(request, resolvedUrl, void 0, stagedHeaders);
232
270
  matchedFallbackRewrite = true;
233
271
  if (response.status !== 404) break;
@@ -250,6 +288,10 @@ async function runPagesRequest(request, deps) {
250
288
  };
251
289
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
252
290
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
291
+ const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
292
+ if (fallbackFilesystemResult) return fallbackFilesystemResult;
293
+ const fallbackApiResult = await handleResolvedApiRoute();
294
+ if (fallbackApiResult) return fallbackApiResult;
253
295
  if (deps.matchPageRoute?.(resolvedPathname, request)) break;
254
296
  }
255
297
  refreshDataRewriteHeader();
@@ -264,4 +306,4 @@ async function runPagesRequest(request, deps) {
264
306
  };
265
307
  }
266
308
  //#endregion
267
- export { runPagesRequest, wrapMiddlewareWithBasePath };
309
+ export { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
@@ -41,6 +41,8 @@ import { IncomingMessage, ServerResponse } from "node:http";
41
41
  */
42
42
  declare function resolveServerEntryImportUrl(entryPath: string): string;
43
43
  declare function importServerEntryModule(entryPath: string): Promise<any>;
44
+ /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
45
+ declare function readNodeStream(req: IncomingMessage): ReadableStream<Uint8Array>;
44
46
  type ProdServerOptions = {
45
47
  /** Port to listen on */port?: number; /** Host to bind to */
46
48
  host?: string; /** Path to the build output directory */
@@ -143,4 +145,4 @@ declare function resolveAppRouterPrerenderSeeder(entryModule: unknown): AppRoute
143
145
  */
144
146
  declare function resolveAppRouterAssetPath(pathname: string, assetPathPrefix: string, assetPrefix: string): string | null;
145
147
  //#endregion
146
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
148
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -3,7 +3,8 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
4
4
  import { normalizePath } from "./normalize-path.js";
5
5
  import { notFoundResponse } from "./http-error-responses.js";
6
- import { filterInternalHeaders, isOpenRedirectShaped } from "./request-pipeline.js";
6
+ import { isOpenRedirectShaped } from "./open-redirect.js";
7
+ import { filterInternalHeaders } from "./request-pipeline.js";
7
8
  import { isUnknownRecord } from "../utils/record.js";
8
9
  import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
9
10
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
@@ -109,11 +110,42 @@ async function importServerEntryModule(entryPath) {
109
110
  }
110
111
  /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
111
112
  function readNodeStream(req) {
112
- return new ReadableStream({ start(controller) {
113
- req.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
114
- req.on("end", () => controller.close());
115
- req.on("error", (err) => controller.error(err));
116
- } });
113
+ let cancelled = false;
114
+ let cleanup = () => {};
115
+ return new ReadableStream({
116
+ start(controller) {
117
+ cleanup = () => {
118
+ req.off("data", onData);
119
+ req.off("end", onEnd);
120
+ req.off("error", onError);
121
+ };
122
+ const onData = (chunk) => {
123
+ if (cancelled) return;
124
+ controller.enqueue(new Uint8Array(chunk));
125
+ if ((controller.desiredSize ?? 0) <= 0) req.pause();
126
+ };
127
+ const onEnd = () => {
128
+ cleanup();
129
+ if (!cancelled) controller.close();
130
+ };
131
+ const onError = (error) => {
132
+ cleanup();
133
+ if (!cancelled) controller.error(error);
134
+ };
135
+ req.on("data", onData);
136
+ req.on("end", onEnd);
137
+ req.on("error", onError);
138
+ req.pause();
139
+ },
140
+ pull() {
141
+ if (!cancelled) req.resume();
142
+ },
143
+ cancel() {
144
+ cancelled = true;
145
+ cleanup();
146
+ req.resume();
147
+ }
148
+ });
117
149
  }
118
150
  /** Content types that benefit from compression. */
119
151
  const COMPRESSIBLE_TYPES = new Set([
@@ -514,7 +546,7 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret) {
514
546
  headers
515
547
  };
516
548
  if (hasBody) {
517
- init.body = Readable.toWeb(req);
549
+ init.body = readNodeStream(req);
518
550
  init.duplex = "half";
519
551
  }
520
552
  return new Request(url, init);
@@ -1008,7 +1040,7 @@ async function startPagesRouterServer(options) {
1008
1040
  const protocol = resolveRequestProtocol(req);
1009
1041
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
1010
1042
  const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
1011
- const isDataRequest = rawReqHeaders.get("x-nextjs-data") === "1";
1043
+ const isDataRequest = isDataReq;
1012
1044
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1013
1045
  const method = req.method ?? "GET";
1014
1046
  const hasBody = method !== "GET" && method !== "HEAD";
@@ -1036,8 +1068,8 @@ async function startPagesRouterServer(options) {
1036
1068
  originalUrl: originalRenderUrl
1037
1069
  }) : null,
1038
1070
  handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
1039
- serveStaticFile: async (requestPathname, stagedHeaders) => {
1040
- if (requestPathname === "/" || requestPathname.startsWith("/api/") || requestPathname.startsWith(`/_next/static/`)) return false;
1071
+ serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1072
+ if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1041
1073
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
1042
1074
  }
1043
1075
  });
@@ -1088,4 +1120,4 @@ async function startPagesRouterServer(options) {
1088
1120
  };
1089
1121
  }
1090
1122
  //#endregion
1091
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
1123
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -1,6 +1,7 @@
1
1
  import { NextHeader } from "../config/next-config.js";
2
2
  import { BasePathMatchState, RequestContext } from "../config/config-matchers.js";
3
3
  import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS } from "./headers.js";
4
+ import { isOpenRedirectShaped } from "./open-redirect.js";
4
5
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
5
6
 
6
7
  //#region src/server/request-pipeline.d.ts
@@ -42,30 +43,6 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
42
43
  * @returns A 404 Response if the path is protocol-relative, or null to continue
43
44
  */
44
45
  declare function guardProtocolRelativeUrl(rawPathname: string): Response | null;
45
- /**
46
- * Returns true if a request pathname looks like a protocol-relative open
47
- * redirect, in either literal or percent-encoded form.
48
- *
49
- * Exported for call sites that need to replicate the guard inline (Pages
50
- * Router worker codegen, Node production server) and for defense-in-depth
51
- * checks inside redirect emitters.
52
- *
53
- * A pathname is considered "open redirect shaped" when its first segment,
54
- * after decoding backslashes and encoded delimiters, would cause a browser
55
- * to resolve a `Location` containing the pathname as protocol-relative:
56
- *
57
- * - literal `//evil.com`
58
- * - literal `/\evil.com` (browsers normalize `\` to `/`)
59
- * - encoded `/%5Cevil.com` (`%5C` decodes to `\` in Location)
60
- * - encoded `/%2F/evil.com` (`%2F` decodes to `/` → `//`)
61
- * - mixed `/%5C%2F`, `/%5C%5C` (and other combinations)
62
- *
63
- * We explicitly do not require a valid percent sequence elsewhere in the
64
- * pathname — we only examine the leading bytes (up to the second real or
65
- * encoded delimiter) so malformed suffixes can still reach the normal
66
- * "400 Bad Request" decode path instead of being masked as "404".
67
- */
68
- declare function isOpenRedirectShaped(rawPathname: string): boolean;
69
46
  type HeaderRecord = Record<string, string | string[]>;
70
47
  type ApplyConfigHeadersOptions = {
71
48
  configHeaders: NextHeader[];
@@ -2,6 +2,7 @@ import { hasBasePath, removeTrailingSlash, stripBasePath } from "../utils/base-p
2
2
  import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, VINEXT_STATIC_FILE_HEADER } from "./headers.js";
3
3
  import { matchHeaders } from "../config/config-matchers.js";
4
4
  import { forbiddenResponse, notFoundResponse } from "./http-error-responses.js";
5
+ import { isOpenRedirectShaped } from "./open-redirect.js";
5
6
  //#region src/server/request-pipeline.ts
6
7
  /**
7
8
  * Shared request pipeline utilities.
@@ -44,39 +45,6 @@ function guardProtocolRelativeUrl(rawPathname) {
44
45
  if (isOpenRedirectShaped(rawPathname)) return notFoundResponse();
45
46
  return null;
46
47
  }
47
- /**
48
- * Returns true if a request pathname looks like a protocol-relative open
49
- * redirect, in either literal or percent-encoded form.
50
- *
51
- * Exported for call sites that need to replicate the guard inline (Pages
52
- * Router worker codegen, Node production server) and for defense-in-depth
53
- * checks inside redirect emitters.
54
- *
55
- * A pathname is considered "open redirect shaped" when its first segment,
56
- * after decoding backslashes and encoded delimiters, would cause a browser
57
- * to resolve a `Location` containing the pathname as protocol-relative:
58
- *
59
- * - literal `//evil.com`
60
- * - literal `/\evil.com` (browsers normalize `\` to `/`)
61
- * - encoded `/%5Cevil.com` (`%5C` decodes to `\` in Location)
62
- * - encoded `/%2F/evil.com` (`%2F` decodes to `/` → `//`)
63
- * - mixed `/%5C%2F`, `/%5C%5C` (and other combinations)
64
- *
65
- * We explicitly do not require a valid percent sequence elsewhere in the
66
- * pathname — we only examine the leading bytes (up to the second real or
67
- * encoded delimiter) so malformed suffixes can still reach the normal
68
- * "400 Bad Request" decode path instead of being masked as "404".
69
- */
70
- function isOpenRedirectShaped(rawPathname) {
71
- if (!rawPathname.startsWith("/")) return false;
72
- const afterSlash = rawPathname.slice(1);
73
- if (afterSlash.startsWith("/") || afterSlash.startsWith("\\")) return true;
74
- if (afterSlash.length >= 3 && afterSlash[0] === "%") {
75
- const encoded = afterSlash.slice(0, 3).toLowerCase();
76
- if (encoded === "%5c" || encoded === "%2f") return true;
77
- }
78
- return false;
79
- }
80
48
  const FILE_LIKE_PATHNAME_RE = /\.[^/]+\/?$/;
81
49
  function isWellKnownPathname(pathname) {
82
50
  return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
@@ -1,4 +1,4 @@
1
- import { CachedAppPageValue } from "../shims/cache.js";
1
+ import { CachedAppPageValue } from "../shims/cache-handler.js";
2
2
 
3
3
  //#region src/server/seed-cache.d.ts
4
4
  type PrerenderCacheSeedMetadata = {
@@ -1,5 +1,5 @@
1
1
  import { normalizePathSeparators } from "../utils/path.js";
2
- import "../utils/asset-prefix.js";
2
+ import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
5
  //#region src/server/static-file-cache.ts
@@ -23,6 +23,7 @@ const CONTENT_TYPES = {
23
23
  ".css": "text/css",
24
24
  ".html": "text/html",
25
25
  ".json": "application/json",
26
+ ".txt": "text/plain; charset=utf-8",
26
27
  ".png": "image/png",
27
28
  ".jpg": "image/jpeg",
28
29
  ".jpeg": "image/jpeg",
@@ -165,9 +166,20 @@ var StaticFileCache = class StaticFileCache {
165
166
  function etagFromFilenameHash(relativePath, ext) {
166
167
  const basename = path.basename(relativePath, ext);
167
168
  const lastDash = basename.lastIndexOf("-");
168
- if (lastDash === -1 || lastDash === basename.length - 1) return null;
169
- const suffix = basename.slice(lastDash + 1);
170
- return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix) ? `W/"${suffix}"` : null;
169
+ if (lastDash !== -1 && lastDash !== basename.length - 1) {
170
+ const suffix = basename.slice(lastDash + 1);
171
+ if (suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)) return `W/"${suffix}"`;
172
+ }
173
+ const normalizedPath = normalizePathSeparators(relativePath);
174
+ const managedMediaSegment = `${ASSET_PREFIX_URL_DIR}/media/`;
175
+ if (normalizedPath.startsWith(managedMediaSegment) || normalizedPath.includes(`/${managedMediaSegment}`)) {
176
+ const lastDot = basename.lastIndexOf(".");
177
+ if (lastDot !== -1) {
178
+ const suffix = basename.slice(lastDot + 1);
179
+ if (/^[0-9a-f]{8}$/.test(suffix)) return `W/"${suffix}"`;
180
+ }
181
+ }
182
+ return null;
171
183
  }
172
184
  function buildVariant(info, baseHeaders, encoding) {
173
185
  return {
@@ -2,17 +2,28 @@ import React from "react";
2
2
 
3
3
  //#region src/shims/before-interactive-context.d.ts
4
4
  /**
5
- * Inline `<Script strategy="beforeInteractive">` content captured during SSR.
5
+ * A `<Script strategy="beforeInteractive">` captured during SSR.
6
6
  *
7
7
  * The Script shim hands these records to the SSR pipeline through
8
8
  * `BeforeInteractiveContext` instead of rendering the `<script>` tag inline.
9
9
  * The pipeline then emits the captured tag immediately after `<head>` opens,
10
10
  * so the script runs before any React-hoisted stylesheets or modulepreload
11
11
  * links. Matches the standard no-flash dark-mode pattern.
12
+ *
13
+ * Both inline (`children`/`dangerouslySetInnerHTML`) and external (`src`)
14
+ * beforeInteractive scripts flow through here, mirroring Next.js which registers
15
+ * inline and src beforeInteractive scripts equally in the App Router runtime
16
+ * (`(self.__next_s=...).push(...)`). An entry has `src` set for external
17
+ * scripts and `innerHTML` set for inline scripts (never both).
12
18
  */
13
19
  type BeforeInteractiveInlineScript = {
14
- /** Optional id attribute. */id?: string; /** Pre-escaped inline content (already passed through `escapeInlineContent`). */
15
- innerHTML: string; /** Nonce to emit on the `<script>` tag, when CSP is enabled. */
20
+ /** Optional id attribute. */id?: string; /** External script URL. Set for src beforeInteractive scripts. */
21
+ src?: string;
22
+ /**
23
+ * Pre-escaped inline content (already passed through `escapeInlineContent`).
24
+ * Set for inline beforeInteractive scripts; omitted for src scripts.
25
+ */
26
+ innerHTML?: string; /** Nonce to emit on the `<script>` tag, when CSP is enabled. */
16
27
  nonce?: string;
17
28
  /**
18
29
  * Additional HTML attributes to emit on the tag. Booleans render as the
@@ -0,0 +1,106 @@
1
+ import { RenderObservation } from "../server/cache-proof.js";
2
+
3
+ //#region src/shims/cache-handler.d.ts
4
+ type CacheHandlerValue = {
5
+ lastModified: number;
6
+ age?: number;
7
+ cacheState?: string;
8
+ cacheControl?: CacheControlMetadata;
9
+ value: IncrementalCacheValue | null;
10
+ };
11
+ type CacheControlMetadata = {
12
+ revalidate: number;
13
+ expire?: number;
14
+ };
15
+ type IncrementalCacheValue = CachedFetchValue | CachedAppPageValue | CachedPagesValue | CachedRouteValue | CachedRedirectValue | CachedImageValue;
16
+ type CachedFetchValue = {
17
+ kind: "FETCH";
18
+ data: {
19
+ headers: Record<string, string>;
20
+ body: string;
21
+ url: string;
22
+ status?: number;
23
+ };
24
+ tags?: string[];
25
+ revalidate: number | false;
26
+ };
27
+ type CachedAppPageValue = {
28
+ kind: "APP_PAGE";
29
+ html: string;
30
+ rscData: ArrayBuffer | undefined;
31
+ headers: Record<string, string | string[]> | undefined;
32
+ postponed: string | undefined;
33
+ renderObservation?: RenderObservation;
34
+ status: number | undefined;
35
+ };
36
+ type CachedPagesValue = {
37
+ kind: "PAGES";
38
+ html: string;
39
+ pageData: object;
40
+ generatedFromDataRequest?: boolean;
41
+ headers: Record<string, string | string[]> | undefined;
42
+ status: number | undefined;
43
+ };
44
+ type CachedRouteValue = {
45
+ kind: "APP_ROUTE";
46
+ body: ArrayBuffer;
47
+ status: number;
48
+ headers: Record<string, string | string[]>;
49
+ };
50
+ type CachedRedirectValue = {
51
+ kind: "REDIRECT";
52
+ props: object;
53
+ };
54
+ type CachedImageValue = {
55
+ kind: "IMAGE";
56
+ etag: string;
57
+ buffer: ArrayBuffer;
58
+ extension: string;
59
+ revalidate?: number;
60
+ };
61
+ type CacheHandlerContext = {
62
+ dev?: boolean;
63
+ maxMemoryCacheSize?: number;
64
+ revalidatedTags?: string[];
65
+ [key: string]: unknown;
66
+ };
67
+ type CacheHandler = {
68
+ get(key: string, ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
69
+ set(key: string, data: IncrementalCacheValue | null, ctx?: Record<string, unknown>): Promise<void>;
70
+ revalidateTag(tags: string | string[], durations?: {
71
+ expire?: number;
72
+ }): Promise<void>;
73
+ resetRequestCache?(): void;
74
+ };
75
+ declare class NoOpCacheHandler implements CacheHandler {
76
+ get(_key: string, _ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
77
+ set(_key: string, _data: IncrementalCacheValue | null, _ctx?: Record<string, unknown>): Promise<void>;
78
+ revalidateTag(_tags: string | string[], _durations?: {
79
+ expire?: number;
80
+ }): Promise<void>;
81
+ }
82
+ type MemoryCacheHandlerOptions = Pick<CacheHandlerContext, "maxMemoryCacheSize"> & {
83
+ cacheMaxMemorySize?: number;
84
+ };
85
+ declare class MemoryCacheHandler implements CacheHandler {
86
+ private store;
87
+ private tagRevalidatedAt;
88
+ private readonly maxMemoryCacheSize;
89
+ private currentMemoryCacheSize;
90
+ constructor(options?: number | MemoryCacheHandlerOptions);
91
+ private estimateEntrySize;
92
+ private deleteEntry;
93
+ private touchEntry;
94
+ private evictLeastRecentlyUsed;
95
+ get(key: string, ctx?: Record<string, unknown>): Promise<CacheHandlerValue | null>;
96
+ set(key: string, data: IncrementalCacheValue | null, ctx?: Record<string, unknown>): Promise<void>;
97
+ revalidateTag(tags: string | string[]): Promise<void>;
98
+ resetRequestCache(): void;
99
+ }
100
+ declare function configureMemoryCacheHandler(options?: MemoryCacheHandlerOptions): void;
101
+ declare function setDataCacheHandler(handler: CacheHandler): void;
102
+ declare function getDataCacheHandler(): CacheHandler;
103
+ declare function setCacheHandler(handler: CacheHandler): void;
104
+ declare function getCacheHandler(): CacheHandler;
105
+ //#endregion
106
+ export { CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, setCacheHandler, setDataCacheHandler };