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.
- package/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +27 -1
- package/dist/build/client-build-config.js +58 -1
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +8 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/config/config-matchers.d.ts +20 -1
- package/dist/config/config-matchers.js +35 -1
- package/dist/config/next-config.d.ts +16 -3
- package/dist/config/next-config.js +30 -2
- package/dist/deploy.js +40 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +54 -4
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-server-entry.js +9 -1
- package/dist/index.js +162 -217
- package/dist/plugins/postcss.js +18 -14
- package/dist/plugins/require-context.d.ts +6 -0
- package/dist/plugins/require-context.js +184 -0
- package/dist/routing/app-route-graph.d.ts +12 -1
- package/dist/routing/app-route-graph.js +137 -5
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +16 -1
- package/dist/server/api-handler.js +4 -0
- package/dist/server/app-browser-entry.js +84 -39
- package/dist/server/app-browser-interception-context.d.ts +2 -1
- package/dist/server/app-browser-interception-context.js +15 -2
- package/dist/server/app-browser-navigation-controller.d.ts +11 -1
- package/dist/server/app-browser-navigation-controller.js +77 -1
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +6 -3
- package/dist/server/app-browser-visible-commit.js +9 -7
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-page-boundary-render.js +41 -19
- package/dist/server/app-page-dispatch.d.ts +6 -0
- package/dist/server/app-page-dispatch.js +3 -1
- package/dist/server/app-page-element-builder.d.ts +1 -0
- package/dist/server/app-page-element-builder.js +22 -10
- package/dist/server/app-page-render.d.ts +6 -0
- package/dist/server/app-page-render.js +5 -3
- package/dist/server/app-page-request.d.ts +8 -6
- package/dist/server/app-page-request.js +12 -9
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +2 -1
- package/dist/server/app-page-stream.d.ts +37 -2
- package/dist/server/app-page-stream.js +36 -3
- package/dist/server/app-pages-bridge.d.ts +16 -0
- package/dist/server/app-pages-bridge.js +23 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +2 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +3 -2
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +4 -3
- package/dist/server/app-rsc-route-matching.d.ts +20 -1
- package/dist/server/app-rsc-route-matching.js +29 -4
- package/dist/server/app-server-action-execution.d.ts +11 -1
- package/dist/server/app-server-action-execution.js +68 -10
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +17 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +54 -31
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/navigation-planner.js +5 -3
- package/dist/server/navigation-trace.d.ts +1 -1
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +4 -0
- package/dist/server/pages-page-data.d.ts +10 -7
- package/dist/server/pages-page-data.js +4 -2
- package/dist/server/pages-page-handler.d.ts +9 -2
- package/dist/server/pages-page-handler.js +29 -16
- package/dist/server/pages-page-response.d.ts +11 -2
- package/dist/server/pages-page-response.js +8 -1
- package/dist/server/pages-readiness.d.ts +36 -0
- package/dist/server/pages-readiness.js +21 -0
- package/dist/server/pages-request-pipeline.d.ts +99 -0
- package/dist/server/pages-request-pipeline.js +209 -0
- package/dist/server/pages-revalidate.d.ts +15 -0
- package/dist/server/pages-revalidate.js +19 -0
- package/dist/server/prod-server.d.ts +6 -2
- package/dist/server/prod-server.js +101 -217
- package/dist/server/socket-error-backstop.d.ts +19 -1
- package/dist/server/socket-error-backstop.js +77 -4
- package/dist/shims/app-router-scroll.js +22 -4
- package/dist/shims/cache-runtime.js +31 -1
- package/dist/shims/error-boundary.d.ts +21 -11
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/fetch-cache.d.ts +14 -1
- package/dist/shims/fetch-cache.js +18 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/internal/link-status-registry.d.ts +43 -0
- package/dist/shims/internal/link-status-registry.js +42 -0
- package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
- package/dist/shims/internal/route-pattern-for-warning.js +40 -0
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/link.js +20 -6
- package/dist/shims/navigation.d.ts +2 -2
- package/dist/shims/navigation.js +63 -7
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +2 -0
- package/dist/shims/router.d.ts +6 -3
- package/dist/shims/router.js +128 -21
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +30 -5
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- 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; /**
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
*
|
|
33
|
-
*
|
|
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
|
/**
|