vinext 1.0.0-beta.2 → 1.0.0-beta.3
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/README.md +6 -9
- package/dist/build/client-build-config.js +7 -1
- package/dist/check.js +4 -0
- package/dist/config/config-matchers.js +4 -1
- package/dist/config/next-config.d.ts +12 -3
- package/dist/config/next-config.js +4 -0
- package/dist/entries/app-browser-entry.d.ts +4 -10
- package/dist/entries/app-browser-entry.js +20 -4
- package/dist/entries/app-rsc-entry.js +3 -0
- package/dist/entries/app-rsc-manifest.js +19 -0
- package/dist/entries/pages-client-entry.js +0 -1
- package/dist/entries/pages-server-entry.js +10 -3
- package/dist/index.js +112 -43
- package/dist/init-cloudflare.d.ts +3 -1
- package/dist/init-cloudflare.js +59 -5
- package/dist/plugins/extensionless-dynamic-import.js +23 -18
- package/dist/plugins/ignore-dynamic-requests.js +3 -1
- package/dist/plugins/import-meta-url.js +17 -6
- package/dist/plugins/require-context.js +20 -15
- package/dist/plugins/transform-cache.d.ts +19 -0
- package/dist/plugins/transform-cache.js +36 -0
- package/dist/routing/app-route-graph.d.ts +14 -6
- package/dist/routing/app-route-graph.js +106 -16
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +3 -3
- package/dist/server/app-browser-entry.js +103 -31
- package/dist/server/app-browser-navigation-controller.d.ts +23 -21
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-page-cache-finalizer.js +1 -1
- package/dist/server/app-page-cache.d.ts +1 -1
- package/dist/server/app-page-cache.js +16 -2
- package/dist/server/app-page-dispatch.d.ts +8 -0
- package/dist/server/app-page-dispatch.js +31 -6
- package/dist/server/app-page-element-builder.d.ts +2 -0
- package/dist/server/app-page-element-builder.js +33 -7
- package/dist/server/app-page-head.d.ts +3 -3
- package/dist/server/app-page-head.js +58 -13
- package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
- package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
- package/dist/server/app-page-probe.d.ts +4 -0
- package/dist/server/app-page-probe.js +6 -2
- package/dist/server/app-page-request.d.ts +3 -0
- package/dist/server/app-page-request.js +1 -1
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +7 -0
- package/dist/server/app-page-route-wiring.js +211 -49
- package/dist/server/app-pages-bridge.d.ts +1 -1
- package/dist/server/app-pages-bridge.js +2 -1
- package/dist/server/app-route-handler-cache.js +4 -0
- package/dist/server/app-route-handler-dispatch.js +9 -3
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-route-module-loader.d.ts +6 -0
- package/dist/server/app-route-module-loader.js +3 -0
- package/dist/server/app-router-entry.js +6 -3
- package/dist/server/app-rsc-errors.js +10 -0
- package/dist/server/app-rsc-handler.js +21 -9
- package/dist/server/app-rsc-route-matching.d.ts +9 -0
- package/dist/server/app-rsc-route-matching.js +6 -0
- package/dist/server/app-ssr-entry.js +4 -4
- package/dist/server/cache-control.d.ts +2 -2
- package/dist/server/cache-control.js +2 -1
- package/dist/server/dev-server.d.ts +3 -1
- package/dist/server/dev-server.js +145 -312
- package/dist/server/headers.d.ts +2 -2
- package/dist/server/headers.js +4 -3
- package/dist/server/isr-cache.d.ts +10 -33
- package/dist/server/isr-cache.js +33 -43
- package/dist/server/isr-decision.d.ts +2 -2
- package/dist/server/pages-api-route.d.ts +2 -0
- package/dist/server/pages-api-route.js +4 -1
- package/dist/server/pages-dev-hydration.js +0 -1
- package/dist/server/pages-get-initial-props.d.ts +1 -1
- package/dist/server/pages-get-initial-props.js +1 -4
- package/dist/server/pages-i18n.js +64 -16
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +7 -3
- package/dist/server/pages-page-data.d.ts +61 -9
- package/dist/server/pages-page-data.js +334 -74
- package/dist/server/pages-page-handler.d.ts +5 -1
- package/dist/server/pages-page-handler.js +115 -19
- package/dist/server/pages-page-response.d.ts +5 -3
- package/dist/server/pages-page-response.js +23 -26
- package/dist/server/pages-request-pipeline.d.ts +7 -0
- package/dist/server/pages-request-pipeline.js +5 -1
- package/dist/server/pages-revalidate.d.ts +1 -1
- package/dist/server/pages-revalidate.js +54 -8
- package/dist/server/pages-router-entry.d.ts +1 -0
- package/dist/server/pages-router-entry.js +10 -4
- package/dist/server/prod-server.d.ts +2 -1
- package/dist/server/prod-server.js +37 -13
- package/dist/server/revalidation-host.d.ts +11 -0
- package/dist/server/revalidation-host.js +19 -0
- package/dist/server/worker-revalidation-context.d.ts +13 -0
- package/dist/server/worker-revalidation-context.js +29 -0
- package/dist/shims/cache-handler.d.ts +1 -1
- package/dist/shims/cache-handler.js +10 -7
- package/dist/shims/cache-runtime.d.ts +6 -0
- package/dist/shims/cache-runtime.js +4 -1
- package/dist/shims/cache.js +1 -1
- package/dist/shims/error.js +1 -1
- package/dist/shims/fetch-cache.js +1 -1
- package/dist/shims/font-google-base.js +17 -8
- package/dist/shims/font-local.js +41 -10
- package/dist/shims/headers.d.ts +10 -13
- package/dist/shims/headers.js +85 -36
- package/dist/shims/internal/cookie-serialize.d.ts +10 -9
- package/dist/shims/internal/cookie-serialize.js +9 -8
- package/dist/shims/internal/interpolate-as.d.ts +15 -1
- package/dist/shims/internal/interpolate-as.js +27 -2
- package/dist/shims/link.js +18 -6
- package/dist/shims/metadata.d.ts +7 -17
- package/dist/shims/metadata.js +74 -24
- package/dist/shims/navigation.js +1 -1
- package/dist/shims/request-context.d.ts +4 -1
- package/dist/shims/router.js +18 -7
- package/dist/shims/script.js +1 -1
- package/dist/shims/server.d.ts +7 -6
- package/dist/shims/server.js +156 -57
- package/dist/shims/unified-request-context.d.ts +21 -2
- package/dist/shims/unified-request-context.js +82 -1
- package/dist/utils/cache-control-metadata.d.ts +2 -1
- package/dist/utils/cache-control-metadata.js +5 -1
- package/dist/utils/html-limited-bots.js +1 -1
- package/dist/utils/protocol-headers.d.ts +7 -1
- package/dist/utils/protocol-headers.js +7 -1
- package/package.json +1 -1
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
1
|
+
import { closeAfterResponse, closeAfterResponseWithBody, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
2
2
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
3
3
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
4
|
-
import {
|
|
4
|
+
import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
|
|
5
|
+
import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_CACHE_HEADER } from "./headers.js";
|
|
5
6
|
import { reportRequestError } from "./instrumentation.js";
|
|
6
7
|
import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
|
|
7
8
|
import { buildMissIsrCacheControl } from "./isr-decision.js";
|
|
8
|
-
import {
|
|
9
|
+
import { coalesceOnDemandRevalidation, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, triggerBackgroundRegeneration } from "./isr-cache.js";
|
|
10
|
+
import { encodeCacheTag } from "../utils/encode-cache-tag.js";
|
|
9
11
|
import { ensureFetchPatch } from "../shims/fetch-cache.js";
|
|
10
|
-
import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
|
|
11
12
|
import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
|
|
12
13
|
import { getScriptNonceFromHeaderSources } from "./csp.js";
|
|
13
14
|
import { extractLocaleFromUrl, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
14
15
|
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
15
16
|
import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
16
17
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
18
|
+
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
17
19
|
import { renderPagesPageResponse } from "./pages-page-response.js";
|
|
18
20
|
import { hasPagesGetInitialProps } from "./pages-get-initial-props.js";
|
|
19
21
|
import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
|
|
@@ -25,7 +27,12 @@ import { createPagesReqRes } from "./pages-node-compat.js";
|
|
|
25
27
|
function finalizePagesPreviewResponse(response, preview) {
|
|
26
28
|
if (preview.data === false && !preview.shouldClear) return response;
|
|
27
29
|
const headers = new Headers(response.headers);
|
|
28
|
-
if (preview.data !== false)
|
|
30
|
+
if (preview.data !== false) {
|
|
31
|
+
headers.set("Cache-Control", PAGES_PREVIEW_CACHE_CONTROL);
|
|
32
|
+
headers.delete("CDN-Cache-Control");
|
|
33
|
+
headers.delete("Cloudflare-CDN-Cache-Control");
|
|
34
|
+
headers.delete("Cache-Tag");
|
|
35
|
+
}
|
|
29
36
|
if (preview.shouldClear) appendPagesPreviewClearCookies(headers);
|
|
30
37
|
return new Response(response.body, {
|
|
31
38
|
headers,
|
|
@@ -33,6 +40,41 @@ function finalizePagesPreviewResponse(response, preview) {
|
|
|
33
40
|
statusText: response.statusText
|
|
34
41
|
});
|
|
35
42
|
}
|
|
43
|
+
function withPagesCacheState(response, state) {
|
|
44
|
+
const headers = new Headers(response.headers);
|
|
45
|
+
if (state === "REVALIDATED") {
|
|
46
|
+
headers.set(NEXTJS_CACHE_HEADER, state);
|
|
47
|
+
headers.delete(VINEXT_CACHE_HEADER);
|
|
48
|
+
} else setCacheStateHeaders(headers, state);
|
|
49
|
+
return new Response(response.body, {
|
|
50
|
+
headers,
|
|
51
|
+
status: response.status,
|
|
52
|
+
statusText: response.statusText
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function applyPagesErrorCachePolicy(response, revalidateSeconds, expireSeconds, cacheTagPathname) {
|
|
56
|
+
const headers = new Headers(response.headers);
|
|
57
|
+
const browserPolicy = headers.get("Cache-Control");
|
|
58
|
+
const sharedPolicies = [headers.get("CDN-Cache-Control"), headers.get("Cloudflare-CDN-Cache-Control")];
|
|
59
|
+
const hasCacheableSharedPolicy = sharedPolicies.some((value) => value && /(?:^|,)\s*s-maxage\s*=/i.test(value));
|
|
60
|
+
if (sharedPolicies.some((value) => value && /(?:private|no-store|no-cache)/i.test(value)) || !hasCacheableSharedPolicy && browserPolicy && /(?:private|no-store|no-cache)/i.test(browserPolicy)) return response;
|
|
61
|
+
headers.delete("CDN-Cache-Control");
|
|
62
|
+
headers.delete("Cloudflare-CDN-Cache-Control");
|
|
63
|
+
headers.delete("Cache-Tag");
|
|
64
|
+
if (revalidateSeconds === void 0) applyCdnResponseHeaders(headers, { cacheControl: NEVER_CACHE_CONTROL });
|
|
65
|
+
else {
|
|
66
|
+
const stem = cacheTagPathname.endsWith("/") ? cacheTagPathname.slice(0, -1) : cacheTagPathname;
|
|
67
|
+
applyCdnResponseHeaders(headers, {
|
|
68
|
+
cacheControl: buildMissIsrCacheControl(revalidateSeconds, expireSeconds),
|
|
69
|
+
tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return new Response(response.body, {
|
|
73
|
+
headers,
|
|
74
|
+
status: response.status,
|
|
75
|
+
statusText: response.statusText
|
|
76
|
+
});
|
|
77
|
+
}
|
|
36
78
|
function shouldEmitPagesClientTraceMetadata(pageModule, appComponent) {
|
|
37
79
|
if (typeof pageModule.getServerSideProps === "function") return true;
|
|
38
80
|
if (typeof pageModule.getStaticProps === "function") return false;
|
|
@@ -122,6 +164,7 @@ function createPagesPageHandler(opts) {
|
|
|
122
164
|
route: options.__forcedRoute,
|
|
123
165
|
params: {}
|
|
124
166
|
} : matchRoute(routeUrl, pageRoutes);
|
|
167
|
+
let isRouteMissErrorRender = false;
|
|
125
168
|
let renderStatusCodeOverride = statusCode;
|
|
126
169
|
let renderAsPath = asPath;
|
|
127
170
|
if (!match) {
|
|
@@ -133,6 +176,7 @@ function createPagesPageHandler(opts) {
|
|
|
133
176
|
route: notFoundRoute,
|
|
134
177
|
params: {}
|
|
135
178
|
};
|
|
179
|
+
isRouteMissErrorRender = true;
|
|
136
180
|
renderStatusCodeOverride = 404;
|
|
137
181
|
renderAsPath = routeUrl;
|
|
138
182
|
} else return buildDefaultPagesNotFoundResponse();
|
|
@@ -141,16 +185,43 @@ function createPagesPageHandler(opts) {
|
|
|
141
185
|
const pageModule = route.module;
|
|
142
186
|
const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
|
|
143
187
|
const isStaticPropsRender = isStaticPropsRoute && typeof pageModule.getServerSideProps !== "function";
|
|
188
|
+
if (!options?.__skipOnDemandCoalesce && !options?.__forcedRoute && isStaticPropsRoute && isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate"))) {
|
|
189
|
+
const snapshot = await coalesceOnDemandRevalidation(pageIsrCacheKey("pages", routeUrl.split("?")[0]), async () => {
|
|
190
|
+
const response = await renderPage(request, url, manifest, middlewareHeaders, {
|
|
191
|
+
...options,
|
|
192
|
+
__skipOnDemandCoalesce: true
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
body: request.method === "HEAD" || response.status === 204 || response.status === 304 ? null : new Uint8Array(await response.arrayBuffer()),
|
|
196
|
+
headers: [...response.headers.entries()],
|
|
197
|
+
status: response.status,
|
|
198
|
+
statusText: response.statusText
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
return new Response(snapshot.body?.slice() ?? null, {
|
|
202
|
+
headers: snapshot.headers,
|
|
203
|
+
status: snapshot.status,
|
|
204
|
+
statusText: snapshot.statusText
|
|
205
|
+
});
|
|
206
|
+
}
|
|
144
207
|
const renderRouteUrl = isStaticPropsRender ? routeUrl.split("?")[0] : routeUrl;
|
|
145
208
|
const routerAsPathSource = isStaticPropsRender ? renderRouteUrl : renderAsPath ?? renderRouteUrl;
|
|
146
209
|
const routerAsPath = i18nConfig ? extractLocaleFromUrl(routerAsPathSource, i18nConfig, locale).url : routerAsPathSource;
|
|
147
|
-
|
|
210
|
+
const uCtx = createRequestContext({ executionContext: getRequestExecutionContext() });
|
|
211
|
+
return closeAfterResponseWithBody(await runWithRequestContext(uCtx, async () => {
|
|
148
212
|
ensureFetchPatch();
|
|
149
213
|
try {
|
|
150
214
|
const routePattern = patternToNextFormat(route.pattern);
|
|
151
215
|
const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
|
|
216
|
+
const isrCachePathname = isStaticPropsRender && (routePattern === "/404" || routePattern === "/500" || routePattern === "/_error") ? routePattern : renderRouteUrl.split("?")[0];
|
|
217
|
+
const isNotFoundErrorRender = routePattern === "/404" || routePattern === "/_error" && renderStatusCode === 404;
|
|
218
|
+
const isStatusErrorRender = isNotFoundErrorRender || routePattern === "/500" || routePattern === "/_error" && renderStatusCode === 500;
|
|
219
|
+
const errorPageRevalidateSeconds = isNotFoundErrorRender ? options?.__notFoundRevalidateSeconds : void 0;
|
|
220
|
+
const errorPageExpireSeconds = isNotFoundErrorRender ? options?.__notFoundExpireSeconds : void 0;
|
|
221
|
+
const errorResponseCachePathname = options?.__notFoundCachePathname ?? isrCachePathname;
|
|
152
222
|
const query = mergeRouteParamsIntoQuery(parseQueryString(renderRouteUrl), params);
|
|
153
223
|
const isOnDemandRevalidate = isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER));
|
|
224
|
+
const revalidateOnlyGenerated = isOnDemandRevalidate && request.headers.has("x-prerender-revalidate-if-generated");
|
|
154
225
|
const preview = isStaticPropsRoute || typeof pageModule.getServerSideProps === "function" ? getPagesPreviewState(request.headers.get("cookie"), { isOnDemandRevalidate }) : {
|
|
155
226
|
data: false,
|
|
156
227
|
shouldClear: false
|
|
@@ -211,11 +282,12 @@ function createPagesPageHandler(opts) {
|
|
|
211
282
|
}
|
|
212
283
|
};
|
|
213
284
|
const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareHeaders);
|
|
285
|
+
const shouldApplyErrorResponsePolicy = previewData === false && !scriptNonce && isStatusErrorRender && (isStaticPropsRoute || isRouteMissErrorRender || options?.__forcedRoute !== void 0);
|
|
214
286
|
let fontLinkHeader = "";
|
|
215
287
|
let allFontPreloads = [];
|
|
216
288
|
try {
|
|
217
289
|
allFontPreloads = getFontPreloads();
|
|
218
|
-
if (allFontPreloads.length > 0) fontLinkHeader = allFontPreloads.map((p) => "<" +
|
|
290
|
+
if (allFontPreloads.length > 0) fontLinkHeader = allFontPreloads.map((p) => "<" + p.href + ">; rel=preload; as=font; type=" + p.type + "; crossorigin").join(", ");
|
|
219
291
|
} catch {}
|
|
220
292
|
const pagesResolvedUrl = (new URL(routeUrl, originalRequestUrl).pathname || "/") + originalRequestUrl.search;
|
|
221
293
|
const createPageReqRes = () => {
|
|
@@ -232,6 +304,7 @@ function createPagesPageHandler(opts) {
|
|
|
232
304
|
isDataReq,
|
|
233
305
|
err: err instanceof Error ? err : void 0,
|
|
234
306
|
applyRequestContexts: applySSRContext,
|
|
307
|
+
basePath: vinextConfig.basePath,
|
|
235
308
|
buildId,
|
|
236
309
|
deploymentId: process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID,
|
|
237
310
|
htmlLimitedBots: vinextConfig.htmlLimitedBots,
|
|
@@ -253,6 +326,7 @@ function createPagesPageHandler(opts) {
|
|
|
253
326
|
isBuildTimePrerendering: typeof process !== "undefined" && process.env && process.env.VINEXT_PRERENDER === "1",
|
|
254
327
|
validatePropsSerialization: process.env.NODE_ENV !== "production" || process.env.VINEXT_PRERENDER === "1",
|
|
255
328
|
isOnDemandRevalidate,
|
|
329
|
+
revalidateOnlyGenerated,
|
|
256
330
|
previewData,
|
|
257
331
|
pageModule,
|
|
258
332
|
AppComponent,
|
|
@@ -265,10 +339,16 @@ function createPagesPageHandler(opts) {
|
|
|
265
339
|
route: { isDynamic: route.isDynamic },
|
|
266
340
|
routePattern,
|
|
267
341
|
routeUrl: renderRouteUrl,
|
|
342
|
+
isrCachePathname,
|
|
268
343
|
runInFreshUnifiedContext(callback) {
|
|
269
|
-
|
|
344
|
+
const revalCtx = createRequestContext({ executionContext: null });
|
|
345
|
+
return runWithRequestContext(revalCtx, async () => {
|
|
270
346
|
ensureFetchPatch();
|
|
271
|
-
|
|
347
|
+
try {
|
|
348
|
+
return await callback();
|
|
349
|
+
} finally {
|
|
350
|
+
await closeAfterResponse(revalCtx);
|
|
351
|
+
}
|
|
272
352
|
});
|
|
273
353
|
},
|
|
274
354
|
safeJsonStringify,
|
|
@@ -284,15 +364,26 @@ function createPagesPageHandler(opts) {
|
|
|
284
364
|
});
|
|
285
365
|
if (pageDataResult.kind === "notFound") {
|
|
286
366
|
const notFoundRoute = findNotFoundRoute();
|
|
287
|
-
|
|
367
|
+
let notFoundResponse;
|
|
368
|
+
if (notFoundRoute && routePattern !== "/404" && routePattern !== "/_error") notFoundResponse = await renderPage(request, url, manifest, middlewareHeaders, {
|
|
288
369
|
statusCode: 404,
|
|
289
370
|
asPath: routerAsPath,
|
|
290
371
|
renderErrorPageOnMiss: false,
|
|
291
|
-
__forcedRoute: notFoundRoute
|
|
292
|
-
|
|
293
|
-
|
|
372
|
+
__forcedRoute: notFoundRoute,
|
|
373
|
+
__notFoundRevalidateSeconds: pageDataResult.revalidateSeconds,
|
|
374
|
+
__notFoundExpireSeconds: pageDataResult.expireSeconds,
|
|
375
|
+
__notFoundCachePathname: isrCachePathname
|
|
376
|
+
});
|
|
377
|
+
else notFoundResponse = buildDefaultPagesNotFoundResponse();
|
|
378
|
+
if (isOnDemandRevalidate) notFoundResponse = withPagesCacheState(notFoundResponse, "REVALIDATED");
|
|
379
|
+
else if (pageDataResult.cacheState) notFoundResponse = withPagesCacheState(notFoundResponse, pageDataResult.cacheState);
|
|
380
|
+
return finalizePagesPreviewResponse(notFoundResponse, preview);
|
|
381
|
+
}
|
|
382
|
+
if (pageDataResult.kind === "response") {
|
|
383
|
+
let response = isOnDemandRevalidate && pageDataResult.onDemandRevalidateSuccess !== false ? withPagesCacheState(pageDataResult.response, "REVALIDATED") : pageDataResult.response;
|
|
384
|
+
if (shouldApplyErrorResponsePolicy) response = applyPagesErrorCachePolicy(response, errorPageRevalidateSeconds, errorPageExpireSeconds, errorResponseCachePathname);
|
|
385
|
+
return finalizePagesPreviewResponse(response, preview);
|
|
294
386
|
}
|
|
295
|
-
if (pageDataResult.kind === "response") return finalizePagesPreviewResponse(pageDataResult.response, preview);
|
|
296
387
|
let pageProps = pageDataResult.pageProps;
|
|
297
388
|
let renderProps = pageDataResult.props;
|
|
298
389
|
if (previewData !== false) renderProps = {
|
|
@@ -312,6 +403,7 @@ function createPagesPageHandler(opts) {
|
|
|
312
403
|
const gsspRes = pageDataResult.gsspRes;
|
|
313
404
|
const documentReqRes = serializedPagesNextData.autoExport === true ? null : pageDataResult.documentReqRes ?? createPageReqRes();
|
|
314
405
|
const isrRevalidateSeconds = pageDataResult.isrRevalidateSeconds;
|
|
406
|
+
const isrExpireSeconds = pageDataResult.isrExpireSeconds;
|
|
315
407
|
const isFallbackRender = pageDataResult.isFallback === true;
|
|
316
408
|
if (isFallbackRender) applySSRContext({
|
|
317
409
|
query: {},
|
|
@@ -337,7 +429,7 @@ function createPagesPageHandler(opts) {
|
|
|
337
429
|
}
|
|
338
430
|
if (!hasUserCacheControl) init.headers["Cache-Control"] = NEVER_CACHE_CONTROL;
|
|
339
431
|
} else if (isStaticPropsRoute) {
|
|
340
|
-
if (isrRevalidateSeconds) {
|
|
432
|
+
if (isrRevalidateSeconds !== null) {
|
|
341
433
|
const headers = new Headers(init.headers);
|
|
342
434
|
applyCdnResponseHeaders(headers, { cacheControl: buildMissIsrCacheControl(isrRevalidateSeconds, vinextConfig.expireTime) });
|
|
343
435
|
for (const [key, value] of headers) init.headers[key] = value;
|
|
@@ -352,7 +444,7 @@ function createPagesPageHandler(opts) {
|
|
|
352
444
|
const pageModuleIds = [];
|
|
353
445
|
if (appAssetPath) pageModuleIds.push(appAssetPath);
|
|
354
446
|
if (route.filePath) pageModuleIds.push(route.filePath);
|
|
355
|
-
|
|
447
|
+
let pageResponse = await renderPagesPageResponse({
|
|
356
448
|
assetTags: collectAssetTags({
|
|
357
449
|
manifest,
|
|
358
450
|
moduleIds: pageModuleIds,
|
|
@@ -386,8 +478,10 @@ function createPagesPageHandler(opts) {
|
|
|
386
478
|
documentReqRes,
|
|
387
479
|
gsspRes,
|
|
388
480
|
isrCacheKey: pageIsrCacheKey,
|
|
389
|
-
|
|
481
|
+
isrCachePathname,
|
|
482
|
+
expireSeconds: isrExpireSeconds,
|
|
390
483
|
isrRevalidateSeconds,
|
|
484
|
+
isOnDemandRevalidate,
|
|
391
485
|
isStaticPropsRoute,
|
|
392
486
|
isrSet,
|
|
393
487
|
i18n: buildI18nRenderContext(i18nConfig, locale, currentDefaultLocale, domainLocales),
|
|
@@ -411,7 +505,9 @@ function createPagesPageHandler(opts) {
|
|
|
411
505
|
userAgent: request.headers.get("user-agent") ?? void 0,
|
|
412
506
|
ifNoneMatch: request.headers.get("if-none-match") ?? void 0,
|
|
413
507
|
requestCacheControl: request.headers.get("cache-control") ?? void 0
|
|
414
|
-
})
|
|
508
|
+
});
|
|
509
|
+
if (shouldApplyErrorResponsePolicy) pageResponse = applyPagesErrorCachePolicy(pageResponse, errorPageRevalidateSeconds, errorPageExpireSeconds, errorResponseCachePathname);
|
|
510
|
+
return finalizePagesPreviewResponse(pageResponse, preview);
|
|
415
511
|
} catch (e) {
|
|
416
512
|
console.error("[vinext] SSR error:", e);
|
|
417
513
|
reportRequestError(e instanceof Error ? e : new Error(String(e)), {
|
|
@@ -445,7 +541,7 @@ function createPagesPageHandler(opts) {
|
|
|
445
541
|
}
|
|
446
542
|
return new Response("Internal Server Error", { status: 500 });
|
|
447
543
|
}
|
|
448
|
-
});
|
|
544
|
+
}), uCtx);
|
|
449
545
|
}
|
|
450
546
|
return renderPage;
|
|
451
547
|
}
|
|
@@ -88,11 +88,13 @@ type RenderPagesPageResponseOptions = {
|
|
|
88
88
|
setDocumentInitialHead?: ((head: ReactNode[]) => void) | undefined;
|
|
89
89
|
documentReqRes?: PagesDocumentReqRes | null;
|
|
90
90
|
gsspRes: PagesGsspResponse | null;
|
|
91
|
-
isrCacheKey: (router: string, pathname: string) => string;
|
|
91
|
+
isrCacheKey: (router: string, pathname: string) => string; /** Filesystem-route identity used for ISR persistence and cache tags. */
|
|
92
|
+
isrCachePathname?: string;
|
|
92
93
|
expireSeconds?: number;
|
|
93
|
-
isrRevalidateSeconds: number | null;
|
|
94
|
+
isrRevalidateSeconds: number | false | null; /** Synchronous `res.revalidate()` render; cache persistence must finish before returning. */
|
|
95
|
+
isOnDemandRevalidate?: boolean;
|
|
94
96
|
isStaticPropsRoute?: boolean;
|
|
95
|
-
isrSet: (key: string, data: CachedPagesValue, revalidateSeconds: number, tags?: string[], expireSeconds?: number) => Promise<void>;
|
|
97
|
+
isrSet: (key: string, data: CachedPagesValue, revalidateSeconds: number | false, tags?: string[], expireSeconds?: number) => Promise<void>;
|
|
96
98
|
i18n: PagesI18nRenderContext;
|
|
97
99
|
/**
|
|
98
100
|
* True when rendering a `getStaticPaths` fallback shell for a path that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
import { NEXTJS_CACHE_HEADER } from "./headers.js";
|
|
2
3
|
import { reportRequestError } from "./instrumentation.js";
|
|
3
|
-
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
4
4
|
import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
|
|
5
5
|
import { buildMissIsrCacheControl } from "./isr-decision.js";
|
|
6
6
|
import { fnv1a52 } from "../utils/hash.js";
|
|
@@ -9,29 +9,19 @@ import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
|
|
|
9
9
|
import { withScriptNonce } from "../shims/script-nonce-context.js";
|
|
10
10
|
import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
|
|
11
11
|
import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
|
|
12
|
+
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
12
13
|
import { readStreamAsText } from "../utils/text-stream.js";
|
|
13
14
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
14
15
|
import { callDocumentGetInitialProps } from "./document-initial-head.js";
|
|
16
|
+
import { isBotUserAgent } from "../utils/html-limited-bots.js";
|
|
15
17
|
import React from "react";
|
|
16
18
|
//#region src/server/pages-page-response.ts
|
|
17
19
|
/**
|
|
18
|
-
* Crawlers that cannot handle streamed HTML: they read metadata only from
|
|
19
|
-
* the first network chunk, so streaming would give them an incomplete <head>.
|
|
20
|
-
* Pattern sourced from Next.js html-bots.ts (updated to match the canary).
|
|
21
|
-
*/
|
|
22
|
-
const HTML_LIMITED_BOT_UA_RE = /[\w-]+-Google|Google-[\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight/i;
|
|
23
|
-
/**
|
|
24
|
-
* Googlebot (the main search crawler) executes JavaScript via a headless
|
|
25
|
-
* browser, so it too cannot safely handle mid-stream HTML mutations.
|
|
26
|
-
* Matches "Googlebot" but NOT suffixed variants like "Googlebot-Image".
|
|
27
|
-
*/
|
|
28
|
-
const HEADLESS_BROWSER_BOT_UA_RE = /Googlebot(?!-)|Googlebot$/i;
|
|
29
|
-
/**
|
|
30
20
|
* Returns true when the User-Agent belongs to a bot or crawler that cannot
|
|
31
21
|
* reliably consume a streamed HTML response.
|
|
32
22
|
*/
|
|
33
23
|
function isPagesStreamingBot(userAgent) {
|
|
34
|
-
return
|
|
24
|
+
return isBotUserAgent(userAgent);
|
|
35
25
|
}
|
|
36
26
|
function generatePagesETag(payload) {
|
|
37
27
|
return "\"" + fnv1a52(payload).toString(36) + payload.length.toString(36) + "\"";
|
|
@@ -64,7 +54,7 @@ function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles, scriptNonce
|
|
|
64
54
|
let html = "";
|
|
65
55
|
const nonceAttr = createNonceAttribute(scriptNonce);
|
|
66
56
|
for (const link of fontLinks) html += `<link rel="stylesheet"${nonceAttr} href="${escapeHtmlAttr(appendAssetDeploymentIdQuery(link))}" />\n `;
|
|
67
|
-
for (const preload of fontPreloads) html += `<link rel="preload"${nonceAttr} href="${escapeHtmlAttr(
|
|
57
|
+
for (const preload of fontPreloads) html += `<link rel="preload"${nonceAttr} href="${escapeHtmlAttr(preload.href)}" as="font" type="${escapeHtmlAttr(preload.type)}" crossorigin />\n `;
|
|
68
58
|
if (fontStyles.length > 0) html += `<style data-vinext-fonts${nonceAttr}>${fontStyles.join("\n")}</style>\n `;
|
|
69
59
|
return html;
|
|
70
60
|
}
|
|
@@ -142,14 +132,18 @@ async function reportPagesIsrCacheWriteError(error, cacheKey, routePattern) {
|
|
|
142
132
|
});
|
|
143
133
|
} catch {}
|
|
144
134
|
}
|
|
145
|
-
function
|
|
146
|
-
const
|
|
135
|
+
async function writePagesIsrCache(options) {
|
|
136
|
+
const bodyHtml = await readStreamAsText(options.stream);
|
|
137
|
+
await options.setCache(options.cacheKey, {
|
|
147
138
|
kind: "PAGES",
|
|
148
139
|
html: options.shellPrefix + bodyHtml + options.shellSuffix,
|
|
149
140
|
pageData: options.pageData,
|
|
150
141
|
headers: void 0,
|
|
151
142
|
status: options.status
|
|
152
|
-
}, options.revalidateSeconds, void 0, options.expireSeconds)
|
|
143
|
+
}, options.revalidateSeconds, void 0, options.expireSeconds);
|
|
144
|
+
}
|
|
145
|
+
function schedulePagesIsrCacheWrite(options) {
|
|
146
|
+
const cacheWritePromise = writePagesIsrCache(options).catch((error) => reportPagesIsrCacheWriteError(error, options.cacheKey, options.routePattern));
|
|
153
147
|
getRequestExecutionContext()?.waitUntil(cacheWritePromise);
|
|
154
148
|
}
|
|
155
149
|
function applyGsspHeaders(headers, gsspRes, statusCode) {
|
|
@@ -235,15 +229,15 @@ async function renderPagesPageResponse(options) {
|
|
|
235
229
|
const responseHeaders = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
236
230
|
const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes ?? options.documentReqRes?.res ?? null, options.statusCode);
|
|
237
231
|
let responseBodyStream = bodyStream;
|
|
238
|
-
if (!options.scriptNonce && options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {
|
|
232
|
+
if (!options.scriptNonce && options.isrRevalidateSeconds !== null && (options.isrRevalidateSeconds === false || options.isrRevalidateSeconds > 0)) {
|
|
239
233
|
const cacheBodyStreamPair = bodyStream.tee();
|
|
240
234
|
responseBodyStream = cacheBodyStreamPair[0];
|
|
241
235
|
const cacheBodyStream = cacheBodyStreamPair[1];
|
|
242
|
-
const isrPathname = options.routeUrl.split("?")[0];
|
|
243
|
-
|
|
236
|
+
const isrPathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
|
|
237
|
+
const cacheWriteOptions = {
|
|
244
238
|
cacheKey: options.isrCacheKey("pages", isrPathname),
|
|
245
239
|
expireSeconds: options.expireSeconds,
|
|
246
|
-
pageData: options.pageProps,
|
|
240
|
+
pageData: options.props ?? { pageProps: options.pageProps },
|
|
247
241
|
revalidateSeconds: options.isrRevalidateSeconds,
|
|
248
242
|
routePattern: options.routePattern,
|
|
249
243
|
setCache: options.isrSet,
|
|
@@ -251,19 +245,22 @@ async function renderPagesPageResponse(options) {
|
|
|
251
245
|
shellSuffix,
|
|
252
246
|
status: finalStatus,
|
|
253
247
|
stream: cacheBodyStream
|
|
254
|
-
}
|
|
248
|
+
};
|
|
249
|
+
if (options.isOnDemandRevalidate) await writePagesIsrCache(cacheWriteOptions);
|
|
250
|
+
else schedulePagesIsrCacheWrite(cacheWriteOptions);
|
|
255
251
|
}
|
|
256
252
|
const compositeStream = await buildPagesCompositeStream(responseBodyStream, shellPrefix, shellSuffix);
|
|
257
253
|
const userSetCacheControl = responseHeaders.has("Cache-Control");
|
|
258
254
|
if (options.scriptNonce) responseHeaders.set("Cache-Control", NO_STORE_CACHE_CONTROL);
|
|
259
|
-
else if (options.isrRevalidateSeconds) {
|
|
260
|
-
const isrPathname = options.routeUrl.split("?")[0];
|
|
255
|
+
else if (options.isrRevalidateSeconds !== null) {
|
|
256
|
+
const isrPathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
|
|
261
257
|
const stem = isrPathname.endsWith("/") ? isrPathname.slice(0, -1) : isrPathname;
|
|
262
258
|
applyCdnResponseHeaders(responseHeaders, {
|
|
263
259
|
cacheControl: buildMissIsrCacheControl(options.isrRevalidateSeconds, options.expireSeconds),
|
|
264
260
|
tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
|
|
265
261
|
});
|
|
266
|
-
|
|
262
|
+
if (options.isOnDemandRevalidate) responseHeaders.set(NEXTJS_CACHE_HEADER, "REVALIDATED");
|
|
263
|
+
else setCacheStateHeaders(responseHeaders, "MISS");
|
|
267
264
|
} else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
|
|
268
265
|
else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
|
|
269
266
|
if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
|
|
@@ -45,6 +45,13 @@ type PagesPipelineDeps = {
|
|
|
45
45
|
ctx?: unknown;
|
|
46
46
|
rawSearch?: string;
|
|
47
47
|
configMatchPathname?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Validate an on-demand revalidation credential using the runtime's
|
|
50
|
+
* authoritative build secret. Production adapters must inject the verifier
|
|
51
|
+
* exported by the generated server entry so this early middleware decision
|
|
52
|
+
* uses the same baked secret as the eventual page renderer.
|
|
53
|
+
*/
|
|
54
|
+
authorizeOnDemandRevalidate?: (headerValue: string | null) => boolean;
|
|
48
55
|
matchPageRoute?: ((pathname: string, request: Request) => PageRouteMatch | null) | null;
|
|
49
56
|
runMiddleware?: ((request: Request, ctx: unknown, opts: {
|
|
50
57
|
isDataRequest: boolean;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { addBasePathToPathname, hasBasePath } from "../utils/base-path.js";
|
|
2
2
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
3
|
+
import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
|
|
3
4
|
import { requestContextFromRequest } from "../config/request-context.js";
|
|
4
5
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
5
6
|
import { applyMiddlewareRequestHeaders, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, sanitizeDestination } from "../config/config-matchers.js";
|
|
6
7
|
import { cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
|
|
8
|
+
import { isOnDemandRevalidateRequest } from "./isr-cache.js";
|
|
7
9
|
import { mergeRewriteQuery } from "../utils/query.js";
|
|
8
10
|
import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
|
|
9
11
|
import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
|
|
@@ -56,6 +58,8 @@ async function runPagesRequest(request, deps) {
|
|
|
56
58
|
const url = new URL(request.url);
|
|
57
59
|
let pathname = url.pathname;
|
|
58
60
|
const search = url.search;
|
|
61
|
+
const revalidateHeader = request.headers.get(PRERENDER_REVALIDATE_HEADER);
|
|
62
|
+
const isOnDemandRevalidate = deps.authorizeOnDemandRevalidate ? deps.authorizeOnDemandRevalidate(revalidateHeader) : isOnDemandRevalidateRequest(revalidateHeader);
|
|
59
63
|
const requestConfigPathname = deps.configMatchPathname ?? pathname;
|
|
60
64
|
const basePathState = {
|
|
61
65
|
basePath,
|
|
@@ -98,7 +102,7 @@ async function runPagesRequest(request, deps) {
|
|
|
98
102
|
};
|
|
99
103
|
return served ? { type: "handled" } : null;
|
|
100
104
|
};
|
|
101
|
-
if (typeof deps.runMiddleware === "function") {
|
|
105
|
+
if (!isOnDemandRevalidate && typeof deps.runMiddleware === "function") {
|
|
102
106
|
const result = await deps.runMiddleware(request, deps.ctx ?? null, { isDataRequest });
|
|
103
107
|
if (result.waitUntilPromises && result.waitUntilPromises.length > 0) {
|
|
104
108
|
const ctx = deps.ctx;
|
|
@@ -10,6 +10,6 @@ type RevalidateOptions = {
|
|
|
10
10
|
*/
|
|
11
11
|
unstable_onlyGenerated?: boolean;
|
|
12
12
|
};
|
|
13
|
-
declare function performOnDemandRevalidate(source: IncomingMessage | Headers, urlPath: string, opts?: RevalidateOptions): Promise<void>;
|
|
13
|
+
declare function performOnDemandRevalidate(source: IncomingMessage | Headers, urlPath: string, opts?: RevalidateOptions, trustedOrigin?: string, allowedRevalidateHeaderKeys?: readonly string[], dev?: boolean): Promise<void>;
|
|
14
14
|
//#endregion
|
|
15
15
|
export { RevalidateOptions, performOnDemandRevalidate };
|
|
@@ -1,19 +1,65 @@
|
|
|
1
|
+
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
1
3
|
import "./headers.js";
|
|
2
|
-
import {
|
|
4
|
+
import { getRevalidateSecret, isOnDemandRevalidateRequest } from "./isr-cache.js";
|
|
5
|
+
import { normalizeDomainHostname } from "../utils/domain-locale.js";
|
|
3
6
|
import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
|
|
4
7
|
//#region src/server/pages-revalidate.ts
|
|
5
|
-
async function performOnDemandRevalidate(source, urlPath, opts = {}) {
|
|
8
|
+
async function performOnDemandRevalidate(source, urlPath, opts = {}, trustedOrigin, allowedRevalidateHeaderKeys = [], dev = false) {
|
|
6
9
|
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
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
+
if (isSourceRevalidationRequest(source)) throw new Error(`Cannot revalidate ${urlPath} from an internal revalidation request`);
|
|
11
|
+
const executionContext = getRequestExecutionContext();
|
|
12
|
+
const dispatchRevalidate = executionContext?.dispatchPagesRevalidate;
|
|
13
|
+
const target = createRevalidateTarget(source, urlPath, trustedOrigin);
|
|
10
14
|
const headers = { [PRERENDER_REVALIDATE_HEADER]: getRevalidateSecret() };
|
|
15
|
+
if (trustedOrigin && !dispatchRevalidate) {
|
|
16
|
+
const logicalHostname = normalizeDomainHostname(resolveRequestHost(source, "localhost"));
|
|
17
|
+
if (logicalHostname) headers[VINEXT_REVALIDATE_HOST_HEADER] = logicalHostname;
|
|
18
|
+
}
|
|
11
19
|
if (opts.unstable_onlyGenerated) headers[PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER] = "1";
|
|
12
|
-
const
|
|
20
|
+
const allowedHeaders = new Set(allowedRevalidateHeaderKeys.map((key) => key.toLowerCase()));
|
|
21
|
+
if (dev) allowedHeaders.add("cookie");
|
|
22
|
+
for (const key of allowedHeaders) {
|
|
23
|
+
const value = readSourceHeader(source, key);
|
|
24
|
+
if (value !== void 0) headers[key] = value;
|
|
25
|
+
}
|
|
26
|
+
if (dispatchRevalidate && executionContext.isInternalPagesRevalidation) throw new Error(`Cannot revalidate ${urlPath} from an internal revalidation request`);
|
|
27
|
+
const res = await fetchRevalidateTarget(target, headers, dispatchRevalidate);
|
|
28
|
+
if (!((res.headers.get("x-vercel-cache") ?? 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}`);
|
|
29
|
+
}
|
|
30
|
+
function readSourceHeader(source, key) {
|
|
31
|
+
if (isWebHeaders(source)) return source.get(key) ?? void 0;
|
|
32
|
+
const value = source.headers[key];
|
|
33
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
34
|
+
}
|
|
35
|
+
function isSourceRevalidationRequest(source) {
|
|
36
|
+
return isOnDemandRevalidateRequest(isWebHeaders(source) ? source.get(PRERENDER_REVALIDATE_HEADER) : source.headers[PRERENDER_REVALIDATE_HEADER]);
|
|
37
|
+
}
|
|
38
|
+
function isWebHeaders(source) {
|
|
39
|
+
return typeof source.get === "function";
|
|
40
|
+
}
|
|
41
|
+
async function fetchRevalidateTarget(initialTarget, headers, dispatchRevalidate) {
|
|
42
|
+
const request = new Request(initialTarget, {
|
|
13
43
|
method: "HEAD",
|
|
14
|
-
headers
|
|
44
|
+
headers,
|
|
45
|
+
redirect: "manual"
|
|
15
46
|
});
|
|
16
|
-
|
|
47
|
+
return dispatchRevalidate ? await dispatchRevalidate(request) : await fetch(request);
|
|
48
|
+
}
|
|
49
|
+
function createRevalidateTarget(source, urlPath, trustedOrigin) {
|
|
50
|
+
const origin = resolveRevalidateOrigin(source, trustedOrigin);
|
|
51
|
+
const target = new URL(`${origin}${urlPath}`);
|
|
52
|
+
if (target.origin !== origin) throw new Error(`Invalid urlPath provided to revalidate(), resolved outside application origin`);
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
function resolveRevalidateOrigin(source, trustedOrigin) {
|
|
56
|
+
if (trustedOrigin) return normalizeRevalidateOrigin(trustedOrigin);
|
|
57
|
+
return normalizeRevalidateOrigin(`${resolveRequestProtocol(source)}://${resolveRequestHost(source, "localhost")}`);
|
|
58
|
+
}
|
|
59
|
+
function normalizeRevalidateOrigin(origin) {
|
|
60
|
+
const parsed = new URL(origin);
|
|
61
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Invalid revalidate origin protocol: ${parsed.protocol}`);
|
|
62
|
+
return parsed.origin;
|
|
17
63
|
}
|
|
18
64
|
//#endregion
|
|
19
65
|
export { performOnDemandRevalidate };
|
|
@@ -22,6 +22,7 @@ type PagesWorkerEnv = {
|
|
|
22
22
|
type PagesWorkerExecutionContext = {
|
|
23
23
|
waitUntil?(promise: Promise<unknown>): void;
|
|
24
24
|
passThroughOnException?(): void;
|
|
25
|
+
cache?: unknown;
|
|
25
26
|
};
|
|
26
27
|
declare const _default: {
|
|
27
28
|
fetch(request: Request, env?: PagesWorkerEnv, ctx?: PagesWorkerExecutionContext): Promise<Response>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
2
2
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
3
4
|
import { notFoundStaticAssetResponse } from "./http-error-responses.js";
|
|
4
5
|
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
5
6
|
import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders } from "./request-pipeline.js";
|
|
@@ -7,6 +8,7 @@ import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js"
|
|
|
7
8
|
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
|
|
8
9
|
import { finalizeMissingStaticAssetResponse } from "./worker-utils.js";
|
|
9
10
|
import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
|
|
11
|
+
import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
|
|
10
12
|
import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
|
|
11
13
|
import { registerConfiguredImageOptimizer } from "virtual:vinext-image-adapters";
|
|
12
14
|
import * as pagesEntry from "virtual:vinext-server-entry";
|
|
@@ -25,7 +27,7 @@ import * as pagesEntry from "virtual:vinext-server-entry";
|
|
|
25
27
|
* import handler from "vinext/server/pages-router-entry";
|
|
26
28
|
* return handler.fetch(request, env, ctx);
|
|
27
29
|
*/
|
|
28
|
-
const { handleApiRoute, hasMiddleware, matchPageRoute, normalizeDataRequest, renderPage, runMiddleware, vinextConfig } = pagesEntry;
|
|
30
|
+
const { authorizeOnDemandRevalidate, handleApiRoute, hasMiddleware, matchPageRoute, normalizeDataRequest, renderPage, runMiddleware, vinextConfig } = pagesEntry;
|
|
29
31
|
const basePath = vinextConfig?.basePath ?? "";
|
|
30
32
|
const assetPathPrefix = assetPrefixPathname(vinextConfig?.assetPrefix ?? "");
|
|
31
33
|
const trailingSlash = vinextConfig?.trailingSlash ?? false;
|
|
@@ -47,7 +49,8 @@ const imageConfig = vinextConfig?.images ? {
|
|
|
47
49
|
var pages_router_entry_default = { async fetch(request, env, ctx) {
|
|
48
50
|
return handleRequest(request, env, ctx);
|
|
49
51
|
} };
|
|
50
|
-
async function handleRequest(request, env,
|
|
52
|
+
async function handleRequest(request, env, platformCtx) {
|
|
53
|
+
const ctx = createWorkerRevalidationContext(platformCtx, (internalRequest, internalCtx) => handleRequest(internalRequest, env, internalCtx));
|
|
51
54
|
registerConfiguredCacheAdapters(env);
|
|
52
55
|
registerConfiguredImageOptimizer(env);
|
|
53
56
|
try {
|
|
@@ -59,7 +62,9 @@ async function handleRequest(request, env, ctx) {
|
|
|
59
62
|
return new Response("Bad Request", { status: 400 });
|
|
60
63
|
}
|
|
61
64
|
const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
|
|
62
|
-
|
|
65
|
+
const filteredHeaders = ctx.isInternalPagesRevalidation ? new Headers(request.headers) : filterInternalHeaders(request.headers);
|
|
66
|
+
filteredHeaders.delete(VINEXT_REVALIDATE_HOST_HEADER);
|
|
67
|
+
request = cloneRequestWithHeaders(request, filteredHeaders);
|
|
63
68
|
const hadBasePath = !basePath || hasBasePath(pathname, basePath);
|
|
64
69
|
{
|
|
65
70
|
const stripped = stripBasePath(pathname, basePath);
|
|
@@ -93,10 +98,11 @@ async function handleRequest(request, env, ctx) {
|
|
|
93
98
|
isDataRequest: isDataReq,
|
|
94
99
|
hasMiddleware,
|
|
95
100
|
ctx,
|
|
101
|
+
authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
|
|
96
102
|
matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
|
|
97
103
|
runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
|
|
98
104
|
renderPage: typeof renderPage === "function" ? (req, resolvedUrl, options, stagedHeaders) => renderPage(req, resolvedUrl, null, ctx, stagedHeaders, options) : null,
|
|
99
|
-
handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx) : null,
|
|
105
|
+
handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx, new URL(req.url).origin) : null,
|
|
100
106
|
serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase) => {
|
|
101
107
|
if (!env?.ASSETS) return false;
|
|
102
108
|
return fetchWorkerFilesystemRoute(request, requestPathname, phase, (assetRequest) => Promise.resolve(env.ASSETS.fetch(assetRequest)));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NextI18nConfig } from "../config/next-config.js";
|
|
1
2
|
import { negotiateEncoding } from "./accept-encoding.js";
|
|
2
3
|
import { StaticFileCache } from "./static-file-cache.js";
|
|
3
4
|
import { resolveRequestHost, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
@@ -103,7 +104,7 @@ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clien
|
|
|
103
104
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
104
105
|
* instead of `req.url`.
|
|
105
106
|
*/
|
|
106
|
-
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string): Request;
|
|
107
|
+
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string, i18nConfig?: NextI18nConfig | null, authorizeOnDemandRevalidate?: (headerValue: string | null) => boolean): Request;
|
|
107
108
|
/**
|
|
108
109
|
* Stream a Web Response back to a Node.js ServerResponse.
|
|
109
110
|
* Supports streaming compression for SSR responses.
|