vinext 1.0.0 → 1.1.0
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 +72 -1
- package/THIRD_PARTY_LICENSES.md +26 -0
- package/dist/build/app-page-static-eligibility.d.ts +15 -0
- package/dist/build/app-page-static-eligibility.js +82 -0
- package/dist/build/lifecycle.js +10 -10
- package/dist/build/pages-client-assets-module.js +6 -1
- package/dist/build/precompress.js +1 -1
- package/dist/build/prerender-paths.js +6 -85
- package/dist/build/prerender.d.ts +8 -1
- package/dist/build/prerender.js +167 -46
- package/dist/build/run-prerender.js +4 -3
- package/dist/build/server-entry.d.ts +2 -0
- package/dist/build/server-entry.js +7 -1
- package/dist/build/server-manifest.d.ts +7 -0
- package/dist/build/server-manifest.js +11 -1
- package/dist/cache/cache-adapters-virtual.d.ts +7 -1
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/client/browser-globals.d.ts +4 -0
- package/dist/client/browser-globals.js +5 -0
- package/dist/client/navigation-runtime.d.ts +11 -1
- package/dist/client/navigation-runtime.js +3 -3
- package/dist/config/config-matchers.js +3 -1
- package/dist/config/dotenv.js +14 -3
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +67 -45
- package/dist/index.d.ts +4 -1
- package/dist/index.js +200 -100
- package/dist/init-cloudflare.d.ts +1 -1
- package/dist/init-cloudflare.js +6 -6
- package/dist/init-platform.js +1 -1
- package/dist/init.js +4 -2
- package/dist/plugins/cache-flight-codec.d.ts +19 -0
- package/dist/plugins/cache-flight-codec.js +68 -0
- package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
- package/dist/plugins/commonjs-esm-facade.js +80 -0
- package/dist/plugins/commonjs-syntax.d.ts +16 -0
- package/dist/plugins/commonjs-syntax.js +22 -0
- package/dist/plugins/fonts.d.ts +8 -11
- package/dist/plugins/fonts.js +71 -34
- package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
- package/dist/plugins/ignore-dynamic-requests.js +54 -3
- package/dist/plugins/import-meta-url.js +6 -6
- package/dist/plugins/og-asset-ownership.d.ts +2 -0
- package/dist/plugins/og-asset-ownership.js +24 -13
- package/dist/plugins/og-assets.d.ts +18 -0
- package/dist/plugins/og-assets.js +50 -8
- package/dist/plugins/og-harfbuzz.js +38 -27
- package/dist/plugins/optimize-imports.js +2 -1
- package/dist/plugins/require-context.d.ts +1 -0
- package/dist/plugins/require-context.js +28 -1
- package/dist/plugins/scan-build-css.d.ts +12 -0
- package/dist/plugins/scan-build-css.js +39 -0
- package/dist/plugins/strip-server-exports.d.ts +2 -0
- package/dist/plugins/strip-server-exports.js +3 -1
- package/dist/plugins/transform-result.d.ts +18 -0
- package/dist/plugins/transform-result.js +23 -2
- package/dist/plugins/typeof-window.d.ts +10 -0
- package/dist/plugins/typeof-window.js +33 -9
- package/dist/routing/app-route-graph.d.ts +0 -1
- package/dist/routing/app-route-graph.js +2 -4
- package/dist/routing/app-route-ids.d.ts +3 -0
- package/dist/routing/app-route-ids.js +6 -0
- package/dist/routing/file-matcher.d.ts +1 -0
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/api-handler.js +17 -7
- package/dist/server/app-bfcache-id.d.ts +1 -0
- package/dist/server/app-bfcache-id.js +7 -1
- package/dist/server/app-bfcache-identity.js +1 -2
- package/dist/server/app-browser-entry.js +15 -9
- package/dist/server/app-browser-history-controller.d.ts +2 -0
- package/dist/server/app-browser-history-controller.js +8 -0
- package/dist/server/app-browser-hydration.d.ts +1 -1
- package/dist/server/app-browser-hydration.js +2 -2
- package/dist/server/app-browser-interception-context.js +26 -2
- package/dist/server/app-browser-navigation-controller.d.ts +7 -1
- package/dist/server/app-browser-navigation-controller.js +94 -11
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-stream.d.ts +1 -1
- package/dist/server/app-fallback-renderer.d.ts +2 -0
- package/dist/server/app-fallback-renderer.js +1 -0
- package/dist/server/app-history-state.d.ts +3 -1
- package/dist/server/app-history-state.js +9 -5
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-optimistic-routing.js +1 -1
- package/dist/server/app-page-boundary-render.d.ts +5 -0
- package/dist/server/app-page-boundary-render.js +6 -4
- package/dist/server/app-page-cache-finalizer.d.ts +11 -0
- package/dist/server/app-page-cache-finalizer.js +41 -7
- package/dist/server/app-page-cache-render.d.ts +18 -0
- package/dist/server/app-page-cache-render.js +65 -44
- package/dist/server/app-page-cache.d.ts +5 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +9 -1
- package/dist/server/app-page-dispatch.js +47 -30
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +27 -42
- package/dist/server/app-page-execution.d.ts +15 -12
- package/dist/server/app-page-execution.js +30 -40
- package/dist/server/app-page-head.d.ts +5 -0
- package/dist/server/app-page-head.js +9 -3
- package/dist/server/app-page-probe.d.ts +0 -109
- package/dist/server/app-page-probe.js +3 -136
- package/dist/server/app-page-render-identity.d.ts +0 -1
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +13 -5
- package/dist/server/app-page-render.js +219 -118
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +1 -2
- package/dist/server/app-page-response.d.ts +4 -3
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +55 -33
- package/dist/server/app-page-rsc-render-status.d.ts +48 -0
- package/dist/server/app-page-rsc-render-status.js +54 -0
- package/dist/server/app-page-search-params-observation.js +1 -1
- package/dist/server/app-page-stream.d.ts +15 -8
- package/dist/server/app-page-stream.js +7 -1
- package/dist/server/app-page-tracing.js +2 -1
- package/dist/server/app-pages-bridge.js +3 -2
- package/dist/server/app-render-abort-error.d.ts +3 -0
- package/dist/server/app-render-abort-error.js +8 -0
- package/dist/server/app-request-stage-dispatch.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +8 -11
- package/dist/server/app-response-header-provenance.d.ts +2 -0
- package/dist/server/app-response-header-provenance.js +10 -4
- package/dist/server/app-route-handler-cache.d.ts +2 -1
- package/dist/server/app-route-handler-cache.js +11 -7
- package/dist/server/app-route-handler-dispatch.js +11 -4
- package/dist/server/app-route-handler-execution.d.ts +9 -1
- package/dist/server/app-route-handler-execution.js +28 -13
- package/dist/server/app-route-handler-policy.js +2 -2
- package/dist/server/app-route-handler-response.d.ts +1 -1
- package/dist/server/app-route-handler-response.js +14 -5
- package/dist/server/app-router-entry.d.ts +2 -1
- package/dist/server/app-router-entry.js +8 -12
- package/dist/server/app-rsc-errors.d.ts +0 -1
- package/dist/server/app-rsc-errors.js +2 -6
- package/dist/server/app-rsc-handler.d.ts +5 -0
- package/dist/server/app-rsc-handler.js +229 -102
- package/dist/server/app-rsc-request-normalization.d.ts +4 -2
- package/dist/server/app-rsc-request-normalization.js +7 -4
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.d.ts +1 -1
- package/dist/server/app-rsc-response-stage.js +6 -4
- package/dist/server/app-rsc-route-matching.d.ts +9 -1
- package/dist/server/app-rsc-route-matching.js +37 -20
- package/dist/server/app-server-action-execution.d.ts +2 -1
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +3 -2
- package/dist/server/app-ssr-entry.js +9 -7
- package/dist/server/app-ssr-search-params-gate.js +1 -1
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +35 -16
- package/dist/server/app-worker-stages.d.ts +2 -2
- package/dist/server/app-worker-stages.js +3 -3
- package/dist/server/cache-control.js +3 -11
- package/dist/server/cacheability-request.js +56 -52
- package/dist/server/config-headers.js +3 -1
- package/dist/server/dev-server.js +23 -5
- package/dist/server/headers.d.ts +16 -0
- package/dist/server/headers.js +19 -1
- package/dist/server/http-error-responses.js +1 -0
- package/dist/server/image-optimization.d.ts +16 -0
- package/dist/server/image-optimization.js +26 -6
- package/dist/server/isr-cache.d.ts +2 -0
- package/dist/server/isr-cache.js +8 -3
- package/dist/server/metadata-route-response.js +94 -37
- package/dist/server/middleware-matcher-pattern.js +8 -4
- package/dist/server/middleware-response-headers.js +2 -0
- package/dist/server/multi-stage.d.ts +8 -0
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/node-response-signal.d.ts +16 -0
- package/dist/server/node-response-signal.js +26 -0
- package/dist/server/open-redirect.d.ts +47 -0
- package/dist/server/open-redirect.js +74 -1
- package/dist/server/pages-api-route.js +8 -2
- package/dist/server/pages-asset-tags.d.ts +1 -0
- package/dist/server/pages-asset-tags.js +73 -22
- package/dist/server/pages-client-assets.d.ts +2 -0
- package/dist/server/pages-page-data.d.ts +6 -1
- package/dist/server/pages-page-data.js +71 -65
- package/dist/server/pages-page-handler.js +50 -15
- package/dist/server/pages-page-response.js +3 -1
- package/dist/server/pages-request-pipeline.d.ts +2 -1
- package/dist/server/pages-request-pipeline.js +20 -5
- package/dist/server/pages-request-stage-entry.js +14 -8
- package/dist/server/prerender-manifest.d.ts +6 -0
- package/dist/server/prerender-manifest.js +1 -1
- package/dist/server/prod-server.d.ts +8 -7
- package/dist/server/prod-server.js +152 -78
- package/dist/server/request-pipeline.d.ts +20 -8
- package/dist/server/request-pipeline.js +21 -7
- package/dist/server/response-compression.d.ts +43 -0
- package/dist/server/response-compression.js +111 -0
- package/dist/server/response-stage-cacheability.js +1 -1
- package/dist/server/seed-cache.js +10 -7
- package/dist/server/socket-error-backstop.d.ts +14 -0
- package/dist/server/socket-error-backstop.js +27 -4
- package/dist/server/worker-revalidation-context.d.ts +2 -1
- package/dist/server/worker-revalidation-context.js +1 -0
- package/dist/server/workers-tracing.d.ts +17 -0
- package/dist/server/workers-tracing.js +48 -8
- package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
- package/dist/shims/cache-adapter-instantiate.js +118 -0
- package/dist/shims/cache-callable-runtime.d.ts +2 -40
- package/dist/shims/cache-callable-runtime.js +50 -168
- package/dist/shims/cache-flight-arguments.d.ts +22 -0
- package/dist/shims/cache-flight-arguments.js +56 -0
- package/dist/shims/cache-request-state.d.ts +18 -14
- package/dist/shims/cache-request-state.js +33 -21
- package/dist/shims/cache-runtime.d.ts +6 -19
- package/dist/shims/cache-runtime.js +168 -226
- package/dist/shims/cache.d.ts +3 -2
- package/dist/shims/cache.js +8 -5
- package/dist/shims/cacheability-classification.d.ts +8 -1
- package/dist/shims/cacheability-classification.js +2 -2
- package/dist/shims/cdn-cache.d.ts +10 -1
- package/dist/shims/cdn-cache.js +16 -9
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +19 -1
- package/dist/shims/error-boundary.d.ts +10 -10
- package/dist/shims/error-boundary.js +26 -7
- package/dist/shims/error.d.ts +4 -18
- package/dist/shims/error.js +17 -10
- package/dist/shims/error.react-server.d.ts +4 -6
- package/dist/shims/error.react-server.js +4 -1
- package/dist/shims/fetch-cache.d.ts +6 -0
- package/dist/shims/fetch-cache.js +23 -4
- package/dist/shims/form.js +1 -1
- package/dist/shims/headers.d.ts +19 -69
- package/dist/shims/headers.js +82 -183
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
- package/dist/shims/internal/app-page-props-cache-key.js +24 -15
- package/dist/shims/internal/cache-context-accessor.d.ts +16 -0
- package/dist/shims/internal/cache-context-accessor.js +15 -0
- package/dist/shims/internal/headers-state.d.ts +73 -0
- package/dist/shims/internal/headers-state.js +157 -0
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/navigation-errors.d.ts +6 -0
- package/dist/shims/navigation-errors.js +11 -1
- package/dist/shims/navigation.d.ts +7 -0
- package/dist/shims/navigation.js +16 -6
- package/dist/shims/request-context.d.ts +9 -0
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/root-params.js +1 -1
- package/dist/shims/server.js +52 -2
- package/dist/shims/thenable-params.d.ts +4 -0
- package/dist/shims/thenable-params.js +25 -3
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/encode-cache-tag.d.ts +1 -0
- package/dist/utils/encode-cache-tag.js +11 -1
- package/dist/utils/mdx-scan.d.ts +5 -0
- package/dist/utils/mdx-scan.js +8 -1
- package/dist/utils/regex-safety.d.ts +10 -0
- package/dist/utils/regex-safety.js +70 -18
- package/dist/utils/vite-cli-invocation.js +8 -1
- package/package.json +8 -4
- package/dist/server/metadata-route-cache-policy.d.ts +0 -5
- package/dist/server/metadata-route-cache-policy.js +0 -6
|
@@ -4,23 +4,28 @@ import { decodeMatchedParams } from "../routing/utils.js";
|
|
|
4
4
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
5
5
|
import { VINEXT_METADATA_ROUTE_CACHE_HEADER } from "./headers.js";
|
|
6
6
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
7
|
-
import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy } from "../shims/cacheability-classification.js";
|
|
8
|
-
import {
|
|
7
|
+
import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
|
|
8
|
+
import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy } from "./cache-control.js";
|
|
9
|
+
import { buildAppRouteMissIsrCacheControl } from "./isr-decision.js";
|
|
9
10
|
import { isrCacheControl } from "./isr-cache.js";
|
|
10
11
|
import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
|
|
11
|
-
import {
|
|
12
|
+
import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
|
|
13
|
+
import { getActiveDraftModeState, getHeadersContext, hasDraftModeCookieHeader, replaceHeadersContext } from "../shims/headers.js";
|
|
14
|
+
import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
|
|
12
15
|
import { ensureFetchPatch, getCollectedFetchTags, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
|
|
13
16
|
import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
|
|
14
17
|
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
15
18
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
16
19
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
17
20
|
import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse } from "./app-route-handler-response.js";
|
|
18
|
-
import {
|
|
21
|
+
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
22
|
+
import { completeAppRouteHandlerResponse } from "./app-route-handler-execution.js";
|
|
19
23
|
//#region src/server/metadata-route-response.ts
|
|
20
24
|
function isOuterMetadataCacheEnabled() {
|
|
21
25
|
return process.env.NODE_ENV !== "development";
|
|
22
26
|
}
|
|
23
27
|
const routeFunctionCache = /* @__PURE__ */ new WeakMap();
|
|
28
|
+
const userMetadataResponses = /* @__PURE__ */ new WeakSet();
|
|
24
29
|
const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
|
|
25
30
|
const CACHE_HEADERS = {
|
|
26
31
|
noCache: "no-cache, no-store",
|
|
@@ -53,17 +58,19 @@ function metadataRouteCacheHeader(route) {
|
|
|
53
58
|
if (route.isDynamic && isImageMetadataRoute(route) && process.env.NODE_ENV === "development") return CACHE_HEADERS.noCache;
|
|
54
59
|
return CACHE_HEADERS.revalidate;
|
|
55
60
|
}
|
|
56
|
-
function withMetadataRouteCacheHeader(response, route) {
|
|
57
|
-
if (hasCdnResponsePolicy(response.headers) && !hasExplicitNonCacheableResponsePolicy(response.headers)) markRouteCacheabilityExplicitResponsePolicy();
|
|
58
|
-
const headers = new Headers(response.headers);
|
|
59
|
-
if (!headers.has("Cache-Control")) headers.set("Cache-Control", metadataRouteCacheHeader(route));
|
|
60
|
-
return new Response(response.body, {
|
|
61
|
-
headers,
|
|
62
|
-
status: response.status,
|
|
63
|
-
statusText: response.statusText
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
61
|
function beginMetadataRouteCacheability(route) {
|
|
62
|
+
const dynamic = route.module?.dynamic;
|
|
63
|
+
if (dynamic === "force-static" || dynamic === "error") {
|
|
64
|
+
const current = getHeadersContext();
|
|
65
|
+
const staticContext = createStaticGenerationHeadersContext({
|
|
66
|
+
dynamicConfig: dynamic,
|
|
67
|
+
routeKind: "route",
|
|
68
|
+
routePattern: route.servedUrl,
|
|
69
|
+
draftModeEnabled: getActiveDraftModeState() ?? void 0,
|
|
70
|
+
draftModeSecret: current?.draftModeSecret
|
|
71
|
+
});
|
|
72
|
+
replaceHeadersContext(staticContext);
|
|
73
|
+
}
|
|
67
74
|
beginRouteCacheability("app-route", route.patternParts ? `/${route.patternParts.join("/")}` : route.servedUrl);
|
|
68
75
|
}
|
|
69
76
|
function getMetadataRouteFunctions(route) {
|
|
@@ -137,29 +144,68 @@ function markMetadataRouteCacheValue(value) {
|
|
|
137
144
|
function buildMetadataRouteTags(route, cleanPathname, collectedTags) {
|
|
138
145
|
return buildPageCacheTags(cleanPathname, collectedTags, route.routeSegments ?? [], "route");
|
|
139
146
|
}
|
|
140
|
-
function captureRenderedMetadataRoute(response) {
|
|
141
|
-
const
|
|
147
|
+
async function captureRenderedMetadataRoute(response, route, cleanPathname) {
|
|
148
|
+
const isUserResponse = userMetadataResponses.has(response);
|
|
149
|
+
let completed = false;
|
|
150
|
+
if (isOuterMetadataCacheEnabled()) {
|
|
151
|
+
const result = await completeAppRouteHandlerResponse(response);
|
|
152
|
+
response = result.response;
|
|
153
|
+
completed = result.completed;
|
|
154
|
+
if (completed) markRouteCacheabilityResponseBodyComplete();
|
|
155
|
+
}
|
|
156
|
+
const browserCacheControl = response.headers.get("Cache-Control") ?? void 0;
|
|
157
|
+
const hasUserPolicy = isUserResponse && hasCdnResponsePolicy(response.headers);
|
|
158
|
+
if (hasUserPolicy) markRouteCacheabilityExplicitResponsePolicy();
|
|
159
|
+
const observedCacheLife = _consumeRequestScopedCacheLife();
|
|
142
160
|
const collectedTags = getCollectedFetchTags();
|
|
161
|
+
const revalidate = typeof route.module?.revalidate === "number" ? route.module.revalidate : Infinity;
|
|
162
|
+
const frameworkRevalidate = Math.min(observedCacheLife?.revalidate ?? Infinity, revalidate);
|
|
163
|
+
const cacheLife = {
|
|
164
|
+
...observedCacheLife,
|
|
165
|
+
revalidate: frameworkRevalidate
|
|
166
|
+
};
|
|
167
|
+
const cacheable = (response.status < 400 || response.status === 404) && completed && !isRenderDynamicLatched() && !getActiveDraftModeState() && !hasDraftModeCookieHeader() && route.module?.dynamic !== "force-dynamic" && frameworkRevalidate > 0 && !response.headers.has("set-cookie");
|
|
168
|
+
const cacheControl = cacheable ? buildAppRouteMissIsrCacheControl(frameworkRevalidate, cacheLife?.expire) : NEVER_CACHE_CONTROL;
|
|
169
|
+
if (isUserResponse && !hasUserPolicy) {
|
|
170
|
+
response = new Response(response.body, {
|
|
171
|
+
headers: new Headers(response.headers),
|
|
172
|
+
status: response.status,
|
|
173
|
+
statusText: response.statusText
|
|
174
|
+
});
|
|
175
|
+
applyCdnResponseHeaders(response.headers, { cacheControl: isOuterMetadataCacheEnabled() ? cacheControl : metadataRouteCacheHeader(route) });
|
|
176
|
+
}
|
|
177
|
+
recordRouteCacheability(cacheable ? {
|
|
178
|
+
cacheable: true,
|
|
179
|
+
cacheControl,
|
|
180
|
+
tags: buildMetadataRouteTags(route, cleanPathname, collectedTags)
|
|
181
|
+
} : {
|
|
182
|
+
cacheable: false,
|
|
183
|
+
dynamicUsage: true
|
|
184
|
+
});
|
|
143
185
|
if (process.env.VINEXT_PRERENDER === "1") {
|
|
144
|
-
applyPrerenderCacheLifeHeader(response.headers,
|
|
186
|
+
applyPrerenderCacheLifeHeader(response.headers, {
|
|
187
|
+
...cacheLife,
|
|
188
|
+
revalidate: cacheable ? frameworkRevalidate : 0
|
|
189
|
+
});
|
|
145
190
|
applyPrerenderCacheTagsHeader(response.headers, collectedTags);
|
|
146
191
|
}
|
|
147
192
|
return {
|
|
193
|
+
browserCacheControl,
|
|
148
194
|
cacheLife,
|
|
195
|
+
cacheable,
|
|
149
196
|
collectedTags,
|
|
150
197
|
response
|
|
151
198
|
};
|
|
152
199
|
}
|
|
153
200
|
async function writeRenderedMetadataRoute(options, route, routeKey, rendered, previousEntry) {
|
|
154
|
-
if (!options.isrSet || !rendered.
|
|
201
|
+
if (!options.isrSet || !rendered.cacheable) return;
|
|
155
202
|
const previousCacheControl = previousEntry?.value.cacheControl;
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const expire = rendered.cacheLife?.expire ?? previousCacheControl?.expire ?? defaultCacheLife.expire;
|
|
203
|
+
const revalidate = rendered.cacheLife?.revalidate ?? previousCacheControl?.revalidate ?? (typeof route.module?.revalidate === "number" ? route.module.revalidate : false);
|
|
204
|
+
const expire = rendered.cacheLife?.expire ?? previousCacheControl?.expire;
|
|
159
205
|
const stale = resolveClientStaleTimeSeconds(rendered.cacheLife) ?? previousCacheControl?.stale;
|
|
160
|
-
const value = markMetadataRouteCacheValue(await buildAppRouteCacheValue(rendered.response));
|
|
206
|
+
const value = markMetadataRouteCacheValue(await buildAppRouteCacheValue(rendered.response, rendered.browserCacheControl));
|
|
161
207
|
await options.isrSet(routeKey, value, {
|
|
162
|
-
cacheControl: isrCacheControl(revalidate, {
|
|
208
|
+
cacheControl: isrCacheControl(revalidate === Infinity ? false : revalidate, {
|
|
163
209
|
...typeof expire === "number" ? { expireSeconds: expire } : {},
|
|
164
210
|
...typeof stale === "number" ? { staleSeconds: stale } : {}
|
|
165
211
|
}),
|
|
@@ -171,16 +217,21 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
|
|
|
171
217
|
try {
|
|
172
218
|
await runWithRequestContext(requestContext, async () => {
|
|
173
219
|
ensureFetchPatch();
|
|
220
|
+
beginMetadataRouteCacheability(route);
|
|
174
221
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
175
222
|
const rendered = await render();
|
|
176
|
-
if (rendered)
|
|
223
|
+
if (rendered) try {
|
|
224
|
+
await writeRenderedMetadataRoute(options, route, routeKey, rendered, previousEntry);
|
|
225
|
+
} finally {
|
|
226
|
+
rendered.response.body?.cancel().catch(() => {});
|
|
227
|
+
}
|
|
177
228
|
});
|
|
178
229
|
} finally {
|
|
179
230
|
await closeAfterResponse(requestContext);
|
|
180
231
|
}
|
|
181
232
|
}
|
|
182
|
-
async function readMatchedPrerenderedMetadataRouteResponse(options, route,
|
|
183
|
-
if (!isOuterMetadataCacheEnabled() || !options.isrGet || !options.isrRouteKey) return {
|
|
233
|
+
async function readMatchedPrerenderedMetadataRouteResponse(options, route, render) {
|
|
234
|
+
if (!isOuterMetadataCacheEnabled() || !options.isrGet || !options.isrRouteKey || getActiveDraftModeState() || hasDraftModeCookieHeader() || route.module?.dynamic === "force-dynamic" || route.module?.revalidate === 0) return {
|
|
184
235
|
cachedEntry: null,
|
|
185
236
|
response: null
|
|
186
237
|
};
|
|
@@ -214,7 +265,7 @@ async function readMatchedPrerenderedMetadataRouteResponse(options, route, funct
|
|
|
214
265
|
revalidateSeconds
|
|
215
266
|
})
|
|
216
267
|
};
|
|
217
|
-
if (
|
|
268
|
+
if (options.isrSet && options.scheduleBackgroundRegeneration) {
|
|
218
269
|
const executionContext = getRequestExecutionContext();
|
|
219
270
|
options.scheduleBackgroundRegeneration(routeKey, () => runMetadataRouteRegeneration(options, route, render, routeKey, cached, executionContext), {
|
|
220
271
|
routerKind: "App Router",
|
|
@@ -330,7 +381,10 @@ async function handleGeneratedSitemap(route, cleanPathname, functions) {
|
|
|
330
381
|
const matchedId = findGeneratedSitemapId(await functions.generateSitemaps({}), rawId);
|
|
331
382
|
if (!matchedId) return notFoundResponse();
|
|
332
383
|
const result = await functions.defaultExport({ id: makeThenableMetadataRouteId(matchedId) });
|
|
333
|
-
if (result instanceof Response)
|
|
384
|
+
if (result instanceof Response) {
|
|
385
|
+
userMetadataResponses.add(result);
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
334
388
|
if (!isSitemapEntries(result)) throw new TypeError("Metadata sitemap routes must return an array.");
|
|
335
389
|
return markFullyBufferedBody(new Response(sitemapToXml(result), { headers: {
|
|
336
390
|
"Content-Type": route.contentType,
|
|
@@ -367,7 +421,10 @@ async function callDynamicMetadataRoute(route, match, makeThenableParams, functi
|
|
|
367
421
|
id: makeThenableMetadataRouteId(matchedImageId)
|
|
368
422
|
});
|
|
369
423
|
} else result = await functions.defaultExport({ params: paramsThenable });
|
|
370
|
-
if (result instanceof Response)
|
|
424
|
+
if (result instanceof Response) {
|
|
425
|
+
userMetadataResponses.add(result);
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
371
428
|
let body;
|
|
372
429
|
if (route.type === "sitemap") {
|
|
373
430
|
if (!isSitemapEntries(result)) throw new TypeError("Metadata sitemap routes must return an array.");
|
|
@@ -410,8 +467,8 @@ function isMetadataRouteRequestPath(metadataRoutes, cleanPathname) {
|
|
|
410
467
|
}
|
|
411
468
|
return false;
|
|
412
469
|
}
|
|
413
|
-
async function writeMetadataRouteMiss(options, route,
|
|
414
|
-
if (process.env.VINEXT_PRERENDER === "1" || !isOuterMetadataCacheEnabled() || !rendered.
|
|
470
|
+
async function writeMetadataRouteMiss(options, route, rendered, cachedEntry) {
|
|
471
|
+
if (process.env.VINEXT_PRERENDER === "1" || !isOuterMetadataCacheEnabled() || !rendered.cacheable || !options.isrRouteKey || !options.isrSet) return;
|
|
415
472
|
try {
|
|
416
473
|
await writeRenderedMetadataRoute(options, route, options.isrRouteKey(options.cleanPathname), {
|
|
417
474
|
...rendered,
|
|
@@ -433,13 +490,13 @@ async function handleMetadataRouteRequest(options) {
|
|
|
433
490
|
const render = async () => {
|
|
434
491
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
435
492
|
const response = await handleGeneratedSitemap(route, options.cleanPathname, functions);
|
|
436
|
-
return response ? captureRenderedMetadataRoute(response) : null;
|
|
493
|
+
return response ? captureRenderedMetadataRoute(response, route, options.cleanPathname) : null;
|
|
437
494
|
};
|
|
438
|
-
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route,
|
|
495
|
+
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
|
|
439
496
|
if (cached.response) return cached.response;
|
|
440
497
|
const rendered = await render();
|
|
441
498
|
if (rendered) {
|
|
442
|
-
await writeMetadataRouteMiss(options, route,
|
|
499
|
+
await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
|
|
443
500
|
return rendered.response;
|
|
444
501
|
}
|
|
445
502
|
}
|
|
@@ -471,12 +528,12 @@ async function handleMetadataRouteRequest(options) {
|
|
|
471
528
|
}
|
|
472
529
|
const render = async () => {
|
|
473
530
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
474
|
-
return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route));
|
|
531
|
+
return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
|
|
475
532
|
};
|
|
476
|
-
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route,
|
|
533
|
+
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
|
|
477
534
|
if (cached.response) return cached.response;
|
|
478
535
|
const rendered = await render();
|
|
479
|
-
await writeMetadataRouteMiss(options, route,
|
|
536
|
+
await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
|
|
480
537
|
return rendered.response;
|
|
481
538
|
}
|
|
482
539
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { analyzeRegexSafety, regexAtomsMayOverlap } from "../utils/regex-safety.js";
|
|
1
|
+
import { analyzeRegexSafety, mayHaveNamedGroups, regexAtomsMayOverlap } from "../utils/regex-safety.js";
|
|
2
2
|
import { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath } from "./middleware-path-to-regexp.js";
|
|
3
3
|
//#region src/server/middleware-matcher-pattern.ts
|
|
4
4
|
const MATCHER_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -138,8 +138,11 @@ function hasOverlappingSequentialRepetition(pattern) {
|
|
|
138
138
|
}
|
|
139
139
|
return false;
|
|
140
140
|
}
|
|
141
|
-
function unsafeTokenReason(token) {
|
|
142
|
-
const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
|
|
141
|
+
function unsafeTokenReason(token, namedGroups) {
|
|
142
|
+
const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
|
|
143
|
+
ignoreCase: true,
|
|
144
|
+
namedGroups
|
|
145
|
+
});
|
|
143
146
|
if (regexSafetyIssue) {
|
|
144
147
|
if (regexSafetyIssue === "analysis budget exceeded") return `parameter "${token.name}" exceeds the regex analysis budget`;
|
|
145
148
|
return `parameter "${token.name}" contains ${regexSafetyIssue}`;
|
|
@@ -150,9 +153,10 @@ function unsafeTokenReason(token) {
|
|
|
150
153
|
return null;
|
|
151
154
|
}
|
|
152
155
|
function validateTokens(tokens) {
|
|
156
|
+
const namedGroups = tokens.some((token) => typeof token !== "string" && mayHaveNamedGroups(token.pattern));
|
|
153
157
|
for (const token of tokens) {
|
|
154
158
|
if (typeof token === "string") continue;
|
|
155
|
-
const reason = unsafeTokenReason(token);
|
|
159
|
+
const reason = unsafeTokenReason(token, namedGroups);
|
|
156
160
|
if (reason) return reason;
|
|
157
161
|
}
|
|
158
162
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./headers.js";
|
|
1
2
|
//#region src/server/middleware-response-headers.ts
|
|
2
3
|
const ADDITIVE_RESPONSE_HEADER_NAMES = /* @__PURE__ */ new Set(["set-cookie", "vary"]);
|
|
3
4
|
function mergeVaryHeader(target, value) {
|
|
@@ -28,6 +29,7 @@ function mergeMiddlewareResponseHeaders(target, middlewareHeaders) {
|
|
|
28
29
|
if (!middlewareHeaders) return;
|
|
29
30
|
for (const [key, value] of middlewareHeaders) {
|
|
30
31
|
const lowerName = key.toLowerCase();
|
|
32
|
+
if (lowerName === "x-vinext-special-error-status") continue;
|
|
31
33
|
if (lowerName === "link" && !value) continue;
|
|
32
34
|
if (lowerName === "vary") {
|
|
33
35
|
mergeVaryHeader(target, value);
|
|
@@ -21,6 +21,14 @@ export type VinextResponseStageDispatchOptions = {
|
|
|
21
21
|
* opt into completed-response admission and honor core's `no-store` policy.
|
|
22
22
|
*/
|
|
23
23
|
cache: "shared" | "bypass";
|
|
24
|
+
/**
|
|
25
|
+
* Render an App page document whole before its first byte, as Next.js
|
|
26
|
+
* renders a static page, so its streamed metadata is in `<head>`. An adapter
|
|
27
|
+
* sets this for a render no client is waiting on, such as a regeneration or
|
|
28
|
+
* a deployment warm-up. It is not a cache selector: a transport must not
|
|
29
|
+
* partition its lookup or stored entry by it.
|
|
30
|
+
*/
|
|
31
|
+
renderWholeDocument?: boolean;
|
|
24
32
|
/**
|
|
25
33
|
* Query-free identity of a shared App page GET/HEAD dispatch, supplied only
|
|
26
34
|
* to adapters that declare `responseStageCacheIdentity: "query-free"` and
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
2
|
import { stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
3
4
|
import { AppElementsWire, compareAppElementsSlotIds } from "./app-elements-wire.js";
|
|
4
5
|
import "./app-elements.js";
|
|
5
|
-
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
6
6
|
import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
7
7
|
import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
8
8
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
//#region src/server/node-response-signal.d.ts
|
|
3
|
+
export declare class ResponseAborted extends Error {
|
|
4
|
+
readonly name = "ResponseAborted";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates an AbortSignal tied to the closing of a ServerResponse.
|
|
8
|
+
*
|
|
9
|
+
* The incoming request cannot be used: once its body has been fully read the
|
|
10
|
+
* readable side is done, so a later client disconnect fires nothing on it
|
|
11
|
+
* (and its `close` event also fires on normal requests). If `finish` fires
|
|
12
|
+
* first, `res.end()` completed and the following `close` is our own teardown;
|
|
13
|
+
* if `close` fires first, the client disconnected before we finished.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signalFromNodeResponse(response: Writable): AbortSignal;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/server/node-response-signal.ts
|
|
2
|
+
const ResponseAbortedName = "ResponseAborted";
|
|
3
|
+
var ResponseAborted = class extends Error {
|
|
4
|
+
name = ResponseAbortedName;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Creates an AbortSignal tied to the closing of a ServerResponse.
|
|
8
|
+
*
|
|
9
|
+
* The incoming request cannot be used: once its body has been fully read the
|
|
10
|
+
* readable side is done, so a later client disconnect fires nothing on it
|
|
11
|
+
* (and its `close` event also fires on normal requests). If `finish` fires
|
|
12
|
+
* first, `res.end()` completed and the following `close` is our own teardown;
|
|
13
|
+
* if `close` fires first, the client disconnected before we finished.
|
|
14
|
+
*/
|
|
15
|
+
function signalFromNodeResponse(response) {
|
|
16
|
+
const { errored, destroyed } = response;
|
|
17
|
+
if (errored || destroyed) return AbortSignal.abort(errored ?? new ResponseAborted());
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
response.once("close", () => {
|
|
20
|
+
if (response.writableFinished) return;
|
|
21
|
+
controller.abort(new ResponseAborted());
|
|
22
|
+
});
|
|
23
|
+
return controller.signal;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ResponseAborted, signalFromNodeResponse };
|
|
@@ -8,4 +8,51 @@
|
|
|
8
8
|
* to resolve a `Location` containing the pathname as protocol-relative.
|
|
9
9
|
*/
|
|
10
10
|
export declare function isOpenRedirectShaped(rawPathname: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* What a request entry answers for a raw path containing a backslash or a
|
|
13
|
+
* repeated slash: Next.js's 308, or a 404 when the collapsed path would still
|
|
14
|
+
* be open-redirect shaped.
|
|
15
|
+
*/
|
|
16
|
+
export type RepeatedSlashRedirect = {
|
|
17
|
+
status: 308;
|
|
18
|
+
location: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: 404;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Returns Next.js's redirect for a raw request path containing a backslash or
|
|
24
|
+
* a repeated slash, or `null` when the path needs no change.
|
|
25
|
+
*
|
|
26
|
+
* Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
|
|
27
|
+
* via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
|
|
28
|
+
* path become `/`, runs of slashes collapse to one and the query is kept.
|
|
29
|
+
* `next start` then builds the Location with `url.format(parseUrl(...))` in
|
|
30
|
+
* `router-server.ts`, so the result is serialized through a WHATWG URL here
|
|
31
|
+
* too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
|
|
32
|
+
* before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
|
|
33
|
+
* `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
|
|
34
|
+
* keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
|
|
35
|
+
*
|
|
36
|
+
* The Location is always same-origin. Absolute-form request targets
|
|
37
|
+
* (`GET http://host//x`) are left alone. A collapsed path that still starts
|
|
38
|
+
* with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
|
|
39
|
+
* never echoed into a Location or routed: it gets a 404 instead.
|
|
40
|
+
*
|
|
41
|
+
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
42
|
+
*/
|
|
43
|
+
export declare function getRepeatedSlashRedirect(rawUrl: string): RepeatedSlashRedirect | null;
|
|
44
|
+
/**
|
|
45
|
+
* Build the Response for a raw request path containing a backslash or a
|
|
46
|
+
* repeated slash. See `getRepeatedSlashRedirect`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function repeatedSlashRedirectResponse(rawUrl: string): Response | null;
|
|
49
|
+
/**
|
|
50
|
+
* Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
|
|
51
|
+
* entries that see the raw request target (dev and `vinext start`). Returns
|
|
52
|
+
* true when a response was sent.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sendRepeatedSlashRedirect(rawUrl: string, res: {
|
|
55
|
+
writeHead(status: number, headers?: Record<string, string>): unknown;
|
|
56
|
+
end(body: string): unknown;
|
|
57
|
+
}): boolean;
|
|
11
58
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notFoundResponse } from "./http-error-responses.js";
|
|
1
2
|
//#region src/server/open-redirect.ts
|
|
2
3
|
/**
|
|
3
4
|
* Returns true if a request pathname looks like a protocol-relative open
|
|
@@ -17,5 +18,77 @@ function isOpenRedirectShaped(rawPathname) {
|
|
|
17
18
|
}
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
21
|
+
const REPEATED_SLASH_OR_BACKSLASH_RE = /\\|\/\//;
|
|
22
|
+
const REDIRECT_LOCATION_BASE = "http://vinext.invalid";
|
|
23
|
+
/**
|
|
24
|
+
* Returns Next.js's redirect for a raw request path containing a backslash or
|
|
25
|
+
* a repeated slash, or `null` when the path needs no change.
|
|
26
|
+
*
|
|
27
|
+
* Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
|
|
28
|
+
* via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
|
|
29
|
+
* path become `/`, runs of slashes collapse to one and the query is kept.
|
|
30
|
+
* `next start` then builds the Location with `url.format(parseUrl(...))` in
|
|
31
|
+
* `router-server.ts`, so the result is serialized through a WHATWG URL here
|
|
32
|
+
* too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
|
|
33
|
+
* before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
|
|
34
|
+
* `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
|
|
35
|
+
* keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
|
|
36
|
+
*
|
|
37
|
+
* The Location is always same-origin. Absolute-form request targets
|
|
38
|
+
* (`GET http://host//x`) are left alone. A collapsed path that still starts
|
|
39
|
+
* with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
|
|
40
|
+
* never echoed into a Location or routed: it gets a 404 instead.
|
|
41
|
+
*
|
|
42
|
+
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
43
|
+
*/
|
|
44
|
+
function getRepeatedSlashRedirect(rawUrl) {
|
|
45
|
+
if (!rawUrl.startsWith("/")) return null;
|
|
46
|
+
const urlParts = rawUrl.split("?");
|
|
47
|
+
const pathname = urlParts[0];
|
|
48
|
+
if (!REPEATED_SLASH_OR_BACKSLASH_RE.test(pathname)) return null;
|
|
49
|
+
const cleanUrl = pathname.replaceAll("\\", "/").replace(/\/\/+/g, "/") + (urlParts[1] ? `?${urlParts.slice(1).join("?")}` : "");
|
|
50
|
+
const parsed = new URL(REDIRECT_LOCATION_BASE + cleanUrl);
|
|
51
|
+
const location = parsed.pathname + parsed.search + parsed.hash;
|
|
52
|
+
return isOpenRedirectShaped(location) ? { status: 404 } : {
|
|
53
|
+
status: 308,
|
|
54
|
+
location
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function repeatedSlashRedirectHeaders(location) {
|
|
58
|
+
return {
|
|
59
|
+
Location: location,
|
|
60
|
+
Refresh: `0;url=${location}`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the Response for a raw request path containing a backslash or a
|
|
65
|
+
* repeated slash. See `getRepeatedSlashRedirect`.
|
|
66
|
+
*/
|
|
67
|
+
function repeatedSlashRedirectResponse(rawUrl) {
|
|
68
|
+
const redirect = getRepeatedSlashRedirect(rawUrl);
|
|
69
|
+
if (!redirect) return null;
|
|
70
|
+
if (redirect.status === 404) return notFoundResponse();
|
|
71
|
+
return new Response(redirect.location, {
|
|
72
|
+
status: 308,
|
|
73
|
+
headers: repeatedSlashRedirectHeaders(redirect.location)
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
|
|
78
|
+
* entries that see the raw request target (dev and `vinext start`). Returns
|
|
79
|
+
* true when a response was sent.
|
|
80
|
+
*/
|
|
81
|
+
function sendRepeatedSlashRedirect(rawUrl, res) {
|
|
82
|
+
const redirect = getRepeatedSlashRedirect(rawUrl);
|
|
83
|
+
if (!redirect) return false;
|
|
84
|
+
if (redirect.status === 404) {
|
|
85
|
+
res.writeHead(404);
|
|
86
|
+
res.end("This page could not be found");
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
res.writeHead(308, repeatedSlashRedirectHeaders(redirect.location));
|
|
90
|
+
res.end(redirect.location);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
20
93
|
//#endregion
|
|
21
|
-
export { isOpenRedirectShaped };
|
|
94
|
+
export { getRepeatedSlashRedirect, isOpenRedirectShaped, repeatedSlashRedirectResponse, sendRepeatedSlashRedirect };
|
|
@@ -3,6 +3,8 @@ import "./server-globals.js";
|
|
|
3
3
|
import { NextRequest } from "../shims/server.js";
|
|
4
4
|
import { internalServerErrorResponse } from "./http-error-responses.js";
|
|
5
5
|
import { cloneRequestWithUrl } from "./request-pipeline.js";
|
|
6
|
+
import { markRouteCacheabilityExplicitResponsePolicy } from "../shims/cacheability-classification.js";
|
|
7
|
+
import { hasCdnResponsePolicy } from "./cache-control.js";
|
|
6
8
|
import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
|
|
7
9
|
import { tracePagesApiHandler } from "./pages-execution-tracing.js";
|
|
8
10
|
import { resolveBodyParserConfig } from "./pages-body-parser-config.js";
|
|
@@ -29,8 +31,12 @@ function isNodeApiRouteModule(module) {
|
|
|
29
31
|
return typeof module.default === "function" && !isEdgeApiRuntime(resolveModuleRuntime(module));
|
|
30
32
|
}
|
|
31
33
|
async function handlePagesApiRoute(options) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const run = async () => {
|
|
35
|
+
const response = await _handlePagesApiRoute(options);
|
|
36
|
+
if (hasCdnResponsePolicy(response.headers)) markRouteCacheabilityExplicitResponsePolicy();
|
|
37
|
+
return response;
|
|
38
|
+
};
|
|
39
|
+
return options.ctx ? runWithExecutionContext(options.ctx, run) : run();
|
|
34
40
|
}
|
|
35
41
|
async function _handlePagesApiRoute(options) {
|
|
36
42
|
if (!options.match) return new Response("404 - API route not found", { status: 404 });
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* unit-testable and lives in a normal typed module rather than a codegen
|
|
10
10
|
* template string.
|
|
11
11
|
*/
|
|
12
|
+
export declare function getSharedChunkFiles(manifest: Record<string, string[]>): string[];
|
|
12
13
|
/**
|
|
13
14
|
* Resolve the effective SSR manifest: prefer the caller-supplied object and
|
|
14
15
|
* fall back to the registered client build metadata.
|