vinext 0.1.3 → 0.1.4

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 (82) 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/prerender.js +1 -0
  4. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  5. package/dist/client/pages-router-link-navigation.js +32 -2
  6. package/dist/client/vinext-next-data.js +2 -0
  7. package/dist/config/config-matchers.d.ts +11 -1
  8. package/dist/config/config-matchers.js +14 -2
  9. package/dist/config/tsconfig-paths.js +14 -1
  10. package/dist/deploy.js +20 -13
  11. package/dist/entries/app-rsc-entry.js +3 -2
  12. package/dist/entries/pages-client-entry.js +14 -13
  13. package/dist/entries/pages-server-entry.js +6 -26
  14. package/dist/index.js +217 -40
  15. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  16. package/dist/plugins/fonts.js +5 -4
  17. package/dist/plugins/strip-server-exports.d.ts +9 -7
  18. package/dist/plugins/strip-server-exports.js +493 -46
  19. package/dist/routing/app-route-graph.js +2 -2
  20. package/dist/server/app-browser-action-result.js +1 -1
  21. package/dist/server/app-browser-entry.js +8 -1
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-state.d.ts +1 -1
  24. package/dist/server/app-browser-state.js +19 -11
  25. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  26. package/dist/server/app-pages-bridge.d.ts +5 -1
  27. package/dist/server/app-pages-bridge.js +5 -13
  28. package/dist/server/app-rsc-handler.d.ts +3 -0
  29. package/dist/server/app-rsc-handler.js +51 -15
  30. package/dist/server/app-rsc-route-matching.js +6 -2
  31. package/dist/server/app-server-action-execution.js +5 -2
  32. package/dist/server/app-ssr-entry.js +1 -29
  33. package/dist/server/before-interactive-head.d.ts +17 -0
  34. package/dist/server/before-interactive-head.js +35 -0
  35. package/dist/server/csp.js +1 -4
  36. package/dist/server/dev-server.js +81 -36
  37. package/dist/server/middleware-matcher.js +12 -3
  38. package/dist/server/middleware-runtime.d.ts +3 -4
  39. package/dist/server/middleware-runtime.js +2 -0
  40. package/dist/server/navigation-planner.d.ts +3 -12
  41. package/dist/server/navigation-planner.js +24 -0
  42. package/dist/server/navigation-trace.d.ts +2 -1
  43. package/dist/server/navigation-trace.js +1 -0
  44. package/dist/server/operation-token.d.ts +40 -0
  45. package/dist/server/operation-token.js +85 -0
  46. package/dist/server/pages-data-route.d.ts +1 -1
  47. package/dist/server/pages-data-route.js +7 -4
  48. package/dist/server/pages-dev-module-url.d.ts +4 -0
  49. package/dist/server/pages-dev-module-url.js +15 -0
  50. package/dist/server/pages-document-initial-props.d.ts +4 -15
  51. package/dist/server/pages-document-initial-props.js +27 -56
  52. package/dist/server/pages-i18n.js +2 -2
  53. package/dist/server/pages-page-data.js +3 -1
  54. package/dist/server/pages-page-handler.js +3 -1
  55. package/dist/server/pages-page-response.d.ts +2 -0
  56. package/dist/server/pages-page-response.js +4 -4
  57. package/dist/server/pages-readiness.js +1 -1
  58. package/dist/server/pages-request-pipeline.d.ts +7 -7
  59. package/dist/server/pages-request-pipeline.js +63 -21
  60. package/dist/server/prod-server.d.ts +3 -1
  61. package/dist/server/prod-server.js +41 -10
  62. package/dist/server/static-file-cache.js +16 -4
  63. package/dist/shims/before-interactive-context.d.ts +14 -3
  64. package/dist/shims/document.d.ts +15 -20
  65. package/dist/shims/document.js +5 -8
  66. package/dist/shims/image.js +9 -2
  67. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  68. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  69. package/dist/shims/internal/pages-data-target.js +1 -1
  70. package/dist/shims/link.js +37 -16
  71. package/dist/shims/metadata.js +4 -4
  72. package/dist/shims/navigation.js +2 -0
  73. package/dist/shims/router.d.ts +6 -2
  74. package/dist/shims/router.js +99 -20
  75. package/dist/shims/script.js +8 -4
  76. package/dist/utils/has-trailing-comma.d.ts +24 -0
  77. package/dist/utils/has-trailing-comma.js +62 -0
  78. package/dist/utils/text-stream.d.ts +1 -1
  79. package/dist/utils/text-stream.js +2 -2
  80. package/dist/utils/vite-version.d.ts +12 -1
  81. package/dist/utils/vite-version.js +9 -1
  82. package/package.json +1 -1
