vinext 0.1.0 → 0.1.2
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 +2 -5
- 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 +33 -1
- package/dist/build/client-build-config.js +66 -1
- package/dist/check.js +4 -3
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +11 -2
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +31 -5
- package/dist/config/config-matchers.js +50 -3
- package/dist/config/next-config.d.ts +29 -3
- package/dist/config/next-config.js +32 -2
- package/dist/deploy.js +47 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +61 -5
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +16 -7
- package/dist/index.d.ts +0 -2
- package/dist/index.js +233 -280
- package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
- package/dist/plugins/dynamic-preload-metadata.js +415 -0
- package/dist/plugins/og-assets.js +2 -2
- package/dist/plugins/optimize-imports.d.ts +8 -4
- package/dist/plugins/optimize-imports.js +16 -12
- 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/plugins/sass.d.ts +53 -24
- package/dist/plugins/sass.js +249 -1
- package/dist/plugins/wasm-module-import.d.ts +15 -0
- package/dist/plugins/wasm-module-import.js +50 -0
- package/dist/routing/app-route-graph.d.ts +35 -2
- package/dist/routing/app-route-graph.js +179 -8
- package/dist/routing/file-matcher.js +1 -1
- 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 +155 -215
- package/dist/server/app-browser-error.d.ts +4 -1
- package/dist/server/app-browser-error.js +7 -1
- package/dist/server/app-browser-history-controller.d.ts +104 -0
- package/dist/server/app-browser-history-controller.js +210 -0
- 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 +13 -2
- package/dist/server/app-browser-navigation-controller.js +83 -4
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
- package/dist/server/app-browser-rsc-redirect.js +30 -8
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +10 -10
- package/dist/server/app-browser-visible-commit.js +10 -8
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.js +22 -1
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +45 -21
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +14 -0
- package/dist/server/app-page-dispatch.js +21 -6
- package/dist/server/app-page-element-builder.d.ts +23 -2
- package/dist/server/app-page-element-builder.js +58 -17
- package/dist/server/app-page-execution.d.ts +1 -1
- package/dist/server/app-page-execution.js +32 -17
- package/dist/server/app-page-render.d.ts +7 -1
- package/dist/server/app-page-render.js +11 -16
- package/dist/server/app-page-request.d.ts +9 -6
- package/dist/server/app-page-request.js +14 -10
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +2 -2
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +10 -8
- package/dist/server/app-page-stream.d.ts +37 -7
- package/dist/server/app-page-stream.js +36 -6
- 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-response.js +11 -10
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +15 -3
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +5 -4
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- 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 +22 -1
- package/dist/server/app-server-action-execution.js +73 -12
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +19 -3
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +97 -43
- package/dist/server/headers.d.ts +8 -1
- package/dist/server/headers.js +8 -1
- package/dist/server/instrumentation-runtime.d.ts +6 -0
- package/dist/server/instrumentation-runtime.js +8 -0
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/isr-decision.d.ts +79 -0
- package/dist/server/isr-decision.js +70 -0
- package/dist/server/metadata-route-response.js +5 -3
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +11 -7
- package/dist/server/middleware.js +1 -0
- package/dist/server/navigation-planner.d.ts +62 -1
- package/dist/server/navigation-planner.js +193 -3
- package/dist/server/navigation-trace.d.ts +12 -2
- package/dist/server/navigation-trace.js +11 -1
- package/dist/server/normalize-path.d.ts +0 -8
- package/dist/server/normalize-path.js +3 -1
- package/dist/server/otel-tracer-extension.d.ts +45 -0
- package/dist/server/otel-tracer-extension.js +89 -0
- package/dist/server/pages-api-route.d.ts +14 -3
- package/dist/server/pages-api-route.js +6 -1
- package/dist/server/pages-asset-tags.d.ts +15 -4
- package/dist/server/pages-asset-tags.js +18 -12
- package/dist/server/pages-data-route.js +5 -1
- package/dist/server/pages-node-compat.d.ts +5 -11
- package/dist/server/pages-node-compat.js +175 -118
- package/dist/server/pages-page-data.d.ts +38 -7
- package/dist/server/pages-page-data.js +64 -18
- package/dist/server/pages-page-handler.d.ts +10 -2
- package/dist/server/pages-page-handler.js +49 -20
- package/dist/server/pages-page-response.d.ts +55 -2
- package/dist/server/pages-page-response.js +74 -6
- 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 +113 -0
- package/dist/server/pages-request-pipeline.js +230 -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 +45 -3
- package/dist/server/prod-server.js +182 -234
- 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 +39 -2
- package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
- package/dist/shims/dynamic-preload-chunks.js +77 -0
- package/dist/shims/dynamic.d.ts +4 -0
- package/dist/shims/dynamic.js +4 -2
- package/dist/shims/error-boundary.d.ts +17 -7
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +22 -1
- package/dist/shims/fetch-cache.js +28 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/head.js +6 -1
- package/dist/shims/headers.d.ts +16 -2
- package/dist/shims/headers.js +37 -1
- package/dist/shims/image-config.js +7 -1
- package/dist/shims/internal/app-route-detection.d.ts +6 -3
- package/dist/shims/internal/app-route-detection.js +10 -6
- package/dist/shims/internal/app-router-context.d.ts +5 -0
- 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/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +9 -18
- package/dist/shims/navigation.js +96 -23
- 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 +156 -22
- package/dist/shims/script-nonce-context.d.ts +1 -1
- package/dist/shims/script-nonce-context.js +11 -3
- package/dist/shims/server.d.ts +17 -1
- package/dist/shims/server.js +31 -6
- package/dist/shims/slot.js +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -0
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +41 -6
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/dist/utils/client-runtime-metadata.d.ts +45 -0
- package/dist/utils/client-runtime-metadata.js +63 -0
- package/dist/utils/hash.d.ts +17 -1
- package/dist/utils/hash.js +36 -1
- package/dist/utils/lazy-chunks.d.ts +27 -1
- package/dist/utils/lazy-chunks.js +65 -1
- package/dist/utils/manifest-paths.d.ts +20 -2
- package/dist/utils/manifest-paths.js +38 -3
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +5 -1
- package/package.json +6 -2
|
@@ -2,13 +2,16 @@ import { createRequestContext, runWithRequestContext } from "../shims/unified-re
|
|
|
2
2
|
import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
|
|
3
3
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
4
4
|
import { matchRoute } from "../routing/pages-router.js";
|
|
5
|
+
import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
|
|
5
6
|
import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
6
7
|
import { importModule, reportRequestError } from "./instrumentation.js";
|
|
7
8
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
8
9
|
import { _runWithCacheState } from "../shims/cache.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
|
|
11
|
+
import { buildMissIsrCacheControl, decideIsr } from "./isr-decision.js";
|
|
12
|
+
import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
|
|
11
13
|
import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
|
|
14
|
+
import { runWithPrivateCache } from "../shims/cache-runtime.js";
|
|
12
15
|
import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
|
|
13
16
|
import "../shims/router-state.js";
|
|
14
17
|
import { runWithHeadState } from "../shims/head-state.js";
|
|
@@ -20,6 +23,7 @@ import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
|
|
|
20
23
|
import { logRequest, now } from "./request-log.js";
|
|
21
24
|
import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFromUrl$1, parseCookieLocaleFromHeader, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
22
25
|
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
26
|
+
import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
23
27
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
24
28
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
25
29
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
@@ -61,7 +65,10 @@ function writeGsspRedirect(res, redirect, isDataReq) {
|
|
|
61
65
|
let dest = redirect.destination;
|
|
62
66
|
if (!dest.startsWith("http://") && !dest.startsWith("https://")) dest = dest.replace(/^[\\/]+/, "/");
|
|
63
67
|
if (isDataReq) {
|
|
64
|
-
|
|
68
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
69
|
+
const dataHeaders = { "Content-Type": "application/json" };
|
|
70
|
+
if (deploymentId) dataHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
71
|
+
res.writeHead(200, dataHeaders);
|
|
65
72
|
res.end(JSON.stringify({ pageProps: {
|
|
66
73
|
__N_REDIRECT: dest,
|
|
67
74
|
__N_REDIRECT_STATUS: status
|
|
@@ -183,8 +190,9 @@ function parseCookieLocale(req, i18nConfig) {
|
|
|
183
190
|
* 4. Render the component to HTML
|
|
184
191
|
* 5. Wrap in _document shell and send response
|
|
185
192
|
*/
|
|
186
|
-
function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, clientTraceMetadata) {
|
|
193
|
+
function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata) {
|
|
187
194
|
const matcher = fileMatcher ?? createValidFileMatcher();
|
|
195
|
+
const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
|
|
188
196
|
const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
|
|
189
197
|
_alsRegistration.catch(() => {});
|
|
190
198
|
return async (req, res, url, statusCode, isDataReq = false) => {
|
|
@@ -226,7 +234,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
226
234
|
const match = matchRoute(localeStrippedUrl, routes);
|
|
227
235
|
if (!match) {
|
|
228
236
|
if (isDataReq) {
|
|
229
|
-
|
|
237
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
238
|
+
const notFoundHeaders = { "Content-Type": "application/json" };
|
|
239
|
+
if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
240
|
+
res.writeHead(404, notFoundHeaders);
|
|
230
241
|
res.end("{}");
|
|
231
242
|
return;
|
|
232
243
|
}
|
|
@@ -241,15 +252,6 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
241
252
|
try {
|
|
242
253
|
await _alsRegistration;
|
|
243
254
|
const routerShim = await importModule(runner, "next/router");
|
|
244
|
-
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext({
|
|
245
|
-
pathname: patternToNextFormat(route.pattern),
|
|
246
|
-
query,
|
|
247
|
-
asPath: url,
|
|
248
|
-
locale: locale ?? currentDefaultLocale,
|
|
249
|
-
locales: i18nConfig?.locales,
|
|
250
|
-
defaultLocale: currentDefaultLocale,
|
|
251
|
-
domainLocales
|
|
252
|
-
});
|
|
253
255
|
if (i18nConfig) {
|
|
254
256
|
await runner.import("vinext/i18n-state");
|
|
255
257
|
const i18nCtx = await importModule(runner, "vinext/i18n-context");
|
|
@@ -262,6 +264,28 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
262
264
|
});
|
|
263
265
|
}
|
|
264
266
|
const pageModule = await importModule(runner, route.filePath);
|
|
267
|
+
let AppComponent = null;
|
|
268
|
+
const appPath = path.join(pagesDir, "_app");
|
|
269
|
+
if (findFileWithExtensions(appPath, matcher)) try {
|
|
270
|
+
AppComponent = (await importModule(runner, appPath)).default ?? null;
|
|
271
|
+
} catch {}
|
|
272
|
+
const pagesNextData = buildPagesReadinessNextData({
|
|
273
|
+
pageModule,
|
|
274
|
+
appComponent: AppComponent,
|
|
275
|
+
hasRewrites
|
|
276
|
+
});
|
|
277
|
+
const navigationIsReady = typeof routerShim.getPagesNavigationIsReadyFromSerializedState === "function" ? routerShim.getPagesNavigationIsReadyFromSerializedState(patternToNextFormat(route.pattern), new URL(url, "http://_").search, pagesNextData) : true;
|
|
278
|
+
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext({
|
|
279
|
+
pathname: patternToNextFormat(route.pattern),
|
|
280
|
+
query,
|
|
281
|
+
asPath: url,
|
|
282
|
+
navigationIsReady,
|
|
283
|
+
nextData: pagesNextData,
|
|
284
|
+
locale: locale ?? currentDefaultLocale,
|
|
285
|
+
locales: i18nConfig?.locales,
|
|
286
|
+
defaultLocale: currentDefaultLocale,
|
|
287
|
+
domainLocales
|
|
288
|
+
});
|
|
265
289
|
_compileEnd = now();
|
|
266
290
|
const PageComponent = pageModule.default;
|
|
267
291
|
if (!PageComponent) {
|
|
@@ -310,7 +334,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
310
334
|
});
|
|
311
335
|
if (fallback === false && !isValidPath) {
|
|
312
336
|
if (isDataReq) {
|
|
313
|
-
|
|
337
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
338
|
+
const notFoundHeaders = { "Content-Type": "application/json" };
|
|
339
|
+
if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
340
|
+
res.writeHead(404, notFoundHeaders);
|
|
314
341
|
res.end("{}");
|
|
315
342
|
return;
|
|
316
343
|
}
|
|
@@ -323,6 +350,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
323
350
|
pathname: patternToNextFormat(route.pattern),
|
|
324
351
|
query,
|
|
325
352
|
asPath: url,
|
|
353
|
+
navigationIsReady: false,
|
|
326
354
|
locale: locale ?? currentDefaultLocale,
|
|
327
355
|
locales: i18nConfig?.locales,
|
|
328
356
|
defaultLocale: currentDefaultLocale,
|
|
@@ -353,7 +381,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
353
381
|
}
|
|
354
382
|
if (result && "notFound" in result && result.notFound) {
|
|
355
383
|
if (isDataReq) {
|
|
356
|
-
|
|
384
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
385
|
+
const notFoundHeaders = { "Content-Type": "application/json" };
|
|
386
|
+
if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
387
|
+
res.writeHead(404, notFoundHeaders);
|
|
357
388
|
res.end("{}");
|
|
358
389
|
return;
|
|
359
390
|
}
|
|
@@ -369,7 +400,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
369
400
|
if (Array.isArray(val)) gsspExtraHeaders[key] = val.map(String);
|
|
370
401
|
else gsspExtraHeaders[key] = String(val);
|
|
371
402
|
}
|
|
372
|
-
if (!Object.keys(gsspExtraHeaders).some((k) => k.toLowerCase() === "cache-control")) gsspExtraHeaders["Cache-Control"] =
|
|
403
|
+
if (!Object.keys(gsspExtraHeaders).some((k) => k.toLowerCase() === "cache-control")) gsspExtraHeaders["Cache-Control"] = NEVER_CACHE_CONTROL;
|
|
373
404
|
}
|
|
374
405
|
const responseHeaders = typeof res.getHeaders === "function" ? res.getHeaders() : void 0;
|
|
375
406
|
const scriptNonce = getScriptNonceFromNodeHeaderSources(req.headers, responseHeaders);
|
|
@@ -385,21 +416,26 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
385
416
|
if (typeof pageModule.getStaticProps === "function" && !isFallbackRender) {
|
|
386
417
|
const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
|
|
387
418
|
const cached = await isrGet(cacheKey);
|
|
388
|
-
|
|
419
|
+
const isOnDemandRevalidate = isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]);
|
|
420
|
+
if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
389
421
|
const cachedHtml = cached.value.value.html;
|
|
390
422
|
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
391
|
-
const
|
|
423
|
+
const { cacheControl: hitCacheControl } = decideIsr({
|
|
424
|
+
cacheState: "HIT",
|
|
425
|
+
kind: "dev",
|
|
426
|
+
revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
|
|
427
|
+
});
|
|
392
428
|
const hitHeaders = {
|
|
393
429
|
"Content-Type": "text/html",
|
|
394
430
|
...buildCacheStateHeaders("HIT"),
|
|
395
|
-
"Cache-Control":
|
|
431
|
+
"Cache-Control": hitCacheControl
|
|
396
432
|
};
|
|
397
433
|
if (earlyFontLinkHeader) hitHeaders["Link"] = earlyFontLinkHeader;
|
|
398
434
|
res.writeHead(200, hitHeaders);
|
|
399
435
|
res.end(transformedHtml);
|
|
400
436
|
return;
|
|
401
437
|
}
|
|
402
|
-
if (cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
438
|
+
if (!isOnDemandRevalidate && cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
403
439
|
const cachedHtml = cached.value.value.html;
|
|
404
440
|
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
405
441
|
triggerBackgroundRegeneration(cacheKey, async () => {
|
|
@@ -420,6 +456,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
420
456
|
pathname: patternToNextFormat(route.pattern),
|
|
421
457
|
query,
|
|
422
458
|
asPath: url,
|
|
459
|
+
navigationIsReady,
|
|
423
460
|
locale: locale ?? currentDefaultLocale,
|
|
424
461
|
locales: i18nConfig?.locales,
|
|
425
462
|
defaultLocale: currentDefaultLocale,
|
|
@@ -450,6 +487,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
450
487
|
const viteRoot = server.config?.root;
|
|
451
488
|
const regenPageUrl = viteRoot ? "/" + path.relative(viteRoot, route.filePath) : route.filePath;
|
|
452
489
|
const regenAppUrl = RegenApp ? viteRoot ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : path.join(pagesDir, "_app") : null;
|
|
490
|
+
const freshPagesNextData = {
|
|
491
|
+
...pagesNextData,
|
|
492
|
+
__vinext: {
|
|
493
|
+
...pagesNextData.__vinext,
|
|
494
|
+
pageModuleUrl: regenPageUrl,
|
|
495
|
+
appModuleUrl: regenAppUrl,
|
|
496
|
+
hasMiddleware
|
|
497
|
+
}
|
|
498
|
+
};
|
|
453
499
|
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script>window.__NEXT_DATA__ = ${safeJsonStringify({
|
|
454
500
|
props: { pageProps: freshProps },
|
|
455
501
|
page: patternToNextFormat(route.pattern),
|
|
@@ -460,11 +506,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
460
506
|
locales: i18nConfig?.locales,
|
|
461
507
|
defaultLocale: currentDefaultLocale,
|
|
462
508
|
domainLocales,
|
|
463
|
-
|
|
464
|
-
pageModuleUrl: regenPageUrl,
|
|
465
|
-
appModuleUrl: regenAppUrl,
|
|
466
|
-
hasMiddleware
|
|
467
|
-
}
|
|
509
|
+
...freshPagesNextData
|
|
468
510
|
})}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}<\/script>`}\n ${cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/)?.[0] ?? ""}</body></html>`, freshProps), revalidate);
|
|
469
511
|
setRevalidateDuration(cacheKey, revalidate);
|
|
470
512
|
}
|
|
@@ -475,11 +517,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
475
517
|
routePath: route.pattern,
|
|
476
518
|
routeType: "render"
|
|
477
519
|
});
|
|
478
|
-
const
|
|
520
|
+
const { cacheControl: staleCacheControl } = decideIsr({
|
|
521
|
+
cacheState: "STALE",
|
|
522
|
+
kind: "dev",
|
|
523
|
+
revalidateSeconds: getRevalidateDuration(cacheKey) ?? 60
|
|
524
|
+
});
|
|
479
525
|
const staleHeaders = {
|
|
480
526
|
"Content-Type": "text/html",
|
|
481
527
|
...buildCacheStateHeaders("STALE"),
|
|
482
|
-
"Cache-Control":
|
|
528
|
+
"Cache-Control": staleCacheControl
|
|
483
529
|
};
|
|
484
530
|
if (earlyFontLinkHeader) staleHeaders["Link"] = earlyFontLinkHeader;
|
|
485
531
|
res.writeHead(200, staleHeaders);
|
|
@@ -491,7 +537,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
491
537
|
locale: locale ?? currentDefaultLocale,
|
|
492
538
|
locales: i18nConfig?.locales,
|
|
493
539
|
defaultLocale: currentDefaultLocale,
|
|
494
|
-
revalidateReason: "stale"
|
|
540
|
+
revalidateReason: isOnDemandRevalidate ? "on-demand" : "stale"
|
|
495
541
|
};
|
|
496
542
|
const result = await pageModule.getStaticProps(context);
|
|
497
543
|
if (result && "props" in result) pageProps = result.props;
|
|
@@ -501,7 +547,10 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
501
547
|
}
|
|
502
548
|
if (result && "notFound" in result && result.notFound) {
|
|
503
549
|
if (isDataReq) {
|
|
504
|
-
|
|
550
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
551
|
+
const notFoundHeaders = { "Content-Type": "application/json" };
|
|
552
|
+
if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
553
|
+
res.writeHead(404, notFoundHeaders);
|
|
505
554
|
res.end("{}");
|
|
506
555
|
return;
|
|
507
556
|
}
|
|
@@ -531,16 +580,16 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
531
580
|
if (isDataReq) {
|
|
532
581
|
const dataHeaders = { "Content-Type": "application/json" };
|
|
533
582
|
if (gsspExtraHeaders) for (const [k, v] of Object.entries(gsspExtraHeaders)) dataHeaders[k] = v;
|
|
583
|
+
const dataRoutePattern = patternToNextFormat(route.pattern);
|
|
584
|
+
if (dataRoutePattern !== "/_error" && dataRoutePattern !== "/500") {
|
|
585
|
+
const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
|
|
586
|
+
if (deploymentId) dataHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
|
|
587
|
+
}
|
|
534
588
|
res.writeHead(statusCode ?? 200, dataHeaders);
|
|
535
589
|
res.end(JSON.stringify({ pageProps }));
|
|
536
590
|
_renderEnd = now();
|
|
537
591
|
return;
|
|
538
592
|
}
|
|
539
|
-
let AppComponent = null;
|
|
540
|
-
const appPath = path.join(pagesDir, "_app");
|
|
541
|
-
if (findFileWithExtensions(appPath, matcher)) try {
|
|
542
|
-
AppComponent = (await importModule(runner, appPath)).default ?? null;
|
|
543
|
-
} catch {}
|
|
544
593
|
const createElement = React.createElement;
|
|
545
594
|
let element;
|
|
546
595
|
const wrapWithRouterContext = routerShim.wrapWithRouterContext;
|
|
@@ -584,6 +633,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
584
633
|
const viteRoot = server.config.root;
|
|
585
634
|
const pageModuleUrl = "/" + path.relative(viteRoot, route.filePath);
|
|
586
635
|
const appModuleUrl = AppComponent ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : null;
|
|
636
|
+
const serializedPagesNextData = {
|
|
637
|
+
...pagesNextData,
|
|
638
|
+
__vinext: {
|
|
639
|
+
...pagesNextData.__vinext,
|
|
640
|
+
pageModuleUrl,
|
|
641
|
+
appModuleUrl,
|
|
642
|
+
hasMiddleware
|
|
643
|
+
}
|
|
644
|
+
};
|
|
587
645
|
const hydrationScript = `
|
|
588
646
|
<script type="module"${nonceAttr}>
|
|
589
647
|
import "vinext/instrumentation-client";
|
|
@@ -641,22 +699,18 @@ hydrate();
|
|
|
641
699
|
locales: i18nConfig?.locales,
|
|
642
700
|
defaultLocale: currentDefaultLocale,
|
|
643
701
|
domainLocales,
|
|
644
|
-
|
|
645
|
-
pageModuleUrl,
|
|
646
|
-
appModuleUrl,
|
|
647
|
-
hasMiddleware
|
|
648
|
-
}
|
|
702
|
+
...serializedPagesNextData
|
|
649
703
|
})}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}`, scriptNonce);
|
|
650
704
|
const docPath = path.join(pagesDir, "_document");
|
|
651
705
|
let DocumentComponent = null;
|
|
652
706
|
if (findFileWithExtensions(docPath, matcher)) try {
|
|
653
707
|
DocumentComponent = (await runner.import(docPath)).default ?? null;
|
|
654
708
|
} catch {}
|
|
655
|
-
const allScripts = `${nextDataScript}\n ${hydrationScript}`;
|
|
709
|
+
const allScripts = `${nextDataScript}\n ${createInlineScriptTag(`window.__VINEXT_PAGE_PATTERNS__=${safeJsonStringify(pagePatterns)}`, scriptNonce)}\n ${hydrationScript}`;
|
|
656
710
|
const extraHeaders = { ...gsspExtraHeaders };
|
|
657
|
-
if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] =
|
|
711
|
+
if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] = NO_STORE_CACHE_CONTROL;
|
|
658
712
|
else {
|
|
659
|
-
extraHeaders["Cache-Control"] =
|
|
713
|
+
extraHeaders["Cache-Control"] = buildMissIsrCacheControl(isrRevalidateSeconds);
|
|
660
714
|
Object.assign(extraHeaders, buildCacheStateHeaders("MISS"));
|
|
661
715
|
}
|
|
662
716
|
if (allFontPreloads.length > 0) extraHeaders["Link"] = allFontPreloads.map((p) => `<${p.href}>; rel=preload; as=font; type=${p.type}; crossorigin`).join(", ");
|
package/dist/server/headers.d.ts
CHANGED
|
@@ -60,6 +60,13 @@ declare const RSC_ACTION_HEADER = "x-rsc-action";
|
|
|
60
60
|
declare const NEXT_ACTION_HEADER = "next-action";
|
|
61
61
|
/** Next.js action-not-found indicator (value "1"). */
|
|
62
62
|
declare const NEXTJS_ACTION_NOT_FOUND_HEADER = "x-nextjs-action-not-found";
|
|
63
|
+
/**
|
|
64
|
+
* Deployment ID header used by the Pages Router for deployment-skew
|
|
65
|
+
* protection. Set on every `/_next/data/` response so the client can detect
|
|
66
|
+
* when a new deployment has been rolled out and trigger a hard navigation.
|
|
67
|
+
* Mirrors `NEXT_NAV_DEPLOYMENT_ID_HEADER` from Next.js `lib/constants.ts`.
|
|
68
|
+
*/
|
|
69
|
+
declare const NEXTJS_DEPLOYMENT_ID_HEADER = "x-nextjs-deployment-id";
|
|
63
70
|
/** Forwarded action marker — set when a request has already been forwarded between workers. */
|
|
64
71
|
declare const ACTION_FORWARDED_HEADER = "x-action-forwarded";
|
|
65
72
|
/** Indicates revalidation occurred — value is JSON kind (1 = path/tag, 2 = dynamic-only). */
|
|
@@ -100,4 +107,4 @@ declare const INTERNAL_HEADERS: string[];
|
|
|
100
107
|
/** Vinext-only internal headers stripped alongside Next.js protocol internals. */
|
|
101
108
|
declare const VINEXT_INTERNAL_HEADERS: string[];
|
|
102
109
|
//#endregion
|
|
103
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
110
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
package/dist/server/headers.js
CHANGED
|
@@ -60,6 +60,13 @@ const RSC_ACTION_HEADER = "x-rsc-action";
|
|
|
60
60
|
const NEXT_ACTION_HEADER = "next-action";
|
|
61
61
|
/** Next.js action-not-found indicator (value "1"). */
|
|
62
62
|
const NEXTJS_ACTION_NOT_FOUND_HEADER = "x-nextjs-action-not-found";
|
|
63
|
+
/**
|
|
64
|
+
* Deployment ID header used by the Pages Router for deployment-skew
|
|
65
|
+
* protection. Set on every `/_next/data/` response so the client can detect
|
|
66
|
+
* when a new deployment has been rolled out and trigger a hard navigation.
|
|
67
|
+
* Mirrors `NEXT_NAV_DEPLOYMENT_ID_HEADER` from Next.js `lib/constants.ts`.
|
|
68
|
+
*/
|
|
69
|
+
const NEXTJS_DEPLOYMENT_ID_HEADER = "x-nextjs-deployment-id";
|
|
63
70
|
/** Forwarded action marker — set when a request has already been forwarded between workers. */
|
|
64
71
|
const ACTION_FORWARDED_HEADER = "x-action-forwarded";
|
|
65
72
|
/** Indicates revalidation occurred — value is JSON kind (1 = path/tag, 2 = dynamic-only). */
|
|
@@ -122,4 +129,4 @@ const INTERNAL_HEADERS = [
|
|
|
122
129
|
/** Vinext-only internal headers stripped alongside Next.js protocol internals. */
|
|
123
130
|
const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
|
|
124
131
|
//#endregion
|
|
125
|
-
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
132
|
+
export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
|
|
@@ -34,6 +34,12 @@
|
|
|
34
34
|
* Ensure the instrumentation module's `register()` and `onRequestError`
|
|
35
35
|
* hooks have been applied exactly once.
|
|
36
36
|
*
|
|
37
|
+
* After `register()` runs, we extend the OTel tracer provider so that spans
|
|
38
|
+
* created inside Cache Component renders (warmup / fallback-resume phases) use
|
|
39
|
+
* a fresh OTel context rather than inheriting the prerender work unit store.
|
|
40
|
+
* This mirrors Next.js's `afterRegistration()` call in
|
|
41
|
+
* `instrumentation-node-extensions.ts`.
|
|
42
|
+
*
|
|
37
43
|
* @param instrumentationModule - The imported `instrumentation.ts` module.
|
|
38
44
|
* Passed as an argument so the generated entry can import it normally
|
|
39
45
|
* without this helper needing to know the module path.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extendTracerProviderForCacheComponents } from "./otel-tracer-extension.js";
|
|
1
2
|
//#region src/server/instrumentation-runtime.ts
|
|
2
3
|
let initialized = false;
|
|
3
4
|
let initPromise = null;
|
|
@@ -8,6 +9,12 @@ function isOnRequestErrorHandler(value) {
|
|
|
8
9
|
* Ensure the instrumentation module's `register()` and `onRequestError`
|
|
9
10
|
* hooks have been applied exactly once.
|
|
10
11
|
*
|
|
12
|
+
* After `register()` runs, we extend the OTel tracer provider so that spans
|
|
13
|
+
* created inside Cache Component renders (warmup / fallback-resume phases) use
|
|
14
|
+
* a fresh OTel context rather than inheriting the prerender work unit store.
|
|
15
|
+
* This mirrors Next.js's `afterRegistration()` call in
|
|
16
|
+
* `instrumentation-node-extensions.ts`.
|
|
17
|
+
*
|
|
11
18
|
* @param instrumentationModule - The imported `instrumentation.ts` module.
|
|
12
19
|
* Passed as an argument so the generated entry can import it normally
|
|
13
20
|
* without this helper needing to know the module path.
|
|
@@ -18,6 +25,7 @@ async function ensureInstrumentationRegistered(instrumentationModule) {
|
|
|
18
25
|
if (initPromise) return initPromise;
|
|
19
26
|
initPromise = (async () => {
|
|
20
27
|
if (typeof instrumentationModule.register === "function") await instrumentationModule.register();
|
|
28
|
+
extendTracerProviderForCacheComponents();
|
|
21
29
|
if (isOnRequestErrorHandler(instrumentationModule.onRequestError)) globalThis.__VINEXT_onRequestErrorHandler__ = instrumentationModule.onRequestError;
|
|
22
30
|
initialized = true;
|
|
23
31
|
})();
|
|
@@ -5,6 +5,42 @@ import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
|
5
5
|
import { AppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
6
6
|
|
|
7
7
|
//#region src/server/isr-cache.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Header set on the internal request that `res.revalidate()` issues to
|
|
10
|
+
* trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
|
|
11
|
+
* `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
|
|
12
|
+
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY: in Next.js this header is NOT a presence flag — it carries the
|
|
15
|
+
* secret `previewModeId`, and `checkIsOnDemandRevalidate`
|
|
16
|
+
* (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
|
|
17
|
+
* request as on-demand revalidation when the value *equals* that secret. If we
|
|
18
|
+
* gated on presence alone, any external client could send
|
|
19
|
+
* `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
|
|
20
|
+
* ISR page, bypassing the fresh/stale cache short-circuits — a
|
|
21
|
+
* cache-stampede/DoS vector. We therefore validate the value against
|
|
22
|
+
* {@link getRevalidateSecret} (a build-time secret shared across all Workers
|
|
23
|
+
* isolates) with a constant-time comparison, and only the matching value (sent
|
|
24
|
+
* by our own `res.revalidate()`) is honored.
|
|
25
|
+
*/
|
|
26
|
+
declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
27
|
+
/**
|
|
28
|
+
* Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
|
|
29
|
+
* `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
|
|
30
|
+
* path if it was already generated, and a 404 response counts as a successful
|
|
31
|
+
* no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
|
|
32
|
+
* (`x-prerender-revalidate-if-generated`) — see
|
|
33
|
+
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
34
|
+
*/
|
|
35
|
+
declare const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
|
|
36
|
+
declare function getRevalidateSecret(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Authorize an incoming request as an on-demand revalidation trigger. Mirrors
|
|
39
|
+
* Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
|
|
40
|
+
* value must *equal* the process revalidate secret. Header presence alone is
|
|
41
|
+
* NOT sufficient — see the security note on {@link PRERENDER_REVALIDATE_HEADER}.
|
|
42
|
+
*/
|
|
43
|
+
declare function isOnDemandRevalidateRequest(headerValue: string | string[] | null | undefined): boolean;
|
|
8
44
|
type ISRCacheEntry = {
|
|
9
45
|
value: CacheHandlerValue;
|
|
10
46
|
isStale: boolean;
|
|
@@ -88,4 +124,4 @@ declare function setRevalidateDuration(key: string, seconds: number): void;
|
|
|
88
124
|
*/
|
|
89
125
|
declare function getRevalidateDuration(key: string): number | undefined;
|
|
90
126
|
//#endregion
|
|
91
|
-
export { ISRCacheEntry, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
127
|
+
export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -7,6 +7,90 @@ import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./
|
|
|
7
7
|
import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
|
|
8
8
|
//#region src/server/isr-cache.ts
|
|
9
9
|
/**
|
|
10
|
+
* Header set on the internal request that `res.revalidate()` issues to
|
|
11
|
+
* trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
|
|
12
|
+
* `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
|
|
13
|
+
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
14
|
+
*
|
|
15
|
+
* SECURITY: in Next.js this header is NOT a presence flag — it carries the
|
|
16
|
+
* secret `previewModeId`, and `checkIsOnDemandRevalidate`
|
|
17
|
+
* (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
|
|
18
|
+
* request as on-demand revalidation when the value *equals* that secret. If we
|
|
19
|
+
* gated on presence alone, any external client could send
|
|
20
|
+
* `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
|
|
21
|
+
* ISR page, bypassing the fresh/stale cache short-circuits — a
|
|
22
|
+
* cache-stampede/DoS vector. We therefore validate the value against
|
|
23
|
+
* {@link getRevalidateSecret} (a build-time secret shared across all Workers
|
|
24
|
+
* isolates) with a constant-time comparison, and only the matching value (sent
|
|
25
|
+
* by our own `res.revalidate()`) is honored.
|
|
26
|
+
*/
|
|
27
|
+
const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
28
|
+
/**
|
|
29
|
+
* Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
|
|
30
|
+
* `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
|
|
31
|
+
* path if it was already generated, and a 404 response counts as a successful
|
|
32
|
+
* no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
|
|
33
|
+
* (`x-prerender-revalidate-if-generated`) — see
|
|
34
|
+
* `.nextjs-ref/packages/next/src/lib/constants.ts`.
|
|
35
|
+
*/
|
|
36
|
+
const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
|
|
37
|
+
/**
|
|
38
|
+
* Build-time secret that authenticates on-demand revalidation requests, the
|
|
39
|
+
* vinext analog of Next.js's prerender-manifest `previewModeId`.
|
|
40
|
+
*
|
|
41
|
+
* `res.revalidate()` loops back into the server via an internal `fetch()`. On
|
|
42
|
+
* Cloudflare Workers that loopback can land on a *different* isolate than the
|
|
43
|
+
* sender, so a per-process random secret would mismatch across isolates and
|
|
44
|
+
* false-reject legitimate revalidations (and, symmetrically, two isolates with
|
|
45
|
+
* independently-rolled secrets could never agree). The fix mirrors Next.js's
|
|
46
|
+
* `previewModeId`: the secret is generated once at BUILD time and baked
|
|
47
|
+
* (server-only — never into the client bundle) into every server bundle via the
|
|
48
|
+
* `__VINEXT_REVALIDATE_SECRET` Vite `define`, so it is byte-for-byte identical in
|
|
49
|
+
* every isolate. See `vinext build` CLI (`__VINEXT_SHARED_REVALIDATE_SECRET`) and
|
|
50
|
+
* the `vinext:compiler-define-server` plugin. The sender attaches it as the
|
|
51
|
+
* {@link PRERENDER_REVALIDATE_HEADER} value; the receiver authorizes a request
|
|
52
|
+
* only when the incoming value equals this secret (see
|
|
53
|
+
* {@link isOnDemandRevalidateRequest}).
|
|
54
|
+
*
|
|
55
|
+
* When the build-time define is absent — dev mode, and any path that doesn't
|
|
56
|
+
* run through `vinext build` — we fall back to a lazily-generated random secret.
|
|
57
|
+
* Those paths are single-process, so a module-scoped value is shared by sender
|
|
58
|
+
* and receiver there — no regression.
|
|
59
|
+
*/
|
|
60
|
+
let devRevalidateSecret;
|
|
61
|
+
function getRevalidateSecret() {
|
|
62
|
+
const baked = process.env.__VINEXT_REVALIDATE_SECRET;
|
|
63
|
+
if (baked) return baked;
|
|
64
|
+
if (devRevalidateSecret === void 0) {
|
|
65
|
+
const bytes = new Uint8Array(32);
|
|
66
|
+
crypto.getRandomValues(bytes);
|
|
67
|
+
devRevalidateSecret = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
68
|
+
}
|
|
69
|
+
return devRevalidateSecret;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Constant-time string equality. Avoids leaking secret length / prefix via
|
|
73
|
+
* early-exit timing on the on-demand revalidation auth check. Returns false
|
|
74
|
+
* for length mismatch (the only safe option without revealing the secret
|
|
75
|
+
* length, and equality is impossible anyway).
|
|
76
|
+
*/
|
|
77
|
+
function safeEqual(a, b) {
|
|
78
|
+
if (a.length !== b.length) return false;
|
|
79
|
+
let mismatch = 0;
|
|
80
|
+
for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
81
|
+
return mismatch === 0;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Authorize an incoming request as an on-demand revalidation trigger. Mirrors
|
|
85
|
+
* Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
|
|
86
|
+
* value must *equal* the process revalidate secret. Header presence alone is
|
|
87
|
+
* NOT sufficient — see the security note on {@link PRERENDER_REVALIDATE_HEADER}.
|
|
88
|
+
*/
|
|
89
|
+
function isOnDemandRevalidateRequest(headerValue) {
|
|
90
|
+
if (typeof headerValue !== "string" || headerValue.length === 0) return false;
|
|
91
|
+
return safeEqual(headerValue, getRevalidateSecret());
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
10
94
|
* Get a cache entry with staleness information.
|
|
11
95
|
*
|
|
12
96
|
* Returns { value, isStale: false } for fresh entries,
|
|
@@ -195,4 +279,4 @@ function getRevalidateDuration(key) {
|
|
|
195
279
|
return revalidateDurations.get(key);
|
|
196
280
|
}
|
|
197
281
|
//#endregion
|
|
198
|
-
export { appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
282
|
+
export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CacheControlMetadata } from "../shims/cache.js";
|
|
2
|
+
import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL } from "./cache-control.js";
|
|
3
|
+
|
|
4
|
+
//#region src/server/isr-decision.d.ts
|
|
5
|
+
type IsrDisposition = "HIT" | "STALE" | "MISS";
|
|
6
|
+
type IsrDecision = {
|
|
7
|
+
disposition: IsrDisposition; /** True when the caller must schedule a background regeneration. */
|
|
8
|
+
scheduleRegeneration: boolean; /** The `Cache-Control` string to stamp on the response. */
|
|
9
|
+
cacheControl: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Per-router special-case policies for `Cache-Control`.
|
|
13
|
+
*
|
|
14
|
+
* - `"app-page"` / `"pages"`: `buildCachedRevalidateCacheControl` for HIT/STALE.
|
|
15
|
+
* - `"app-route"`: same, but `revalidateSeconds=0` forces `NEVER_CACHE_CONTROL`
|
|
16
|
+
* and `revalidateSeconds=Infinity` forces `STATIC_CACHE_CONTROL`.
|
|
17
|
+
* - `"dev"`: like `"pages"`, but `revalidate=0`/`Infinity` guards are absent
|
|
18
|
+
* (dev never caches when revalidate=0 and never has Infinity entries in practice).
|
|
19
|
+
*/
|
|
20
|
+
type IsrPolicyKind = "app-page" | "app-route" | "pages" | "dev";
|
|
21
|
+
type DecideIsrOptions = {
|
|
22
|
+
/**
|
|
23
|
+
* The cache state. Content guards (kind-mismatch, empty body,
|
|
24
|
+
* query-variant-unproven) must have already passed before passing
|
|
25
|
+
* `"HIT"` or `"STALE"` here.
|
|
26
|
+
*/
|
|
27
|
+
cacheState: "HIT" | "STALE" | "MISS"; /** Which router is making the decision. */
|
|
28
|
+
kind: IsrPolicyKind;
|
|
29
|
+
/**
|
|
30
|
+
* The route's configured revalidate window in seconds. Used as the fallback
|
|
31
|
+
* when `cacheControlMeta` is absent.
|
|
32
|
+
*
|
|
33
|
+
* For `"dev"` call sites this is the only source of the revalidate value —
|
|
34
|
+
* dev never has metadata attached to a cache entry.
|
|
35
|
+
*/
|
|
36
|
+
revalidateSeconds: number;
|
|
37
|
+
/**
|
|
38
|
+
* The expire ceiling (seconds from epoch) read from the route config.
|
|
39
|
+
* Absent when the route pre-dates expire metadata support.
|
|
40
|
+
*/
|
|
41
|
+
expireSeconds?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Optional per-entry metadata written alongside the cache value.
|
|
44
|
+
* When present its `revalidate`/`expire` fields override the route defaults,
|
|
45
|
+
* exactly as the call sites do today with `cacheControl?.revalidate ?? revalidateSeconds`.
|
|
46
|
+
*/
|
|
47
|
+
cacheControlMeta?: CacheControlMetadata;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Derive the `Cache-Control` string for an ISR response.
|
|
51
|
+
*
|
|
52
|
+
* Content guards (kind mismatch, query-variant-unproven, empty body) are the
|
|
53
|
+
* caller's responsibility and must happen *before* this call. `cacheState`
|
|
54
|
+
* must only be `"HIT"` or `"STALE"` when those guards have already passed.
|
|
55
|
+
*/
|
|
56
|
+
declare function decideIsr(options: DecideIsrOptions): IsrDecision;
|
|
57
|
+
/**
|
|
58
|
+
* Build the `Cache-Control` string for a fresh MISS response whose ISR policy
|
|
59
|
+
* is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
|
|
60
|
+
* no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
|
|
61
|
+
*
|
|
62
|
+
* Separate from `decideIsr` because a MISS doesn't read a cache entry and
|
|
63
|
+
* therefore never has `cacheControlMeta`. `expireSeconds` here is the route
|
|
64
|
+
* config ceiling passed directly from the caller (not a per-entry fallback).
|
|
65
|
+
*/
|
|
66
|
+
declare function buildMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
|
|
67
|
+
/**
|
|
68
|
+
* Build the `Cache-Control` string for a fresh (MISS) app-route response.
|
|
69
|
+
*
|
|
70
|
+
* Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
|
|
71
|
+
* that `decideIsr` uses for app-route cached responses. `expireSeconds` is
|
|
72
|
+
* the route config ceiling passed directly (not per-entry metadata fallback).
|
|
73
|
+
*
|
|
74
|
+
* Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
|
|
75
|
+
* response that has no per-entry cache metadata.
|
|
76
|
+
*/
|
|
77
|
+
declare function buildAppRouteMissIsrCacheControl(revalidateSeconds: number, expireSeconds?: number): string;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
|