vinext 0.1.0 → 0.1.1

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 (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
@@ -117,6 +117,7 @@ async function renderPagesIsrHtml(options) {
117
117
  params: options.params,
118
118
  routePattern: options.routePattern,
119
119
  safeJsonStringify: options.safeJsonStringify,
120
+ nextData: options.nextData,
120
121
  vinext: options.vinext
121
122
  });
122
123
  return rewritePagesCachedHtml(options.cachedHtml, freshBody, nextDataScript);
@@ -174,11 +175,11 @@ async function resolvePagesPageData(options) {
174
175
  const cacheKey = options.isrCacheKey("pages", pathname);
175
176
  const cached = await options.isrGet(cacheKey);
176
177
  const cachedValue = cached?.value.value;
177
- if (cachedValue?.kind === "PAGES" && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) return {
178
+ if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) return {
178
179
  kind: "response",
179
180
  response: buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status)
180
181
  };
181
- if (cachedValue?.kind === "PAGES" && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
182
+ if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
182
183
  options.triggerBackgroundRegeneration(cacheKey, async function() {
183
184
  return options.runInFreshUnifiedContext(async () => {
184
185
  const freshResult = await options.pageModule.getStaticProps?.({
@@ -200,6 +201,7 @@ async function resolvePagesPageData(options) {
200
201
  renderIsrPassToStringAsync: options.renderIsrPassToStringAsync,
201
202
  routePattern: options.routePattern,
202
203
  safeJsonStringify: options.safeJsonStringify,
204
+ nextData: options.nextData,
203
205
  vinext: options.vinext
204
206
  });
205
207
  await options.isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshResult.props, options.statusCode), freshResult.revalidate, void 0, options.expireSeconds);
@@ -47,8 +47,15 @@ type CreatePagesPageHandlerOptions = {
47
47
  vinextConfig: VinextConfigSubset; /** Build ID embedded at build time (or null in dev). */
48
48
  buildId: string | null; /** Whether the app has user-defined middleware. */
49
49
  hasMiddleware: boolean; /** Absolute file path of `pages/_app` (or null). Used for manifest lookup. */
50
- appAssetPath: string | null; /** `setSSRContext` from `next/router`. */
51
- setSSRContext: ((ctx: Record<string, unknown> | null) => void) | null; /** `setI18nContext` from `vinext/i18n-context`. */
50
+ appAssetPath: string | null; /** Whether next.config rewrites are configured (gates Pages router readiness). */
51
+ hasRewrites: boolean; /** `setSSRContext` from `next/router`. */
52
+ setSSRContext: ((ctx: Record<string, unknown> | null) => void) | null;
53
+ /**
54
+ * `getPagesNavigationIsReadyFromSerializedState` from `next/router`. Decides
55
+ * the initial `router.isReady` value for the Pages Router navigation
56
+ * compat hooks (mirrors Next.js's Pages adapter readiness gate).
57
+ */
58
+ getPagesNavigationIsReadyFromSerializedState: ((routePattern: string | undefined, searchString: string, nextData?: Record<string, unknown>) => boolean) | null; /** `setI18nContext` from `vinext/i18n-context`. */
52
59
  setI18nContext: ((ctx: Record<string, unknown>) => void) | null; /** `wrapWithRouterContext` from `next/router`. */
53
60
  wrapWithRouterContext: ((element: ReactNode) => ReactNode) | null; /** `resetSSRHead` from `next/head`. */
54
61
  resetSSRHead: (() => void) | undefined; /** `getSSRHeadHTML` from `next/head`. */
@@ -2,12 +2,13 @@ import { createRequestContext, runWithRequestContext } from "../shims/unified-re
2
2
  import { patternToNextFormat } from "../routing/route-validation.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { reportRequestError } from "./instrumentation.js";
5
- import { isrCacheKey, isrGet, isrSet, triggerBackgroundRegeneration } from "./isr-cache.js";
5
+ import { PRERENDER_REVALIDATE_HEADER, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, triggerBackgroundRegeneration } from "./isr-cache.js";
6
6
  import { ensureFetchPatch } from "../shims/fetch-cache.js";
7
7
  import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
8
8
  import { getScriptNonceFromHeaderSources } from "./csp.js";
9
9
  import { resolvePagesI18nRequest } from "./pages-i18n.js";
10
10
  import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
11
+ import { buildPagesReadinessNextData } from "./pages-readiness.js";
11
12
  import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
12
13
  import { buildNextDataJsonResponse, buildNextDataNotFoundResponse, normalizePagesDataRequest } from "./pages-data-route.js";
13
14
  import { createPagesReqRes } from "./pages-node-compat.js";
@@ -30,7 +31,7 @@ function buildI18nRenderContext(i18nConfig, locale, currentDefaultLocale, domain
30
31
  * accepts the same options shape the generated entry always passed inline.
31
32
  */
32
33
  function createPagesPageHandler(opts) {
33
- const { pageRoutes, errorPageRoute, matchRoute, i18nConfig, vinextConfig, buildId, hasMiddleware, appAssetPath, setSSRContext, setI18nContext, wrapWithRouterContext, resetSSRHead, getSSRHeadHTML, setDocumentInitialHead, flushPreloads, getFontLinks, getFontStyles, getFontPreloads, renderToReadableStream, renderIsrPassToStringAsync, safeJsonStringify, sanitizeDestination, createPageElement, enhancePageElement, AppComponent, DocumentComponent } = opts;
34
+ const { pageRoutes, errorPageRoute, matchRoute, i18nConfig, vinextConfig, buildId, hasMiddleware, appAssetPath, hasRewrites, setSSRContext, getPagesNavigationIsReadyFromSerializedState, setI18nContext, wrapWithRouterContext, resetSSRHead, getSSRHeadHTML, setDocumentInitialHead, flushPreloads, getFontLinks, getFontStyles, getFontPreloads, renderToReadableStream, renderIsrPassToStringAsync, safeJsonStringify, sanitizeDestination, createPageElement, enhancePageElement, AppComponent, DocumentComponent } = opts;
34
35
  function renderToStringAsync(element) {
35
36
  return renderToReadableStream(element).then((stream) => new Response(stream).text());
36
37
  }
@@ -102,15 +103,24 @@ function createPagesPageHandler(opts) {
102
103
  const routePattern = patternToNextFormat(route.pattern);
103
104
  const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
104
105
  const query = mergeRouteParamsIntoQuery(parseQueryString(routeUrl), params);
105
- function applySSRContext() {
106
+ const pageModule = route.module;
107
+ const pagesNextData = buildPagesReadinessNextData({
108
+ pageModule,
109
+ appComponent: AppComponent,
110
+ hasRewrites
111
+ });
112
+ const navigationIsReady = typeof getPagesNavigationIsReadyFromSerializedState === "function" ? getPagesNavigationIsReadyFromSerializedState(routePattern, new URL(renderAsPath ?? routeUrl, "http://_").search, pagesNextData) : true;
113
+ function applySSRContext(extra) {
106
114
  if (typeof setSSRContext === "function") setSSRContext({
107
115
  pathname: routePattern,
108
116
  query,
109
117
  asPath: renderAsPath ?? routeUrl,
118
+ navigationIsReady,
110
119
  locale,
111
120
  locales: i18nConfig ? i18nConfig.locales : void 0,
112
121
  defaultLocale: currentDefaultLocale,
113
- domainLocales
122
+ domainLocales,
123
+ ...extra
114
124
  });
115
125
  if (i18nConfig && typeof setI18nContext === "function") setI18nContext({
116
126
  locale,
@@ -120,8 +130,7 @@ function createPagesPageHandler(opts) {
120
130
  hostname: new URL(request.url).hostname
121
131
  });
122
132
  }
123
- applySSRContext();
124
- const pageModule = route.module;
133
+ applySSRContext({ nextData: pagesNextData });
125
134
  const PageComponent = pageModule.default;
126
135
  if (!PageComponent) return new Response("Page has no default export", { status: 500 });
127
136
  if (!isDataReq && routePattern !== "/_error" && routePattern !== "/404" && routePattern !== "/500" && renderStatusCodeOverride === void 0) {
@@ -133,6 +142,15 @@ function createPagesPageHandler(opts) {
133
142
  }
134
143
  const pageModuleUrl = resolveClientModuleUrl(manifest, route.filePath);
135
144
  const appModuleUrl = resolveClientModuleUrl(manifest, appAssetPath);
145
+ const serializedPagesNextData = {
146
+ ...pagesNextData,
147
+ __vinext: {
148
+ ...pagesNextData.__vinext,
149
+ pageModuleUrl,
150
+ appModuleUrl,
151
+ hasMiddleware
152
+ }
153
+ };
136
154
  const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareHeaders);
137
155
  let fontLinkHeader = "";
138
156
  let allFontPreloads = [];
@@ -164,6 +182,7 @@ function createPagesPageHandler(opts) {
164
182
  isrSet,
165
183
  expireSeconds: vinextConfig.expireTime,
166
184
  isBuildTimePrerendering: typeof process !== "undefined" && process.env && process.env.VINEXT_PRERENDER === "1",
185
+ isOnDemandRevalidate: isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER)),
167
186
  pageModule,
168
187
  params,
169
188
  query,
@@ -183,11 +202,8 @@ function createPagesPageHandler(opts) {
183
202
  scriptNonce,
184
203
  statusCode: renderStatusCode,
185
204
  triggerBackgroundRegeneration,
186
- vinext: {
187
- pageModuleUrl,
188
- appModuleUrl,
189
- hasMiddleware
190
- }
205
+ vinext: serializedPagesNextData.__vinext,
206
+ nextData: serializedPagesNextData
191
207
  });
192
208
  if (pageDataResult.kind === "notFound") {
193
209
  const notFoundRoute = findNotFoundRoute();
@@ -212,6 +228,7 @@ function createPagesPageHandler(opts) {
212
228
  pathname: routePattern,
213
229
  query,
214
230
  asPath: renderAsPath ?? routeUrl,
231
+ navigationIsReady: false,
215
232
  locale,
216
233
  locales: i18nConfig ? i18nConfig.locales : void 0,
217
234
  defaultLocale: currentDefaultLocale,
@@ -288,11 +305,7 @@ function createPagesPageHandler(opts) {
288
305
  safeJsonStringify,
289
306
  scriptNonce,
290
307
  statusCode: renderStatusCode,
291
- vinext: {
292
- pageModuleUrl,
293
- appModuleUrl,
294
- hasMiddleware
295
- }
308
+ nextData: serializedPagesNextData
296
309
  });
297
310
  } catch (e) {
298
311
  console.error("[vinext] SSR error:", e);
@@ -8,6 +8,14 @@ type PagesFontPreload = {
8
8
  href: string;
9
9
  type: string;
10
10
  };
11
+ /**
12
+ * The `__NEXT_DATA__` fields beyond the always-present core that the Pages
13
+ * renderer serializes: the `__vinext` block plus the readiness flags
14
+ * (gssp/gsp/gip/appGip/autoExport/isExperimentalCompile) the client uses to
15
+ * recompute the initial `router.isReady`. Shared by every render path
16
+ * (initial, ISR regeneration) so they emit identical readiness state.
17
+ */
18
+ type PagesNextDataExtras = Pick<VinextNextData, "__vinext" | "appGip" | "autoExport" | "gip" | "gsp" | "gssp" | "isExperimentalCompile">;
11
19
  type PagesI18nRenderContext = {
12
20
  locale?: string;
13
21
  locales?: string[];
@@ -73,10 +81,11 @@ type RenderPagesPageResponseOptions = {
73
81
  scriptNonce?: string;
74
82
  statusCode?: number;
75
83
  vinext?: VinextNextData["__vinext"];
84
+ nextData?: PagesNextDataExtras;
76
85
  };
77
- declare function buildPagesNextDataScript(options: Pick<RenderPagesPageResponseOptions, "buildId" | "i18n" | "isFallback" | "pageProps" | "params" | "routePattern" | "safeJsonStringify" | "scriptNonce"> & {
86
+ declare function buildPagesNextDataScript(options: Pick<RenderPagesPageResponseOptions, "buildId" | "i18n" | "isFallback" | "pageProps" | "params" | "routePattern" | "safeJsonStringify" | "scriptNonce" | "nextData"> & {
78
87
  vinext?: VinextNextData["__vinext"];
79
88
  }): string;
80
89
  declare function renderPagesPageResponse(options: RenderPagesPageResponseOptions): Promise<Response>;
81
90
  //#endregion
82
- export { PagesGsspResponse, PagesI18nRenderContext, buildPagesNextDataScript, renderPagesPageResponse };
91
+ export { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras, buildPagesNextDataScript, renderPagesPageResponse };
@@ -27,13 +27,19 @@ function buildPagesNextDataScript(options) {
27
27
  buildId: options.buildId,
28
28
  isFallback: options.isFallback === true
29
29
  };
30
+ if (options.nextData) {
31
+ for (const [key, value] of Object.entries(options.nextData)) if (value !== void 0) nextDataPayload[key] = value;
32
+ }
30
33
  if (options.i18n.locales) {
31
34
  nextDataPayload.locale = options.i18n.locale;
32
35
  nextDataPayload.locales = options.i18n.locales;
33
36
  nextDataPayload.defaultLocale = options.i18n.defaultLocale;
34
37
  nextDataPayload.domainLocales = options.i18n.domainLocales;
35
38
  }
36
- if (options.vinext) nextDataPayload.__vinext = options.vinext;
39
+ if (options.vinext) nextDataPayload.__vinext = {
40
+ ...options.nextData?.__vinext,
41
+ ...options.vinext
42
+ };
37
43
  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)}` : "";
38
44
  return createInlineScriptTag(`window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}`, options.scriptNonce);
39
45
  }
@@ -129,6 +135,7 @@ async function renderPagesPageResponse(options) {
129
135
  routePattern: options.routePattern,
130
136
  safeJsonStringify: options.safeJsonStringify,
131
137
  scriptNonce: options.scriptNonce,
138
+ nextData: options.nextData,
132
139
  vinext: options.vinext
133
140
  });
134
141
  const bodyMarker = "<!--VINEXT_STREAM_BODY-->";
@@ -0,0 +1,36 @@
1
+ import { VinextNextData } from "../client/vinext-next-data.js";
2
+ import { PagesPageModule } from "./pages-page-data.js";
3
+
4
+ //#region src/server/pages-readiness.d.ts
5
+ /**
6
+ * Shared Pages Router readiness modeling.
7
+ *
8
+ * The initial `router.isReady` value for the `next/navigation` compat hooks is
9
+ * derived from the page/_app data-fetching exports plus the configured-rewrites
10
+ * flag, serialized into `__NEXT_DATA__`. The dev SSR handler and the production
11
+ * Pages page handler must compute this identically so server HTML and client
12
+ * hydration agree — see `getPagesNavigationIsReadyFromSerializedState` in
13
+ * `shims/router.ts`.
14
+ */
15
+ /**
16
+ * The serialized readiness flags (gssp/gsp/gip/appGip/autoExport +
17
+ * `__vinext.hasRewrites`) that gate the initial Pages Router `router.isReady`.
18
+ * The field names/types are projected from the canonical `VinextNextData` so
19
+ * this stays in lockstep with the `__NEXT_DATA__` shape it feeds into.
20
+ */
21
+ type PagesReadinessNextData = Pick<VinextNextData, "gssp" | "gsp" | "gip" | "appGip" | "autoExport"> & {
22
+ __vinext: Pick<NonNullable<VinextNextData["__vinext"]>, "hasRewrites">;
23
+ };
24
+ /**
25
+ * Build the readiness flags for a Pages Router render. Shared by the dev and
26
+ * production Pages render paths.
27
+ */
28
+ declare function buildPagesReadinessNextData(options: {
29
+ pageModule: PagesPageModule;
30
+ appComponent: {
31
+ getInitialProps?: unknown;
32
+ } | null | undefined;
33
+ hasRewrites: boolean;
34
+ }): PagesReadinessNextData;
35
+ //#endregion
36
+ export { PagesReadinessNextData, buildPagesReadinessNextData };
@@ -0,0 +1,21 @@
1
+ //#region src/server/pages-readiness.ts
2
+ /**
3
+ * Build the readiness flags for a Pages Router render. Shared by the dev and
4
+ * production Pages render paths.
5
+ */
6
+ function buildPagesReadinessNextData(options) {
7
+ const hasPageGssp = typeof options.pageModule.getServerSideProps === "function";
8
+ const hasPageGsp = typeof options.pageModule.getStaticProps === "function";
9
+ const hasPageGip = typeof options.pageModule.default?.getInitialProps === "function";
10
+ const hasAppGip = typeof options.appComponent?.getInitialProps === "function";
11
+ return {
12
+ gssp: hasPageGssp,
13
+ gsp: hasPageGsp,
14
+ gip: hasPageGip,
15
+ appGip: hasAppGip,
16
+ autoExport: !hasPageGssp && !hasPageGsp && !hasPageGip && !hasAppGip,
17
+ __vinext: { hasRewrites: options.hasRewrites }
18
+ };
19
+ }
20
+ //#endregion
21
+ export { buildPagesReadinessNextData };
@@ -0,0 +1,99 @@
1
+ import { NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "../config/next-config.js";
2
+ import { HeaderRecord } from "./request-pipeline.js";
3
+
4
+ //#region src/server/pages-request-pipeline.d.ts
5
+ type PagesRenderOptions = {
6
+ isDataReq?: boolean;
7
+ renderErrorPageOnMiss?: boolean;
8
+ };
9
+ type MiddlewareResult = {
10
+ continue: boolean;
11
+ redirectUrl?: string;
12
+ redirectStatus?: number;
13
+ rewriteUrl?: string;
14
+ rewriteStatus?: number;
15
+ status?: number;
16
+ responseHeaders?: Iterable<[string, string]>;
17
+ response?: Response;
18
+ waitUntilPromises?: Promise<unknown>[];
19
+ };
20
+ type PagesPipelineDeps = {
21
+ basePath: string;
22
+ trailingSlash: boolean;
23
+ i18nConfig: NextI18nConfig | null;
24
+ configRedirects: NextRedirect[];
25
+ configRewrites: {
26
+ beforeFiles: NextRewrite[];
27
+ afterFiles: NextRewrite[];
28
+ fallback: NextRewrite[];
29
+ };
30
+ configHeaders: NextHeader[];
31
+ hadBasePath: boolean;
32
+ isDataReq: boolean;
33
+ isDataRequest: boolean;
34
+ ctx?: unknown;
35
+ rawSearch?: string;
36
+ matchPageRoute?: ((pathname: string, request: Request) => {
37
+ route: {
38
+ isDynamic: boolean;
39
+ };
40
+ } | null) | null;
41
+ runMiddleware?: ((request: Request, ctx: unknown, opts: {
42
+ isDataRequest: boolean;
43
+ }) => Promise<MiddlewareResult>) | null;
44
+ renderPage?: ((request: Request, resolvedUrl: string, options?: PagesRenderOptions, stagedHeaders?: Headers) => Promise<Response>) | null;
45
+ handleApi?: ((request: Request, apiUrl: string, ctx: unknown) => Promise<Response>) | null;
46
+ /**
47
+ * Optional override for proxying external rewrite destinations.
48
+ * When supplied, the pipeline calls this instead of proxyExternalRequest(currentRequest, url).
49
+ * Receives the pipeline's current request (with post-middleware headers applied) and the
50
+ * external target URL. Dev adapters supply this to forward the original Node req body
51
+ * (which is not included in the pipeline's body-less Web Request).
52
+ */
53
+ proxyExternal?: ((currentRequest: Request, externalUrl: string) => Promise<Response>) | null;
54
+ /**
55
+ * Optional public-directory static file server (Node prod only).
56
+ * Called post-middleware (so middleware can intercept/redirect public files) with the
57
+ * original basePath-stripped pathname and the staged middleware response headers.
58
+ * The callback writes the file to its own output (Node `res`) and resolves `true` when
59
+ * it served the request; the pipeline then returns `{ type: "handled" }`. Resolves `false`
60
+ * to fall through to rewrites/render. Worker/dev adapters omit this — their public files
61
+ * are served by the asset binding / Vite respectively.
62
+ */
63
+ serveStaticFile?: ((requestPathname: string, stagedHeaders: HeaderRecord) => Promise<boolean>) | null;
64
+ };
65
+ type PagesPipelineResult = {
66
+ type: "response";
67
+ response: Response;
68
+ defaultContentType?: string;
69
+ } | {
70
+ type: "handled";
71
+ } | {
72
+ type: "render";
73
+ resolvedUrl: string;
74
+ renderOptions: PagesRenderOptions | undefined;
75
+ stagedHeaders: HeaderRecord; /** Post-middleware request headers — dev adapters apply these to req.headers before SSR. */
76
+ requestHeaders: Headers;
77
+ middlewareStatus: number | undefined;
78
+ isDataReq: boolean;
79
+ } | {
80
+ type: "api";
81
+ apiUrl: string;
82
+ stagedHeaders: HeaderRecord; /** Post-middleware request headers — dev adapters apply these to req.headers before API handler. */
83
+ requestHeaders: Headers;
84
+ middlewareStatus: number | undefined;
85
+ } | {
86
+ type: "next";
87
+ };
88
+ /**
89
+ * Run the Pages Router request pipeline.
90
+ *
91
+ * ASSUMPTION: request already has internal headers filtered and basePath stripped.
92
+ * The adapter is responsible for that pre-processing before calling runPagesRequest.
93
+ * The adapter also handles: open-redirect guard, _next/static 404, image optimization,
94
+ * _next/data normalization, Node decode/normalize/400, public-file serving.
95
+ * runPagesRequest receives a "clean" request with basePath-stripped URL.
96
+ */
97
+ declare function runPagesRequest(request: Request, deps: PagesPipelineDeps): Promise<PagesPipelineResult>;
98
+ //#endregion
99
+ export { MiddlewareResult, PagesPipelineDeps, PagesPipelineResult, PagesRenderOptions, runPagesRequest };
@@ -0,0 +1,209 @@
1
+ import { hasBasePath } from "../utils/base-path.js";
2
+ import { applyMiddlewareRequestHeaders, isExternalUrl, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
3
+ import { applyConfigHeadersToHeaderRecord, normalizeTrailingSlash } from "./request-pipeline.js";
4
+ import { mergeRewriteQuery } from "../utils/query.js";
5
+ import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
6
+ import { mergeHeaders } from "./worker-utils.js";
7
+ //#region src/server/pages-request-pipeline.ts
8
+ /**
9
+ * Run the Pages Router request pipeline.
10
+ *
11
+ * ASSUMPTION: request already has internal headers filtered and basePath stripped.
12
+ * The adapter is responsible for that pre-processing before calling runPagesRequest.
13
+ * The adapter also handles: open-redirect guard, _next/static 404, image optimization,
14
+ * _next/data normalization, Node decode/normalize/400, public-file serving.
15
+ * runPagesRequest receives a "clean" request with basePath-stripped URL.
16
+ */
17
+ async function runPagesRequest(request, deps) {
18
+ const { basePath, trailingSlash, i18nConfig, configRedirects, configRewrites, configHeaders, hadBasePath, isDataReq, isDataRequest } = deps;
19
+ const proxyExternal = (currentReq, externalUrl) => deps.proxyExternal ? deps.proxyExternal(currentReq, externalUrl) : proxyExternalRequest(currentReq, externalUrl);
20
+ const url = new URL(request.url);
21
+ let pathname = url.pathname;
22
+ const search = url.search;
23
+ const basePathState = {
24
+ basePath,
25
+ hadBasePath
26
+ };
27
+ {
28
+ const trailingSlashRedirect = normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
29
+ if (trailingSlashRedirect) return {
30
+ type: "response",
31
+ response: trailingSlashRedirect
32
+ };
33
+ }
34
+ const reqCtx = requestContextFromRequest(request);
35
+ const requestHostname = i18nConfig ? url.hostname : "";
36
+ const matchPathname = i18nConfig ? normalizeDefaultLocalePathname(pathname, i18nConfig, { hostname: requestHostname }) : pathname;
37
+ if (configRedirects.length) {
38
+ const redirect = matchRedirect(matchPathname, configRedirects, reqCtx, basePathState);
39
+ if (redirect) {
40
+ const location = preserveRedirectDestinationQuery(sanitizeDestination(basePath && hadBasePath && !isExternalUrl(redirect.destination) && !hasBasePath(redirect.destination, basePath) ? basePath + redirect.destination : redirect.destination), deps.rawSearch ?? search);
41
+ return {
42
+ type: "response",
43
+ response: new Response(null, {
44
+ status: redirect.permanent ? 308 : 307,
45
+ headers: { Location: location }
46
+ })
47
+ };
48
+ }
49
+ }
50
+ let resolvedUrl = pathname + search;
51
+ const middlewareHeaders = {};
52
+ let middlewareStatus;
53
+ if (typeof deps.runMiddleware === "function") {
54
+ const result = await deps.runMiddleware(request, deps.ctx ?? null, { isDataRequest });
55
+ if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
56
+ const ctx = deps.ctx;
57
+ if (ctx && typeof ctx.waitUntil === "function") for (const p of result.waitUntilPromises) ctx.waitUntil(p);
58
+ else Promise.allSettled(result.waitUntilPromises);
59
+ }
60
+ if (!result.continue) {
61
+ if (result.redirectUrl) {
62
+ const redirectHeaders = { Location: result.redirectUrl };
63
+ if (result.responseHeaders) for (const [key, value] of result.responseHeaders) {
64
+ const existing = redirectHeaders[key];
65
+ if (existing === void 0) redirectHeaders[key] = value;
66
+ else if (Array.isArray(existing)) existing.push(value);
67
+ else redirectHeaders[key] = [existing, value];
68
+ }
69
+ const headers = new Headers();
70
+ for (const [k, v] of Object.entries(redirectHeaders)) if (Array.isArray(v)) for (const item of v) headers.append(k, item);
71
+ else headers.set(k, v);
72
+ return {
73
+ type: "response",
74
+ response: new Response(null, {
75
+ status: result.redirectStatus ?? 307,
76
+ headers
77
+ })
78
+ };
79
+ }
80
+ if (result.response) return {
81
+ type: "response",
82
+ response: result.response
83
+ };
84
+ }
85
+ if (result.responseHeaders) for (const [key, value] of result.responseHeaders) if (key === "set-cookie") {
86
+ const existing = middlewareHeaders[key];
87
+ if (Array.isArray(existing)) existing.push(value);
88
+ else if (existing) middlewareHeaders[key] = [existing, value];
89
+ else middlewareHeaders[key] = [value];
90
+ } else middlewareHeaders[key] = value;
91
+ if (result.rewriteUrl) resolvedUrl = result.rewriteUrl;
92
+ middlewareStatus = result.status ?? result.rewriteStatus;
93
+ }
94
+ const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, request, { preserveCredentialHeaders: isExternalUrl(resolvedUrl) });
95
+ request = postMwReq;
96
+ let resolvedPathname = resolvedUrl.split("?")[0];
97
+ const matchResolvedPathname = (p) => i18nConfig ? normalizeDefaultLocalePathname(p, i18nConfig, { hostname: requestHostname }) : p;
98
+ if (configHeaders.length) applyConfigHeadersToHeaderRecord(middlewareHeaders, {
99
+ configHeaders,
100
+ pathname: matchPathname,
101
+ requestContext: reqCtx,
102
+ basePathState
103
+ });
104
+ if (isExternalUrl(resolvedUrl)) return {
105
+ type: "response",
106
+ response: mergeHeaders(await proxyExternal(request, resolvedUrl), middlewareHeaders, void 0)
107
+ };
108
+ if (deps.serveStaticFile) {
109
+ if (await deps.serveStaticFile(pathname, middlewareHeaders)) return { type: "handled" };
110
+ }
111
+ let configRewriteFired = false;
112
+ if (configRewrites.beforeFiles?.length) {
113
+ const rewritten = matchRewrite(matchResolvedPathname(resolvedPathname), configRewrites.beforeFiles, postMwReqCtx, basePathState);
114
+ if (rewritten) {
115
+ if (isExternalUrl(rewritten)) return {
116
+ type: "response",
117
+ response: await proxyExternal(request, rewritten)
118
+ };
119
+ resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
120
+ resolvedPathname = resolvedUrl.split("?")[0];
121
+ configRewriteFired = true;
122
+ }
123
+ }
124
+ if (basePath && !hadBasePath && !configRewriteFired) return {
125
+ type: "response",
126
+ response: new Response("This page could not be found", {
127
+ status: 404,
128
+ headers: { "Content-Type": "text/html; charset=utf-8" }
129
+ })
130
+ };
131
+ const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, i18nConfig);
132
+ const apiLookupPathname = apiLookupUrl.split("?")[0];
133
+ if (apiLookupPathname.startsWith("/api/") || apiLookupPathname === "/api") if (typeof deps.handleApi === "function") return {
134
+ type: "response",
135
+ defaultContentType: "application/octet-stream",
136
+ response: mergeHeaders(await deps.handleApi(request, apiLookupUrl, deps.ctx ?? null), middlewareHeaders, middlewareStatus)
137
+ };
138
+ else return {
139
+ type: "api",
140
+ apiUrl: apiLookupUrl,
141
+ stagedHeaders: middlewareHeaders,
142
+ requestHeaders: request.headers,
143
+ middlewareStatus
144
+ };
145
+ const pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
146
+ let resolvedPathnameChanged = false;
147
+ if ((!pageMatch || pageMatch.route.isDynamic) && configRewrites.afterFiles?.length) {
148
+ const rewritten = matchRewrite(matchResolvedPathname(resolvedPathname), configRewrites.afterFiles, postMwReqCtx, basePathState);
149
+ if (rewritten) {
150
+ if (isExternalUrl(rewritten)) return {
151
+ type: "response",
152
+ response: await proxyExternal(request, rewritten)
153
+ };
154
+ resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
155
+ resolvedPathname = resolvedUrl.split("?")[0];
156
+ resolvedPathnameChanged = true;
157
+ }
158
+ }
159
+ if (typeof deps.renderPage === "function") {
160
+ const renderPageMatch = resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch;
161
+ const shouldDeferErrorPageOnMiss = !isDataReq && !isDataRequest && !!deps.matchPageRoute && !renderPageMatch;
162
+ const initialRenderOptions = shouldDeferErrorPageOnMiss ? { renderErrorPageOnMiss: false } : isDataReq ? { isDataReq: true } : void 0;
163
+ const stagedHeaders = new Headers();
164
+ for (const [k, v] of Object.entries(middlewareHeaders)) if (Array.isArray(v)) for (const item of v) stagedHeaders.append(k, item);
165
+ else stagedHeaders.set(k, v);
166
+ let response = await deps.renderPage(request, resolvedUrl, initialRenderOptions, stagedHeaders);
167
+ let matchedFallbackRewrite = false;
168
+ if (response.status === 404 && shouldDeferErrorPageOnMiss && configRewrites.fallback?.length) {
169
+ const fallbackRewrite = matchRewrite(matchResolvedPathname(resolvedPathname), configRewrites.fallback, postMwReqCtx, basePathState);
170
+ if (fallbackRewrite) {
171
+ if (isExternalUrl(fallbackRewrite)) return {
172
+ type: "response",
173
+ response: await proxyExternal(request, fallbackRewrite)
174
+ };
175
+ response = await deps.renderPage(request, mergeRewriteQuery(resolvedUrl, fallbackRewrite), void 0, stagedHeaders);
176
+ matchedFallbackRewrite = true;
177
+ }
178
+ }
179
+ if (response.status === 404 && shouldDeferErrorPageOnMiss && !matchedFallbackRewrite) response = await deps.renderPage(request, resolvedUrl, void 0, stagedHeaders);
180
+ const merged = mergeHeaders(response, middlewareHeaders, middlewareStatus);
181
+ if (merged !== response) merged.__vinextStreamedHtmlResponse = response.__vinextStreamedHtmlResponse;
182
+ return {
183
+ type: "response",
184
+ response: merged,
185
+ defaultContentType: "text/html"
186
+ };
187
+ }
188
+ if (!(resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch) && configRewrites.fallback?.length) {
189
+ const fallbackRewrite = matchRewrite(matchResolvedPathname(resolvedPathname), configRewrites.fallback, postMwReqCtx, basePathState);
190
+ if (fallbackRewrite) {
191
+ if (isExternalUrl(fallbackRewrite)) return {
192
+ type: "response",
193
+ response: await proxyExternal(request, fallbackRewrite)
194
+ };
195
+ resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
196
+ }
197
+ }
198
+ return {
199
+ type: "render",
200
+ resolvedUrl,
201
+ renderOptions: isDataReq ? { isDataReq: true } : void 0,
202
+ stagedHeaders: middlewareHeaders,
203
+ requestHeaders: request.headers,
204
+ middlewareStatus,
205
+ isDataReq
206
+ };
207
+ }
208
+ //#endregion
209
+ export { runPagesRequest };
@@ -0,0 +1,15 @@
1
+ import { IncomingMessage } from "node:http";
2
+
3
+ //#region src/server/pages-revalidate.d.ts
4
+ type RevalidateOptions = {
5
+ /**
6
+ * Only revalidate the path if it was already generated (cached). Mirrors
7
+ * Next.js's `unstable_onlyGenerated`: sets the
8
+ * `x-prerender-revalidate-if-generated` header and makes a 404 response count
9
+ * as a successful no-op rather than an error.
10
+ */
11
+ unstable_onlyGenerated?: boolean;
12
+ };
13
+ declare function performOnDemandRevalidate(source: IncomingMessage | Headers, urlPath: string, opts?: RevalidateOptions): Promise<void>;
14
+ //#endregion
15
+ export { RevalidateOptions, performOnDemandRevalidate };
@@ -0,0 +1,19 @@
1
+ import "./headers.js";
2
+ import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, getRevalidateSecret } from "./isr-cache.js";
3
+ import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
4
+ //#region src/server/pages-revalidate.ts
5
+ async function performOnDemandRevalidate(source, urlPath, opts = {}) {
6
+ if (typeof urlPath !== "string" || !urlPath.startsWith("/")) throw new Error(`Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received ${urlPath}`);
7
+ const proto = resolveRequestProtocol(source);
8
+ const host = resolveRequestHost(source, "localhost");
9
+ const target = new URL(urlPath, `${proto}://${host}`);
10
+ const headers = { [PRERENDER_REVALIDATE_HEADER]: getRevalidateSecret() };
11
+ if (opts.unstable_onlyGenerated) headers[PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER] = "1";
12
+ const res = await fetch(target, {
13
+ method: "HEAD",
14
+ headers
15
+ });
16
+ if (!(res.headers.get("x-nextjs-cache")?.toUpperCase() === "REVALIDATED" || res.status === 200 || res.status === 404 && opts.unstable_onlyGenerated === true)) throw new Error(`Failed to revalidate ${urlPath}: ${res.status}`);
17
+ }
18
+ //#endregion
19
+ export { performOnDemandRevalidate };
@@ -29,8 +29,12 @@ declare function mergeResponseHeaders(middlewareHeaders: Record<string, string |
29
29
  /**
30
30
  * Merge middleware/config headers and an optional status override into a new
31
31
  * Web Response while preserving the original body stream when allowed.
32
- * Keep this in sync with server/worker-utils.ts and the generated copy in
33
- * deploy.ts.
32
+ *
33
+ * This is the canonical {@link mergeHeaders} (server/worker-utils.ts) with the
34
+ * arguments in (headers, response) order. The request path now calls
35
+ * `runPagesRequest`, which uses `mergeHeaders` directly; this wrapper is retained
36
+ * only for its existing tests and any external callers, so there is a single
37
+ * implementation to keep in sync. (deploy.ts still emits its own generated copy.)
34
38
  */
35
39
  declare function mergeWebResponse(middlewareHeaders: Record<string, string | string[]>, response: Response, statusOverride?: number): Response;
36
40
  /**