@@ -19,19 +19,19 @@ import React from "react";
19
19
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx
20
20
  * (search for `loadDocumentInitialProps` and `documentElement`).
21
21
  *
22
- * vinext only forwards `docProps`. The full `DocumentContext`
23
- * (`renderPage`, `defaultGetInitialProps`, `pathname`, `query`, `req`, `res`,
24
- * `err`, `asPath`) is not yet plumbed through. The common upstream pattern
22
+ * `runDocumentRenderPage()` supplies `renderPage`, `defaultGetInitialProps`,
23
+ * and the request pathname/query/asPath fields needed by the common upstream
24
+ * pattern:
25
25
  *
26
26
  * static async getInitialProps(ctx) {
27
27
  * const initialProps = await Document.getInitialProps(ctx)
28
28
  * return { ...initialProps, docValue }
29
29
  * }
30
30
  *
31
- * works because the base `Document.getInitialProps` shim in
32
- * `shims/document.tsx` returns `{ html: "" }` and ignores `ctx`. User
33
- * overrides that *only* read `ctx` will see `undefined` fields — that is a
34
- * separate gap tracked alongside the shim TODO.
31
+ * The standalone `loadUserDocumentInitialProps()` compatibility path supplies
32
+ * an empty default render result because it is only used after the body has
33
+ * already been produced. Request-only fields such as req/res remain outside
34
+ * that compatibility helper.
35
35
  *
