vinext 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +27 -1
- package/dist/build/client-build-config.js +58 -1
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +8 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/config/config-matchers.d.ts +20 -1
- package/dist/config/config-matchers.js +35 -1
- package/dist/config/next-config.d.ts +16 -3
- package/dist/config/next-config.js +30 -2
- package/dist/deploy.js +40 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +54 -4
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-server-entry.js +9 -1
- package/dist/index.js +162 -217
- package/dist/plugins/postcss.js +18 -14
- package/dist/plugins/require-context.d.ts +6 -0
- package/dist/plugins/require-context.js +184 -0
- package/dist/routing/app-route-graph.d.ts +12 -1
- package/dist/routing/app-route-graph.js +137 -5
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +16 -1
- package/dist/server/api-handler.js +4 -0
- package/dist/server/app-browser-entry.js +84 -39
- package/dist/server/app-browser-interception-context.d.ts +2 -1
- package/dist/server/app-browser-interception-context.js +15 -2
- package/dist/server/app-browser-navigation-controller.d.ts +11 -1
- package/dist/server/app-browser-navigation-controller.js +77 -1
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +6 -3
- package/dist/server/app-browser-visible-commit.js +9 -7
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-page-boundary-render.js +41 -19
- package/dist/server/app-page-dispatch.d.ts +6 -0
- package/dist/server/app-page-dispatch.js +3 -1
- package/dist/server/app-page-element-builder.d.ts +1 -0
- package/dist/server/app-page-element-builder.js +22 -10
- package/dist/server/app-page-render.d.ts +6 -0
- package/dist/server/app-page-render.js +5 -3
- package/dist/server/app-page-request.d.ts +8 -6
- package/dist/server/app-page-request.js +12 -9
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +2 -1
- package/dist/server/app-page-stream.d.ts +37 -2
- package/dist/server/app-page-stream.js +36 -3
- package/dist/server/app-pages-bridge.d.ts +16 -0
- package/dist/server/app-pages-bridge.js +23 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +2 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +3 -2
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +4 -3
- package/dist/server/app-rsc-route-matching.d.ts +20 -1
- package/dist/server/app-rsc-route-matching.js +29 -4
- package/dist/server/app-server-action-execution.d.ts +11 -1
- package/dist/server/app-server-action-execution.js +68 -10
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +17 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +54 -31
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/navigation-planner.js +5 -3
- package/dist/server/navigation-trace.d.ts +1 -1
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +4 -0
- package/dist/server/pages-page-data.d.ts +10 -7
- package/dist/server/pages-page-data.js +4 -2
- package/dist/server/pages-page-handler.d.ts +9 -2
- package/dist/server/pages-page-handler.js +29 -16
- package/dist/server/pages-page-response.d.ts +11 -2
- package/dist/server/pages-page-response.js +8 -1
- package/dist/server/pages-readiness.d.ts +36 -0
- package/dist/server/pages-readiness.js +21 -0
- package/dist/server/pages-request-pipeline.d.ts +99 -0
- package/dist/server/pages-request-pipeline.js +209 -0
- package/dist/server/pages-revalidate.d.ts +15 -0
- package/dist/server/pages-revalidate.js +19 -0
- package/dist/server/prod-server.d.ts +6 -2
- package/dist/server/prod-server.js +101 -217
- package/dist/server/socket-error-backstop.d.ts +19 -1
- package/dist/server/socket-error-backstop.js +77 -4
- package/dist/shims/app-router-scroll.js +22 -4
- package/dist/shims/cache-runtime.js +31 -1
- package/dist/shims/error-boundary.d.ts +21 -11
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/fetch-cache.d.ts +14 -1
- package/dist/shims/fetch-cache.js +18 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/internal/link-status-registry.d.ts +43 -0
- package/dist/shims/internal/link-status-registry.js +42 -0
- package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
- package/dist/shims/internal/route-pattern-for-warning.js +40 -0
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/link.js +20 -6
- package/dist/shims/navigation.d.ts +2 -2
- package/dist/shims/navigation.js +63 -7
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +2 -0
- package/dist/shims/router.d.ts +6 -3
- package/dist/shims/router.js +128 -21
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +30 -5
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/package.json +5 -1
|
@@ -110,6 +110,15 @@ function isRequestBodyTooLarge(error) {
|
|
|
110
110
|
return error instanceof Error && error.message === "Request body too large";
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
+
* Build the error thrown when a server-action request body exceeds the
|
|
114
|
+
* configured size limit. Matches Next.js' `Body exceeded {limit} limit.`
|
|
115
|
+
* message + docs link (action-handler.ts) verbatim — including the original
|
|
116
|
+
* config string (e.g. "2mb") — so it reads identically in logs.
|
|
117
|
+
*/
|
|
118
|
+
function createBodyExceededError(limitLabel) {
|
|
119
|
+
return /* @__PURE__ */ new Error(`Body exceeded ${limitLabel} limit.\nTo configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/api-reference/next-config-js/serverActions#bodysizelimit`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
113
122
|
* Collapse repeated `cookies().set(name, ...)` / `cookies().delete(name)`
|
|
114
123
|
* calls down to the last value per name, matching Next.js'
|
|
115
124
|
* `MutableRequestCookiesAdapter` semantics. Next.js stores response cookies in
|
|
@@ -325,7 +334,13 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
325
334
|
return payloadResponse;
|
|
326
335
|
}
|
|
327
336
|
const action = await options.decodeAction(body);
|
|
328
|
-
if (!isAppServerActionFunction(action))
|
|
337
|
+
if (!isAppServerActionFunction(action)) {
|
|
338
|
+
if (options.hasPageRoute) return createActionNotFoundResponse(null, {
|
|
339
|
+
clearRequestContext: options.clearRequestContext,
|
|
340
|
+
getAndClearPendingCookies: options.getAndClearPendingCookies
|
|
341
|
+
});
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
329
344
|
let actionRedirect = null;
|
|
330
345
|
let actionError = void 0;
|
|
331
346
|
let actionFailed = false;
|
|
@@ -411,23 +426,66 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
411
426
|
return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action parsing failed: " + getServerActionFailureMessage(error));
|
|
412
427
|
}
|
|
413
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Render the response for a fetch (client-invoked) server action whose request
|
|
431
|
+
* body exceeds the configured `serverActions.bodySizeLimit`.
|
|
432
|
+
*
|
|
433
|
+
* Next.js does not return a bare 413 here: it throws the body-exceeded error
|
|
434
|
+
* before the action runs, then — for fetch actions — emits a Flight response
|
|
435
|
+
* with status 500 carrying the rejected action result, so the nearest client
|
|
436
|
+
* error boundary catches it (see action-handler.ts, the `isFetchAction` branch
|
|
437
|
+
* of the generic error path). vinext mirrors that by rendering a Flight stream
|
|
438
|
+
* with `returnValue: { ok: false }` and no page root (the action never ran, so
|
|
439
|
+
* nothing was revalidated and the page render is skipped). A bare 413 plain
|
|
440
|
+
* response would bypass the boundary and surface the wrong status/content-type.
|
|
441
|
+
*/
|
|
442
|
+
async function renderFetchActionBodyExceededResponse(options) {
|
|
443
|
+
const error = createBodyExceededError(options.maxActionBodySizeLabel);
|
|
444
|
+
console.error("[vinext] Server action error:", error);
|
|
445
|
+
options.reportRequestError(normalizeError(error), {
|
|
446
|
+
path: options.cleanPathname,
|
|
447
|
+
method: options.request.method,
|
|
448
|
+
headers: Object.fromEntries(options.request.headers.entries())
|
|
449
|
+
}, {
|
|
450
|
+
routerKind: "App Router",
|
|
451
|
+
routePath: options.cleanPathname,
|
|
452
|
+
routeType: "action"
|
|
453
|
+
});
|
|
454
|
+
getAndClearActionRevalidationKind();
|
|
455
|
+
options.getAndClearPendingCookies();
|
|
456
|
+
const returnValue = {
|
|
457
|
+
ok: false,
|
|
458
|
+
data: options.sanitizeErrorForClient(error)
|
|
459
|
+
};
|
|
460
|
+
const temporaryReferences = options.createTemporaryReferenceSet();
|
|
461
|
+
const onRenderError = options.createRscOnErrorHandler(options.request, options.cleanPathname, options.cleanPathname);
|
|
462
|
+
const rscStream = await options.renderToReadableStream({ returnValue }, {
|
|
463
|
+
temporaryReferences,
|
|
464
|
+
onError: onRenderError
|
|
465
|
+
});
|
|
466
|
+
const headers = new Headers({
|
|
467
|
+
"Content-Type": VINEXT_RSC_CONTENT_TYPE,
|
|
468
|
+
Vary: VINEXT_RSC_VARY_HEADER
|
|
469
|
+
});
|
|
470
|
+
applyEdgeRuntimeHeader(headers, options.isEdgeRuntime);
|
|
471
|
+
mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
|
|
472
|
+
applyRscCompatibilityIdHeader(headers);
|
|
473
|
+
return createServerActionRscResponse(rscStream, {
|
|
474
|
+
status: 500,
|
|
475
|
+
headers
|
|
476
|
+
}, options.clearRequestContext);
|
|
477
|
+
}
|
|
414
478
|
async function handleServerActionRscRequest(options) {
|
|
415
479
|
if (options.request.method.toUpperCase() !== "POST" || !options.actionId) return null;
|
|
416
480
|
const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
|
|
417
481
|
if (csrfResponse) return csrfResponse;
|
|
418
|
-
if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize)
|
|
419
|
-
options.clearRequestContext();
|
|
420
|
-
return payloadTooLargeResponse();
|
|
421
|
-
}
|
|
482
|
+
if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) return renderFetchActionBodyExceededResponse(options);
|
|
422
483
|
try {
|
|
423
484
|
let body;
|
|
424
485
|
try {
|
|
425
486
|
body = options.contentType.startsWith("multipart/form-data") ? await options.readFormDataWithLimit(options.request, options.maxActionBodySize) : await options.readBodyWithLimit(options.request, options.maxActionBodySize);
|
|
426
487
|
} catch (error) {
|
|
427
|
-
if (isRequestBodyTooLarge(error))
|
|
428
|
-
options.clearRequestContext();
|
|
429
|
-
return payloadTooLargeResponse();
|
|
430
|
-
}
|
|
488
|
+
if (isRequestBodyTooLarge(error)) return renderFetchActionBodyExceededResponse(options);
|
|
431
489
|
throw error;
|
|
432
490
|
}
|
|
433
491
|
const payloadResponse = await validateServerActionPayload(body);
|
|
@@ -599,7 +657,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
599
657
|
let errorPattern = match ? match.route.pattern : options.cleanPathname;
|
|
600
658
|
if (match) {
|
|
601
659
|
const { route: actionRoute, params: actionParams } = match;
|
|
602
|
-
const actionRerenderTarget = resolveAppPageActionRerenderTarget({
|
|
660
|
+
const actionRerenderTarget = await resolveAppPageActionRerenderTarget({
|
|
603
661
|
cleanPathname: options.cleanPathname,
|
|
604
662
|
currentParams: actionParams,
|
|
605
663
|
currentRoute: actionRoute,
|
|
@@ -30,6 +30,12 @@ declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navContext: Na
|
|
|
30
30
|
* SSR head. Undefined or empty disables emission entirely.
|
|
31
31
|
*/
|
|
32
32
|
clientTraceMetadata?: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Maximum total length (in characters) of the preload `Link` header React
|
|
35
|
+
* emits during SSR. `0` disables emission. From `reactMaxHeadersLength` in
|
|
36
|
+
* `next.config`. Undefined falls back to React's own default.
|
|
37
|
+
*/
|
|
38
|
+
reactMaxHeadersLength?: number;
|
|
33
39
|
rootParams?: RootParams; /** Dev-only: original server error to surface in the browser overlay. */
|
|
34
40
|
initialDevServerError?: unknown;
|
|
35
41
|
/** When true, wait for the full React tree (including Suspense boundaries)
|
|
@@ -24,6 +24,13 @@ import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.e
|
|
|
24
24
|
import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
|
|
25
25
|
import clientReferences from "virtual:vite-rsc/client-references";
|
|
26
26
|
//#region src/server/app-ssr-entry.ts
|
|
27
|
+
/**
|
|
28
|
+
* Default cap for the preload `Link` header, matching Next.js's
|
|
29
|
+
* `defaultConfig.reactMaxHeadersLength`. Used when no config value threads
|
|
30
|
+
* through (e.g. error-boundary renders) so React's internal cap agrees with
|
|
31
|
+
* the response-layer combine cap.
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6e3;
|
|
27
34
|
const clientReferencePreloader = createClientReferencePreloader({
|
|
28
35
|
getReferences() {
|
|
29
36
|
return clientReferences;
|
|
@@ -175,7 +182,15 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
175
182
|
const ssrRoot = withScriptNonce(createElement(BeforeInteractiveContext.Provider, { value: registerBeforeInteractiveInlineScript }, ssrTree), options?.scriptNonce);
|
|
176
183
|
const bootstrapModuleUrl = extractBootstrapModuleUrl(await import.meta.viteRsc.loadBootstrapScriptContent("index"));
|
|
177
184
|
const errorMetaRenderer = createSsrErrorMetaRenderer({ basePath: options?.basePath });
|
|
185
|
+
let reactLinkHeader = "";
|
|
186
|
+
const maxHeadersLength = options?.reactMaxHeadersLength ?? DEFAULT_REACT_MAX_HEADERS_LENGTH;
|
|
187
|
+
const captureHeaders = maxHeadersLength > 0;
|
|
178
188
|
const htmlStream = await renderToReadableStream(ssrRoot, {
|
|
189
|
+
onHeaders: captureHeaders ? (headers) => {
|
|
190
|
+
const link = headers.get("Link");
|
|
191
|
+
if (link) reactLinkHeader = link;
|
|
192
|
+
} : void 0,
|
|
193
|
+
maxHeadersLength: captureHeaders ? maxHeadersLength : void 0,
|
|
179
194
|
bootstrapModules: bootstrapModuleUrl ? [bootstrapModuleUrl] : void 0,
|
|
180
195
|
formState: options?.formState ?? null,
|
|
181
196
|
nonce: options?.scriptNonce,
|
|
@@ -210,7 +225,8 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
210
225
|
return {
|
|
211
226
|
htmlStream: deferUntilStreamConsumed(htmlStream.pipeThrough(createTickBufferedTransform(rscEmbed, getInsertedHTML, getBeforeInteractiveHeadHTML, inlineCssManifest, inlineCssFontStyles, inlineCssFontStyleFallbackHTML, options?.scriptNonce)), cleanup),
|
|
212
227
|
metadataReady: Promise.resolve(),
|
|
213
|
-
capturedRscData: options?.capturedRscDataRef?.value ?? null
|
|
228
|
+
capturedRscData: options?.capturedRscDataRef?.value ?? null,
|
|
229
|
+
linkHeader: reactLinkHeader
|
|
214
230
|
};
|
|
215
231
|
} catch (error) {
|
|
216
232
|
cleanup();
|
|
@@ -36,7 +36,7 @@ declare function parseCookieLocale(req: IncomingMessage, i18nConfig: NextI18nCon
|
|
|
36
36
|
* 4. Render the component to HTML
|
|
37
37
|
* 5. Wrap in _document shell and send response
|
|
38
38
|
*/
|
|
39
|
-
declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean,
|
|
39
|
+
declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean, hasRewrites?: boolean,
|
|
40
40
|
/**
|
|
41
41
|
* Allow-list of OpenTelemetry propagation keys to emit as `<meta>` tags
|
|
42
42
|
* in the SSR head. Sourced from `experimental.clientTraceMetadata` in
|
|
@@ -6,9 +6,9 @@ import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
|
6
6
|
import { importModule, reportRequestError } from "./instrumentation.js";
|
|
7
7
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
8
8
|
import { _runWithCacheState } from "../shims/cache.js";
|
|
9
|
-
import { buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
|
|
10
|
-
import { runWithPrivateCache } from "../shims/cache-runtime.js";
|
|
9
|
+
import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
|
|
11
10
|
import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
|
|
11
|
+
import { runWithPrivateCache } from "../shims/cache-runtime.js";
|
|
12
12
|
import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
|
|
13
13
|
import "../shims/router-state.js";
|
|
14
14
|
import { runWithHeadState } from "../shims/head-state.js";
|
|
@@ -20,6 +20,7 @@ import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
|
|
|
20
20
|
import { logRequest, now } from "./request-log.js";
|
|
21
21
|
import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFromUrl$1, parseCookieLocaleFromHeader, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
22
22
|
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
23
|
+
import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
23
24
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
24
25
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
25
26
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
@@ -183,8 +184,9 @@ function parseCookieLocale(req, i18nConfig) {
|
|
|
183
184
|
* 4. Render the component to HTML
|
|
184
185
|
* 5. Wrap in _document shell and send response
|
|
185
186
|
*/
|
|
186
|
-
function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, clientTraceMetadata) {
|
|
187
|
+
function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata) {
|
|
187
188
|
const matcher = fileMatcher ?? createValidFileMatcher();
|
|
189
|
+
const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
|
|
188
190
|
const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
|
|
189
191
|
_alsRegistration.catch(() => {});
|
|
190
192
|
return async (req, res, url, statusCode, isDataReq = false) => {
|
|
@@ -241,15 +243,6 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
241
243
|
try {
|
|
242
244
|
await _alsRegistration;
|
|
243
245
|
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
246
|
if (i18nConfig) {
|
|
254
247
|
await runner.import("vinext/i18n-state");
|
|
255
248
|
const i18nCtx = await importModule(runner, "vinext/i18n-context");
|
|
@@ -262,6 +255,28 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
262
255
|
});
|
|
263
256
|
}
|
|
264
257
|
const pageModule = await importModule(runner, route.filePath);
|
|
258
|
+
let AppComponent = null;
|
|
259
|
+
const appPath = path.join(pagesDir, "_app");
|
|
260
|
+
if (findFileWithExtensions(appPath, matcher)) try {
|
|
261
|
+
AppComponent = (await importModule(runner, appPath)).default ?? null;
|
|
262
|
+
} catch {}
|
|
263
|
+
const pagesNextData = buildPagesReadinessNextData({
|
|
264
|
+
pageModule,
|
|
265
|
+
appComponent: AppComponent,
|
|
266
|
+
hasRewrites
|
|
267
|
+
});
|
|
268
|
+
const navigationIsReady = typeof routerShim.getPagesNavigationIsReadyFromSerializedState === "function" ? routerShim.getPagesNavigationIsReadyFromSerializedState(patternToNextFormat(route.pattern), new URL(url, "http://_").search, pagesNextData) : true;
|
|
269
|
+
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext({
|
|
270
|
+
pathname: patternToNextFormat(route.pattern),
|
|
271
|
+
query,
|
|
272
|
+
asPath: url,
|
|
273
|
+
navigationIsReady,
|
|
274
|
+
nextData: pagesNextData,
|
|
275
|
+
locale: locale ?? currentDefaultLocale,
|
|
276
|
+
locales: i18nConfig?.locales,
|
|
277
|
+
defaultLocale: currentDefaultLocale,
|
|
278
|
+
domainLocales
|
|
279
|
+
});
|
|
265
280
|
_compileEnd = now();
|
|
266
281
|
const PageComponent = pageModule.default;
|
|
267
282
|
if (!PageComponent) {
|
|
@@ -323,6 +338,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
323
338
|
pathname: patternToNextFormat(route.pattern),
|
|
324
339
|
query,
|
|
325
340
|
asPath: url,
|
|
341
|
+
navigationIsReady: false,
|
|
326
342
|
locale: locale ?? currentDefaultLocale,
|
|
327
343
|
locales: i18nConfig?.locales,
|
|
328
344
|
defaultLocale: currentDefaultLocale,
|
|
@@ -385,7 +401,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
385
401
|
if (typeof pageModule.getStaticProps === "function" && !isFallbackRender) {
|
|
386
402
|
const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
|
|
387
403
|
const cached = await isrGet(cacheKey);
|
|
388
|
-
|
|
404
|
+
const isOnDemandRevalidate = isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]);
|
|
405
|
+
if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
389
406
|
const cachedHtml = cached.value.value.html;
|
|
390
407
|
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
391
408
|
const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
|
|
@@ -399,7 +416,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
399
416
|
res.end(transformedHtml);
|
|
400
417
|
return;
|
|
401
418
|
}
|
|
402
|
-
if (cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
419
|
+
if (!isOnDemandRevalidate && cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
|
|
403
420
|
const cachedHtml = cached.value.value.html;
|
|
404
421
|
const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
|
|
405
422
|
triggerBackgroundRegeneration(cacheKey, async () => {
|
|
@@ -420,6 +437,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
420
437
|
pathname: patternToNextFormat(route.pattern),
|
|
421
438
|
query,
|
|
422
439
|
asPath: url,
|
|
440
|
+
navigationIsReady,
|
|
423
441
|
locale: locale ?? currentDefaultLocale,
|
|
424
442
|
locales: i18nConfig?.locales,
|
|
425
443
|
defaultLocale: currentDefaultLocale,
|
|
@@ -450,6 +468,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
450
468
|
const viteRoot = server.config?.root;
|
|
451
469
|
const regenPageUrl = viteRoot ? "/" + path.relative(viteRoot, route.filePath) : route.filePath;
|
|
452
470
|
const regenAppUrl = RegenApp ? viteRoot ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : path.join(pagesDir, "_app") : null;
|
|
471
|
+
const freshPagesNextData = {
|
|
472
|
+
...pagesNextData,
|
|
473
|
+
__vinext: {
|
|
474
|
+
...pagesNextData.__vinext,
|
|
475
|
+
pageModuleUrl: regenPageUrl,
|
|
476
|
+
appModuleUrl: regenAppUrl,
|
|
477
|
+
hasMiddleware
|
|
478
|
+
}
|
|
479
|
+
};
|
|
453
480
|
await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script>window.__NEXT_DATA__ = ${safeJsonStringify({
|
|
454
481
|
props: { pageProps: freshProps },
|
|
455
482
|
page: patternToNextFormat(route.pattern),
|
|
@@ -460,11 +487,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
460
487
|
locales: i18nConfig?.locales,
|
|
461
488
|
defaultLocale: currentDefaultLocale,
|
|
462
489
|
domainLocales,
|
|
463
|
-
|
|
464
|
-
pageModuleUrl: regenPageUrl,
|
|
465
|
-
appModuleUrl: regenAppUrl,
|
|
466
|
-
hasMiddleware
|
|
467
|
-
}
|
|
490
|
+
...freshPagesNextData
|
|
468
491
|
})}${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
492
|
setRevalidateDuration(cacheKey, revalidate);
|
|
470
493
|
}
|
|
@@ -491,7 +514,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
491
514
|
locale: locale ?? currentDefaultLocale,
|
|
492
515
|
locales: i18nConfig?.locales,
|
|
493
516
|
defaultLocale: currentDefaultLocale,
|
|
494
|
-
revalidateReason: "stale"
|
|
517
|
+
revalidateReason: isOnDemandRevalidate ? "on-demand" : "stale"
|
|
495
518
|
};
|
|
496
519
|
const result = await pageModule.getStaticProps(context);
|
|
497
520
|
if (result && "props" in result) pageProps = result.props;
|
|
@@ -536,11 +559,6 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
536
559
|
_renderEnd = now();
|
|
537
560
|
return;
|
|
538
561
|
}
|
|
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
562
|
const createElement = React.createElement;
|
|
545
563
|
let element;
|
|
546
564
|
const wrapWithRouterContext = routerShim.wrapWithRouterContext;
|
|
@@ -584,6 +602,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
584
602
|
const viteRoot = server.config.root;
|
|
585
603
|
const pageModuleUrl = "/" + path.relative(viteRoot, route.filePath);
|
|
586
604
|
const appModuleUrl = AppComponent ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : null;
|
|
605
|
+
const serializedPagesNextData = {
|
|
606
|
+
...pagesNextData,
|
|
607
|
+
__vinext: {
|
|
608
|
+
...pagesNextData.__vinext,
|
|
609
|
+
pageModuleUrl,
|
|
610
|
+
appModuleUrl,
|
|
611
|
+
hasMiddleware
|
|
612
|
+
}
|
|
613
|
+
};
|
|
587
614
|
const hydrationScript = `
|
|
588
615
|
<script type="module"${nonceAttr}>
|
|
589
616
|
import "vinext/instrumentation-client";
|
|
@@ -641,18 +668,14 @@ hydrate();
|
|
|
641
668
|
locales: i18nConfig?.locales,
|
|
642
669
|
defaultLocale: currentDefaultLocale,
|
|
643
670
|
domainLocales,
|
|
644
|
-
|
|
645
|
-
pageModuleUrl,
|
|
646
|
-
appModuleUrl,
|
|
647
|
-
hasMiddleware
|
|
648
|
-
}
|
|
671
|
+
...serializedPagesNextData
|
|
649
672
|
})}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}`, scriptNonce);
|
|
650
673
|
const docPath = path.join(pagesDir, "_document");
|
|
651
674
|
let DocumentComponent = null;
|
|
652
675
|
if (findFileWithExtensions(docPath, matcher)) try {
|
|
653
676
|
DocumentComponent = (await runner.import(docPath)).default ?? null;
|
|
654
677
|
} catch {}
|
|
655
|
-
const allScripts = `${nextDataScript}\n ${hydrationScript}`;
|
|
678
|
+
const allScripts = `${nextDataScript}\n ${createInlineScriptTag(`window.__VINEXT_PAGE_PATTERNS__=${safeJsonStringify(pagePatterns)}`, scriptNonce)}\n ${hydrationScript}`;
|
|
656
679
|
const extraHeaders = { ...gsspExtraHeaders };
|
|
657
680
|
if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] = "no-store, must-revalidate";
|
|
658
681
|
else {
|
|
@@ -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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
|
-
import { matchRoutePattern, matchRoutePatternPrefix } from "../routing/route-pattern.js";
|
|
2
|
+
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
3
3
|
import { compareAppElementsSlotIds } from "./app-elements-wire.js";
|
|
4
4
|
import "./app-elements.js";
|
|
5
5
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
@@ -119,8 +119,10 @@ function findRouteManifestInterceptionForProof(routeManifest, proof) {
|
|
|
119
119
|
const candidateInterceptions = routeManifest.segmentGraph.interceptionsBySlotId.get(proof.slotId) ?? [];
|
|
120
120
|
for (const interception of candidateInterceptions) {
|
|
121
121
|
if (!matchRoutePatternPrefix(sourceParts, interception.sourcePatternParts)) continue;
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
const exactTargetParams = matchRoutePattern(targetParts, interception.targetPatternParts);
|
|
123
|
+
const allowsMiddlewareRewriteTarget = exactTargetParams === null && matchRoutePatternWithOptionalDynamicSegments(targetParts, interception.targetPatternParts);
|
|
124
|
+
if (exactTargetParams === null && !allowsMiddlewareRewriteTarget) continue;
|
|
125
|
+
if (!allowsMiddlewareRewriteTarget && interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
|
|
124
126
|
return interception;
|
|
125
127
|
}
|
|
126
128
|
return null;
|
|
@@ -47,4 +47,4 @@ declare function createNavigationLifecycleTraceFields(options: {
|
|
|
47
47
|
declare function createNavigationTrace(code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
|
|
48
48
|
declare function prependNavigationTraceEntry(trace: NavigationTrace, code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
|
|
49
49
|
//#endregion
|
|
50
|
-
export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace,
|
|
50
|
+
export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PagesBodyParseError } from "./pages-media-type.js";
|
|
2
|
+
import { RevalidateOptions } from "./pages-revalidate.js";
|
|
2
3
|
|
|
3
4
|
//#region src/server/pages-node-compat.d.ts
|
|
4
5
|
type PagesRequestQuery = Record<string, string | string[]>;
|
|
@@ -33,6 +34,7 @@ type PagesReqResResponse = {
|
|
|
33
34
|
send: (data: unknown) => void;
|
|
34
35
|
redirect: (statusOrUrl: number | string, url?: string) => void;
|
|
35
36
|
getHeaders: () => PagesReqResHeaders;
|
|
37
|
+
revalidate: (urlPath: string, opts?: RevalidateOptions) => Promise<void>;
|
|
36
38
|
};
|
|
37
39
|
type CreatePagesReqResOptions = {
|
|
38
40
|
body: unknown;
|
|
@@ -2,6 +2,7 @@ import { parseCookies } from "../config/config-matchers.js";
|
|
|
2
2
|
import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
3
3
|
import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
|
|
4
4
|
import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
|
|
5
|
+
import { performOnDemandRevalidate } from "./pages-revalidate.js";
|
|
5
6
|
import { decode } from "node:querystring";
|
|
6
7
|
//#region src/server/pages-node-compat.ts
|
|
7
8
|
const MAX_PAGES_API_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;
|
|
@@ -166,6 +167,9 @@ function createPagesReqRes(options) {
|
|
|
166
167
|
const headers = { ...resHeaders };
|
|
167
168
|
if (setCookieHeaders.length > 0) headers["set-cookie"] = setCookieHeaders;
|
|
168
169
|
return headers;
|
|
170
|
+
},
|
|
171
|
+
async revalidate(urlPath, opts) {
|
|
172
|
+
await performOnDemandRevalidate(options.request.headers, urlPath, opts);
|
|
169
173
|
}
|
|
170
174
|
};
|
|
171
175
|
return {
|
|
@@ -2,7 +2,7 @@ import { Route } from "../routing/pages-router.js";
|
|
|
2
2
|
import { VinextNextData } from "../client/vinext-next-data.js";
|
|
3
3
|
import { CachedPagesValue } from "../shims/cache.js";
|
|
4
4
|
import { ISRCacheEntry } from "./isr-cache.js";
|
|
5
|
-
import { PagesGsspResponse, PagesI18nRenderContext } from "./pages-page-response.js";
|
|
5
|
+
import { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras } from "./pages-page-response.js";
|
|
6
6
|
import { ReactNode } from "react";
|
|
7
7
|
|
|
8
8
|
//#region src/server/pages-page-data.d.ts
|
|
@@ -90,6 +90,7 @@ type RenderPagesIsrHtmlOptions = {
|
|
|
90
90
|
routePattern: string;
|
|
91
91
|
safeJsonStringify: (value: unknown) => string;
|
|
92
92
|
vinext?: VinextNextData["__vinext"];
|
|
93
|
+
nextData?: PagesNextDataExtras;
|
|
93
94
|
};
|
|
94
95
|
type ResolvePagesPageDataOptions = {
|
|
95
96
|
applyRequestContexts: () => void;
|
|
@@ -125,15 +126,16 @@ type ResolvePagesPageDataOptions = {
|
|
|
125
126
|
* When true, this dispatch was triggered by an on-demand revalidation
|
|
126
127
|
* request (e.g. `res.revalidate()` in a Pages Router API route, or an
|
|
127
128
|
* equivalent webhook). Maps to `revalidateReason: "on-demand"` when
|
|
128
|
-
* `getStaticProps` is invoked
|
|
129
|
+
* `getStaticProps` is invoked, and bypasses the fresh/stale cache-hit
|
|
130
|
+
* short-circuits so the entry is regenerated synchronously. Mirrors Next.js's
|
|
129
131
|
* `renderOpts.isOnDemandRevalidate` flag — see
|
|
130
132
|
* `.nextjs-ref/packages/next/src/server/render.tsx`.
|
|
131
133
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
134
|
+
* The page handler sets this only when the incoming request's
|
|
135
|
+
* `x-prerender-revalidate` header (`PRERENDER_REVALIDATE_HEADER`) *equals* the
|
|
136
|
+
* process revalidate secret that `res.revalidate()` attaches to its internal
|
|
137
|
+
* request (`isOnDemandRevalidateRequest`). It is never set on mere header
|
|
138
|
+
* presence — see the security note in `isr-cache.ts`.
|
|
137
139
|
*/
|
|
138
140
|
isOnDemandRevalidate?: boolean;
|
|
139
141
|
pageModule: PagesPageModule;
|
|
@@ -155,6 +157,7 @@ type ResolvePagesPageDataOptions = {
|
|
|
155
157
|
}) => void;
|
|
156
158
|
renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
|
|
157
159
|
vinext?: VinextNextData["__vinext"];
|
|
160
|
+
nextData?: PagesNextDataExtras;
|
|
158
161
|
};
|
|
159
162
|
type ResolvePagesPageDataRenderResult = {
|
|
160
163
|
kind: "render";
|