36
36
  * Returns `null` when the user did not override the base shim (the static
37
37
  * `getInitialProps` reference still points at the shim's stub) so callers
@@ -47,7 +47,7 @@ async function loadUserDocumentInitialProps(DocumentComponent) {
47
47
  const getInitialProps = DocumentComponent.getInitialProps;
48
48
  if (typeof getInitialProps !== "function") return null;
49
49
  if (getInitialProps === BASE_GET_INITIAL_PROPS) return null;
50
- const result = await getInitialProps({});
50
+ const result = await getInitialProps({ defaultGetInitialProps: async () => ({ html: "" }) });
51
51
  return result && typeof result === "object" ? result : null;
52
52
  }
53
53
  /**
@@ -60,11 +60,10 @@ async function loadUserDocumentInitialProps(DocumentComponent) {
60
60
  * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
61
61
  * the `getInitialProps` + `renderPage` contract lives in one place.
62
62
  *
63
- * `getInitialProps` is invoked at most once here. When this returns `consumed`
64
- * or `rendered`, callers MUST treat that as the single invocation and must not
65
- * call `loadUserDocumentInitialProps` (which would invoke it again — and, for a
66
- * throwing override, surface the error as a 500 rather than the clean fallback
67
- * this contract guarantees).
63
+ * `getInitialProps` is invoked at most once here. When this returns `rendered`,
64
+ * callers MUST treat that as the single invocation and must not call
65
+ * `loadUserDocumentInitialProps` again. Errors intentionally propagate to the
66
+ * Pages Router's normal error-page pipeline, matching Next.js.
68
67
  *
69
68
  * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
70
69
  */
@@ -74,58 +73,30 @@ async function runDocumentRenderPage(input) {
74
73
  if (DocCtor.getInitialProps === BASE_GET_INITIAL_PROPS) return { status: "skipped" };
75
74
  if (!input.enhancePageElement) return { status: "skipped" };
76
75
  const enhancePageElement = input.enhancePageElement;
77
- let renderPageCalled = false;
78
76
  const renderPage = async (opts = {}) => {
79
- renderPageCalled = true;
80
- const wrapped = withScriptNonce(enhancePageElement(opts), input.scriptNonce);
77
+ const wrapped = withScriptNonce(enhancePageElement(typeof opts === "function" ? { enhanceComponent: opts } : opts), input.scriptNonce);
81
78
  return {
82
79
  html: await readStreamAsText(await input.renderToReadableStream(wrapped)),
83
80
  head: []
84
81
  };
85
82
  };
86
- let docInitialProps;
87
- try {
88
- docInitialProps = await DocCtor.getInitialProps({
89
- renderPage,
90
- defaultGetInitialProps: async (ctx) => {
91
- const result = await (ctx.renderPage ?? renderPage)({ enhanceApp: (App) => (props) => React.createElement(App, props) });
92
- return {
93
- html: result.html,
94
- head: result.head ?? [],
95
- styles: void 0
96
- };
97
- },
98
- ...input.context
99
- });
100
- } catch (err) {
101
- console.error("[vinext] _document.getInitialProps() threw:", err);
102
- return {
103
- status: "consumed",
104
- docProps: {},
105
- head: []
106
- };
107
- }
83
+ const docInitialProps = await DocCtor.getInitialProps({
84
+ renderPage,
85
+ defaultGetInitialProps: async (ctx) => {
86
+ const result = await ctx.renderPage({ enhanceApp: (App) => (props) => React.createElement(App, props) });
87
+ return {
88
+ html: result.html,
89
+ head: result.head ?? [],
90
+ styles: void 0
91
+ };
92
+ },
93
+ ...input.context
94
+ });
108
95
  const { html: _html, head: rawHead, styles: _styles, ...docProps } = docInitialProps ?? {};
109
96
  const head = Array.isArray(rawHead) ? rawHead : [];
110
- if (!renderPageCalled) return {
111
- status: "consumed",
112
- docProps,
113
- head
114
- };
115
- if (!docInitialProps || typeof docInitialProps.html !== "string") {
116
- console.error(`[vinext] "${DocCtor.displayName ?? DocCtor.name ?? "Document"}.getInitialProps()" did not return an object with a string "html" prop`);
117
- return {
118
- status: "consumed",
119
- docProps,
120
- head
121
- };
122
- }
97
+ if (!docInitialProps || typeof docInitialProps.html !== "string") throw new Error(`"${DocCtor.displayName ?? DocCtor.name ?? "Document"}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`);
123
98
  let stylesHTML = "";
124
- if (docInitialProps.styles != null) try {
125
- stylesHTML = await input.renderStylesToString(React.createElement(React.Fragment, null, docInitialProps.styles));
126
- } catch (err) {
127
- console.error("[vinext] Failed to render _document.getInitialProps() styles:", err);
128
- }
99
+ if (docInitialProps.styles != null) stylesHTML = await input.renderStylesToString(React.createElement(React.Fragment, null, docInitialProps.styles));
129
100
  return {
130
101
  status: "rendered",
131
102
  bodyHtml: docInitialProps.html,
@@ -127,8 +127,8 @@ function parseCookieLocaleFromHeader(cookieHeader, i18nConfig) {
127
127
  } catch {
128
128
  return null;
129
129
  }
130
- if (i18nConfig.locales.includes(value)) return value;
131
- return null;
130
+ const lowerValue = value.toLowerCase();
131
+ return i18nConfig.locales.find((locale) => locale.toLowerCase() === lowerValue) ?? null;
132
132
  }
133
133
  function formatLocalizedRootPath(locale, defaultLocale, basePath = "", trailingSlash = false, search = "") {
134
134
  if (locale.toLowerCase() === defaultLocale.toLowerCase()) return void 0;
@@ -198,7 +198,9 @@ function applyBotETagAndCheck(cachedResponse, html, options) {
198
198
  function rewritePagesCachedHtml(cachedHtml, freshBody, nextDataScript) {
199
199
  const bodyStart = cachedHtml.indexOf("<div id=\"__next\">");
200
200
  const contentStart = bodyStart >= 0 ? bodyStart + 17 : -1;
201
- const nextDataStart = cachedHtml.indexOf("<script>window.__NEXT_DATA__");
201
+ const canonicalNextDataStart = cachedHtml.search(/<script\b(?=[^>]*\bid=["']__NEXT_DATA__["'])(?=[^>]*\btype=["']application\/json["'])[^>]*>/);
202
+ const legacyNextDataStart = cachedHtml.indexOf("<script>window.__NEXT_DATA__");
203
+ const nextDataStart = canonicalNextDataStart >= 0 ? canonicalNextDataStart : legacyNextDataStart;
202
204
  if (contentStart >= 0 && nextDataStart >= 0) {
203
205
  const region = cachedHtml.slice(contentStart, nextDataStart);
204
206
  const lastCloseDiv = region.lastIndexOf("</div>");
@@ -184,7 +184,7 @@ function createPagesPageHandler(opts) {
184
184
  const pagesResolvedUrl = (new URL(routeUrl, originalRequestUrl).pathname || "/") + originalRequestUrl.search;
185
185
  const pageDataResult = await resolvePagesPageData({
186
186
  isDataReq,
187
- err,
187
+ err: err instanceof Error ? err : void 0,
188
188
  applyRequestContexts: applySSRContext,
189
189
  buildId,
190
190
  deploymentId: process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID,
@@ -327,6 +327,7 @@ function createPagesPageHandler(opts) {
327
327
  return typeof wrapWithRouterContext === "function" ? wrapWithRouterContext(el) : el;
328
328
  },
329
329
  DocumentComponent,
330
+ err: err instanceof Error ? err : void 0,
330
331
  flushPreloads: typeof flushPreloads === "function" ? flushPreloads : void 0,
331
332
  fontLinkHeader,
332
333
  fontPreloads: allFontPreloads,
@@ -344,6 +345,7 @@ function createPagesPageHandler(opts) {
344
345
  pageProps,
345
346
  props: renderProps,
346
347
  params,
348
+ query,
347
349
  renderDocumentToString(element) {
348
350
  return renderToStringAsync(element);
349
351
  },
@@ -66,6 +66,7 @@ type RenderPagesPageResponseOptions = {
66
66
  */
67
67
  enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined;
68
68
  DocumentComponent: ComponentType | null;
69
+ err?: Error;
69
70
  flushPreloads?: (() => Promise<void> | void) | undefined;
70
71
  fontLinkHeader: string;
71
72
  fontPreloads: PagesFontPreload[];
@@ -95,6 +96,7 @@ type RenderPagesPageResponseOptions = {
95
96
  pageProps: Record<string, unknown>;
96
97
  props?: Record<string, unknown>;
97
98
  params: Record<string, unknown>;
99
+ query?: Record<string, unknown>;
98
100
  renderDocumentToString: (element: ReactNode) => Promise<string>;
99
101
  renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;
100
102
  resetSSRHead?: (() => void) | undefined;
@@ -7,7 +7,7 @@ import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders }
7
7
  import { buildMissIsrCacheControl } from "./isr-decision.js";
8
8
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
9
9
  import { withScriptNonce } from "../shims/script-nonce-context.js";
10
- import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr } from "./html.js";
10
+ import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
11
11
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
12
12
  import { readStreamAsText } from "../utils/text-stream.js";
13
13
  import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
@@ -89,8 +89,7 @@ function buildPagesNextDataScript(options) {
89
89
  ...options.nextData?.__vinext,
90
90
  ...options.vinext
91
91
  };
92
- const localeGlobals = options.i18n.locales ? `;window.__VINEXT_LOCALE__=${options.safeJsonStringify(options.i18n.locale)};window.__VINEXT_LOCALES__=${options.safeJsonStringify(options.i18n.locales)};window.__VINEXT_DEFAULT_LOCALE__=${options.safeJsonStringify(options.i18n.defaultLocale)}` : "";
93
- return createInlineScriptTag(`window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}`, options.scriptNonce);
92
+ return `<script id="__NEXT_DATA__" type="application/json"${createNonceAttribute(options.scriptNonce)}>${options.safeJsonStringify(nextDataPayload)}<\/script>`;
94
93
  }
95
94
  async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, options) {
96
95
  if (options.DocumentComponent) {
@@ -197,8 +196,9 @@ async function renderPagesPageResponse(options) {
197
196
  renderStylesToString: async (element) => readStreamAsText(await options.renderToReadableStream(element)),
198
197
  scriptNonce: options.scriptNonce,
199
198
  context: {
199
+ err: options.err,
200
200
  pathname: options.routePattern,
201
- query: options.params,
201
+ query: options.query ?? options.params,
202
202
  asPath: options.routeUrl
203
203
  }
204
204
  });
@@ -10,7 +10,7 @@ function buildPagesReadinessNextData(options) {
10
10
  const hasAppGip = typeof options.appComponent?.getInitialProps === "function";
11
11
  return {
12
12
  gssp: hasPageGssp,
13
- gsp: hasPageGsp,
13
+ gsp: hasPageGsp ? true : void 0,
14
14
  gip: hasPageGip,
15
15
  appGip: hasAppGip,
16
16
  autoExport: !hasPageGssp && !hasPageGsp && !hasPageGip && !hasAppGip,
@@ -7,6 +7,8 @@ type PagesRenderOptions = {
7
7
  renderErrorPageOnMiss?: boolean;
8
8
  originalUrl?: string;
9
9
  };
10
+ type FilesystemRoutePhase = "direct" | "beforeFiles" | "afterFiles" | "fallback";
11
+ declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>): Promise<Response | false>;
10
12
  type MiddlewareResult = {
11
13
  continue: boolean;
12
14
  redirectUrl?: string;
@@ -53,15 +55,13 @@ type PagesPipelineDeps = {
53
55
  */
54
56
  proxyExternal?: ((currentRequest: Request, externalUrl: string) => Promise<Response>) | null;
55
57
  /**
56
- * Optional public-directory static file server (Node prod only).
58
+ * Optional filesystem/static-asset probe supplied by each runtime adapter.
57
59
  * Called post-middleware (so middleware can intercept/redirect public files) with the
58
60
  * original basePath-stripped pathname and the staged middleware response headers.
59
- * The callback writes the file to its own output (Node `res`) and resolves `true` when
60
- * it served the request; the pipeline then returns `{ type: "handled" }`. Resolves `false`
61
- * to fall through to rewrites/render. Worker/dev adapters omit this — their public files
62
- * are served by the asset binding / Vite respectively.
61
+ * Node may write directly to `res` and return true; dev/Workers return a Response.
62
+ * Resolves false to continue through rewrites, API routes, and page rendering.
63
63
  */
64
- serveStaticFile?: ((requestPathname: string, stagedHeaders: HeaderRecord) => Promise<boolean>) | null;
64
+ serveFilesystemRoute?: ((requestPathname: string, stagedHeaders: HeaderRecord, phase: FilesystemRoutePhase) => Promise<boolean | Response>) | null;
65
65
  };
66
66
  /**
67
67
  * Wrap an adapter's `runMiddleware` callback so middleware receives the original
@@ -111,4 +111,4 @@ type PagesPipelineResult = {
111
111
  */
112
112
  declare function runPagesRequest(request: Request, deps: PagesPipelineDeps): Promise<PagesPipelineResult>;
113
113
  //#endregion
114
- export { MiddlewareResult, PagesPipelineDeps, PagesPipelineResult, PagesRenderOptions, runPagesRequest, wrapMiddlewareWithBasePath };
114
+ export { FilesystemRoutePhase, MiddlewareResult, PagesPipelineDeps, PagesPipelineResult, PagesRenderOptions, fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath };
@@ -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 };
@@ -109,11 +109,42 @@ async function importServerEntryModule(entryPath) {
109
109
  }
110
110
  /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
111
111
  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
- } });
112
+ let cancelled = false;
113
+ let cleanup = () => {};
114
+ return new ReadableStream({
115
+ start(controller) {
116
+ cleanup = () => {
117
+ req.off("data", onData);
118
+ req.off("end", onEnd);
119
+ req.off("error", onError);
120
+ };
121
+ const onData = (chunk) => {
122
+ if (cancelled) return;
123
+ controller.enqueue(new Uint8Array(chunk));
124
+ if ((controller.desiredSize ?? 0) <= 0) req.pause();
125
+ };
126
+ const onEnd = () => {
127
+ cleanup();
128
+ if (!cancelled) controller.close();
129
+ };
130
+ const onError = (error) => {
131
+ cleanup();
132
+ if (!cancelled) controller.error(error);
133
+ };
134
+ req.on("data", onData);
135
+ req.on("end", onEnd);
136
+ req.on("error", onError);
137
+ req.pause();
138
+ },
139
+ pull() {
140
+ if (!cancelled) req.resume();
141
+ },
142
+ cancel() {
143
+ cancelled = true;
144
+ cleanup();
145
+ req.resume();
146
+ }
147
+ });
117
148
  }
118
149
  /** Content types that benefit from compression. */
119
150
  const COMPRESSIBLE_TYPES = new Set([
@@ -514,7 +545,7 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret) {
514
545
  headers
515
546
  };
516
547
  if (hasBody) {
517
- init.body = Readable.toWeb(req);
548
+ init.body = readNodeStream(req);
518
549
  init.duplex = "half";
519
550
  }
520
551
  return new Request(url, init);
@@ -1008,7 +1039,7 @@ async function startPagesRouterServer(options) {
1008
1039
  const protocol = resolveRequestProtocol(req);
1009
1040
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
1010
1041
  const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
1011
- const isDataRequest = rawReqHeaders.get("x-nextjs-data") === "1";
1042
+ const isDataRequest = isDataReq;
1012
1043
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1013
1044
  const method = req.method ?? "GET";
1014
1045
  const hasBody = method !== "GET" && method !== "HEAD";
@@ -1036,8 +1067,8 @@ async function startPagesRouterServer(options) {
1036
1067
  originalUrl: originalRenderUrl
1037
1068
  }) : null,
1038
1069
  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;
1070
+ serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1071
+ if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1041
1072
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
1042
1073
  }
1043
1074
  });
@@ -1088,4 +1119,4 @@ async function startPagesRouterServer(options) {
1088
1119
  };
1089
1120
  }
1090
1121
  //#endregion
1091
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
1122
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -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