vinext 1.4.0 → 1.4.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/README.md +20 -20
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +3 -6
- package/dist/client/dev-error-overlay-store.d.ts +1 -0
- package/dist/client/dev-error-overlay.d.ts +2 -0
- package/dist/client/dev-error-overlay.js +19 -3
- package/dist/config/config-matchers.js +2 -0
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +52 -2
- package/dist/config/next-config.d.ts +6 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.js +0 -1
- package/dist/entries/pages-server-entry.js +3 -1
- package/dist/index.js +29 -20
- package/dist/plugins/strip-server-exports.js +2 -1
- package/dist/server/api-handler.js +2 -1
- package/dist/server/app-browser-entry.js +7 -3
- package/dist/server/app-browser-rsc-redirect.js +3 -3
- package/dist/server/app-browser-server-action-client.js +2 -2
- package/dist/server/app-browser-visible-commit.js +2 -2
- package/dist/server/app-optimistic-routing.js +2 -3
- package/dist/server/app-page-boundary-render.js +5 -10
- package/dist/server/app-page-dispatch.d.ts +0 -1
- package/dist/server/app-page-dispatch.js +0 -1
- package/dist/server/app-page-element-builder.js +3 -3
- package/dist/server/app-page-execution.js +9 -6
- package/dist/server/app-page-render.d.ts +0 -1
- package/dist/server/app-page-render.js +14 -14
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +22 -5
- package/dist/server/app-page-stream.d.ts +10 -8
- package/dist/server/app-page-stream.js +2 -1
- package/dist/server/app-request-stage-independent-entry.js +1 -1
- package/dist/server/app-route-handler-execution.js +3 -2
- package/dist/server/app-route-handler-runtime.d.ts +1 -1
- package/dist/server/app-route-handler-runtime.js +11 -7
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +10 -10
- package/dist/server/app-rsc-cache-busting.js +27 -68
- package/dist/server/app-rsc-error-handler.js +1 -1
- package/dist/server/app-rsc-errors.js +4 -1
- package/dist/server/app-rsc-handler.js +53 -62
- package/dist/server/app-rsc-request-normalization.d.ts +10 -12
- package/dist/server/app-rsc-request-normalization.js +11 -14
- package/dist/server/app-rsc-response-stage.js +4 -12
- package/dist/server/app-server-action-execution.js +8 -4
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +13 -8
- package/dist/server/app-worker-stages.d.ts +12 -3
- package/dist/server/app-worker-stages.js +37 -4
- package/dist/server/bfcache-identity.d.ts +7 -0
- package/dist/server/bfcache-identity.js +10 -1
- package/dist/server/cacheability-manifest.js +1 -2
- package/dist/server/dev-origin-check.d.ts +2 -1
- package/dist/server/dev-origin-check.js +2 -1
- package/dist/server/dev-server.js +2 -1
- package/dist/server/image-optimization.d.ts +5 -0
- package/dist/server/image-optimization.js +6 -1
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +1 -1
- package/dist/server/middleware-matcher.js +19 -6
- package/dist/server/middleware-runtime.js +6 -2
- package/dist/server/multi-stage.d.ts +7 -3
- package/dist/server/pages-node-compat.d.ts +1 -1
- package/dist/server/pages-node-compat.js +2 -0
- package/dist/server/pages-page-data.js +2 -1
- package/dist/server/pages-page-handler.js +17 -6
- package/dist/server/pages-page-response.d.ts +9 -0
- package/dist/server/pages-page-response.js +18 -4
- package/dist/server/pages-request-stage-entry.js +24 -4
- package/dist/server/pages-router-entry.js +3 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +42 -8
- package/dist/server/request-error-log.d.ts +9 -0
- package/dist/server/request-error-log.js +13 -0
- package/dist/server/request-pipeline.d.ts +9 -4
- package/dist/server/request-pipeline.js +23 -15
- package/dist/server/rsc-completion-metadata.js +3 -0
- package/dist/server/static-file-cache.d.ts +9 -0
- package/dist/server/static-file-cache.js +13 -2
- package/dist/shims/cache-request-state.d.ts +6 -6
- package/dist/shims/cache-request-state.js +6 -36
- package/dist/shims/cache-runtime.d.ts +3 -1
- package/dist/shims/cache-runtime.js +77 -47
- package/dist/shims/cache.d.ts +10 -5
- package/dist/shims/cache.js +35 -22
- package/dist/shims/client-hook-error.d.ts +2 -0
- package/dist/shims/client-hook-error.js +16 -1
- package/dist/shims/fetch-cache.js +6 -2
- package/dist/shims/headers.d.ts +2 -1
- package/dist/shims/headers.js +5 -3
- package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
- package/dist/shims/link.js +5 -10
- package/dist/shims/navigation.js +2 -3
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/slot.d.ts +6 -2
- package/dist/shims/slot.js +10 -3
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/cache-life-profiles.d.ts +33 -0
- package/dist/utils/cache-life-profiles.js +111 -0
- package/dist/utils/middleware-request-headers.d.ts +14 -3
- package/dist/utils/middleware-request-headers.js +54 -7
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
|
8
8
|
import { extractLocaleFromUrl, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
9
9
|
import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
|
|
10
10
|
import { reportRequestError } from "./instrumentation.js";
|
|
11
|
+
import { logRequestError } from "./request-error-log.js";
|
|
11
12
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
12
13
|
import { beginRouteCacheability, isRouteCacheabilityIdentityProbe, isRouteCacheabilityProbe, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityFinalResponseUncacheable, recordRouteCacheability } from "../shims/cacheability-classification.js";
|
|
13
14
|
import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, shouldUseNextDeployCacheControl } from "./cache-control.js";
|
|
@@ -22,7 +23,7 @@ import { buildPagesReadinessNextData } from "./pages-readiness.js";
|
|
|
22
23
|
import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
|
|
23
24
|
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
24
25
|
import { pagesPathCacheTag } from "./pages-cache-tag.js";
|
|
25
|
-
import { renderPagesPageResponse } from "./pages-page-response.js";
|
|
26
|
+
import { applyPagesPrerenderRevalidateHeader, renderPagesPageResponse } from "./pages-page-response.js";
|
|
26
27
|
import { hasPagesGetInitialProps } from "./pages-get-initial-props.js";
|
|
27
28
|
import { traceFindPageComponents, tracePagesDocumentStream } from "./pages-execution-tracing.js";
|
|
28
29
|
import { mergePagesNotFoundSourceHeaders, resolvePagesPageData, resolvePagesStatusPageRenderPath, setPagesResponseHeader } from "./pages-page-data.js";
|
|
@@ -244,10 +245,12 @@ function createPagesPageHandler(opts) {
|
|
|
244
245
|
hasRewrites
|
|
245
246
|
});
|
|
246
247
|
const isCacheabilityProbe = isRouteCacheabilityProbe();
|
|
247
|
-
|
|
248
|
+
const isTopLevelPageRoute = !isRouteMissErrorRender && options?.__forcedRoute === void 0;
|
|
249
|
+
const isInitialPropsRender = isTopLevelPageRoute && !isStaticPropsRoute && pagesReadiness.gssp !== true && (pagesReadiness.gip === true || pagesReadiness.appGip === true);
|
|
250
|
+
if (isTopLevelPageRoute) {
|
|
248
251
|
beginRouteCacheability("pages-page", route.pattern);
|
|
249
252
|
if (isRouteCacheabilityIdentityProbe()) return new Response(null, { status: 204 });
|
|
250
|
-
if (pagesReadiness.gssp !== true &&
|
|
253
|
+
if (pagesReadiness.gssp !== true && !isInitialPropsRender && !isStaticPropsRoute) recordRouteCacheability({
|
|
251
254
|
cacheable: true,
|
|
252
255
|
cacheControl: STATIC_CACHE_CONTROL
|
|
253
256
|
});
|
|
@@ -282,7 +285,7 @@ function createPagesPageHandler(opts) {
|
|
|
282
285
|
resolveSentResponse = resolve;
|
|
283
286
|
});
|
|
284
287
|
const reportSsrError = async (e) => {
|
|
285
|
-
|
|
288
|
+
logRequestError("[vinext] SSR error:", e);
|
|
286
289
|
await reportRequestError(e, {
|
|
287
290
|
path: url,
|
|
288
291
|
method: request.method,
|
|
@@ -435,6 +438,7 @@ function createPagesPageHandler(opts) {
|
|
|
435
438
|
clientTraceMetadata: shouldEmitPagesClientTraceMetadata(pageModule, AppComponent) ? vinextConfig.clientTraceMetadata : void 0,
|
|
436
439
|
documentReqRes: render.documentReqRes,
|
|
437
440
|
gsspRes: render.gsspRes,
|
|
441
|
+
isInitialPropsRender,
|
|
438
442
|
isrCacheKey: pageIsrCacheKey,
|
|
439
443
|
isrCachePathname,
|
|
440
444
|
expireSeconds: render.expireSeconds,
|
|
@@ -585,6 +589,7 @@ function createPagesPageHandler(opts) {
|
|
|
585
589
|
notFoundResponse = stripPagesNotFoundFramingHeaders(notFoundResponse);
|
|
586
590
|
if (isOnDemandRevalidate) notFoundResponse = withPagesCacheState(notFoundResponse, "REVALIDATED", isrCachePathname);
|
|
587
591
|
else if (pageDataResult.cacheState) notFoundResponse = withPagesCacheState(notFoundResponse, pageDataResult.cacheState);
|
|
592
|
+
if (pageDataResult.revalidateSeconds !== void 0) applyPagesPrerenderRevalidateHeader(notFoundResponse.headers, pageDataResult.revalidateSeconds);
|
|
588
593
|
return finalizePagesPreviewResponse(withBrowserPolicy(notFoundResponse), preview);
|
|
589
594
|
}
|
|
590
595
|
if (pageDataResult.kind === "response") {
|
|
@@ -614,6 +619,7 @@ function createPagesPageHandler(opts) {
|
|
|
614
619
|
});
|
|
615
620
|
if (isDataReq) {
|
|
616
621
|
const headers = new Headers();
|
|
622
|
+
let resHasCacheControl = false;
|
|
617
623
|
if (gsspRes && typeof gsspRes.getHeaders === "function") {
|
|
618
624
|
const gsspHeaders = gsspRes.getHeaders();
|
|
619
625
|
for (const k of Object.keys(gsspHeaders)) {
|
|
@@ -622,10 +628,15 @@ function createPagesPageHandler(opts) {
|
|
|
622
628
|
if (k.toLowerCase() === "set-cookie" && Array.isArray(v)) for (const cookie of v) headers.append(k, String(cookie));
|
|
623
629
|
else headers.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
624
630
|
}
|
|
631
|
+
resHasCacheControl = headers.has("Cache-Control");
|
|
632
|
+
} else if (isInitialPropsRender && documentReqRes) {
|
|
633
|
+
const resHeaders = documentReqRes.res.getHeaders();
|
|
634
|
+
for (const [name, value] of Object.entries(resHeaders)) if (isCdnResponsePolicyHeader(name)) setPagesResponseHeader(headers, name, value);
|
|
635
|
+
resHasCacheControl = Boolean(resHeaders["cache-control"]);
|
|
625
636
|
}
|
|
626
637
|
if (hasCdnResponsePolicy(headers)) markRouteCacheabilityExplicitResponsePolicy();
|
|
627
|
-
if (gsspRes) {
|
|
628
|
-
if (!
|
|
638
|
+
if (gsspRes || isInitialPropsRender) {
|
|
639
|
+
if (!resHasCacheControl) headers.set("Cache-Control", NEVER_CACHE_CONTROL);
|
|
629
640
|
} else if (isStaticPropsRoute) {
|
|
630
641
|
if (isrRevalidateSeconds !== null) applyCdnResponseHeaders(headers, {
|
|
631
642
|
cacheControl: buildMissIsrCacheControl(isrRevalidateSeconds, vinextConfig.expireTime),
|
|
@@ -17,6 +17,13 @@ export declare function generatePagesETag(payload: string): string;
|
|
|
17
17
|
* A page whose status carries no body has nothing to store.
|
|
18
18
|
*/
|
|
19
19
|
export declare function rendersPagesNotModified(status: number): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Hand the revalidate getStaticProps returned to build/prerender.ts, which
|
|
22
|
+
* records it in vinext-prerender.json like Next.js's `initialRevalidateSeconds`.
|
|
23
|
+
* The CDN adapter may rewrite Cache-Control, so the build cannot read it there.
|
|
24
|
+
* Only build-time renders carry this header.
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyPagesPrerenderRevalidateHeader(headers: Headers, revalidateSeconds: number | false): void;
|
|
20
27
|
type PagesFontPreload = {
|
|
21
28
|
href: string;
|
|
22
29
|
type: string;
|
|
@@ -79,6 +86,8 @@ type RenderPagesPageResponseOptions = {
|
|
|
79
86
|
setDocumentInitialHead?: ((head: ReactNode[]) => void) | undefined;
|
|
80
87
|
documentReqRes?: PagesDocumentReqRes | null;
|
|
81
88
|
gsspRes: PagesGsspResponse | null;
|
|
89
|
+
/** A page or custom `_app` getInitialProps render; rendered per request like gSSP. */
|
|
90
|
+
isInitialPropsRender?: boolean;
|
|
82
91
|
isrCacheKey: (router: string, pathname: string) => string;
|
|
83
92
|
/** Filesystem-route identity used for ISR persistence and cache tags. */
|
|
84
93
|
isrCachePathname?: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isBotUserAgent } from "../utils/html-limited-bots.js";
|
|
2
|
-
import { NEXTJS_CACHE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
|
|
2
|
+
import { NEXTJS_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
|
|
3
3
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
4
4
|
import { reportRequestError } from "./instrumentation.js";
|
|
5
|
+
import { logRequestError } from "./request-error-log.js";
|
|
5
6
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
6
7
|
import { markRouteCacheabilityExplicitResponsePolicy } from "../shims/cacheability-classification.js";
|
|
7
8
|
import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, shouldUseNextDeployCacheControl } from "./cache-control.js";
|
|
@@ -17,6 +18,7 @@ import { spliceDocumentHeadClose, spliceDocumentHeadOpen, spliceDocumentScripts,
|
|
|
17
18
|
import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
|
|
18
19
|
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
19
20
|
import { pagesPathCacheTag } from "./pages-cache-tag.js";
|
|
21
|
+
import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
|
|
20
22
|
import { readStreamAsText } from "../utils/text-stream.js";
|
|
21
23
|
import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
|
|
22
24
|
import { callDocumentGetInitialProps } from "./document-initial-head.js";
|
|
@@ -42,6 +44,17 @@ function generatePagesETag(payload) {
|
|
|
42
44
|
function rendersPagesNotModified(status) {
|
|
43
45
|
return status === 204 || status === 205 || status === 304 || getCdnCacheAdapter().deferNotModifiedResponse?.() !== true;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Hand the revalidate getStaticProps returned to build/prerender.ts, which
|
|
49
|
+
* records it in vinext-prerender.json like Next.js's `initialRevalidateSeconds`.
|
|
50
|
+
* The CDN adapter may rewrite Cache-Control, so the build cannot read it there.
|
|
51
|
+
* Only build-time renders carry this header.
|
|
52
|
+
*/
|
|
53
|
+
function applyPagesPrerenderRevalidateHeader(headers, revalidateSeconds) {
|
|
54
|
+
if (typeof process === "undefined" || process.env?.VINEXT_PRERENDER !== "1") return;
|
|
55
|
+
headers.delete(VINEXT_PRERENDER_CACHE_LIFE_HEADER);
|
|
56
|
+
if (typeof revalidateSeconds === "number") applyPrerenderCacheLifeHeader(headers, { revalidate: revalidateSeconds });
|
|
57
|
+
}
|
|
45
58
|
function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles, scriptNonce) {
|
|
46
59
|
let html = "";
|
|
47
60
|
const nonceAttr = createNonceAttribute(scriptNonce);
|
|
@@ -121,7 +134,7 @@ async function buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix) {
|
|
|
121
134
|
} });
|
|
122
135
|
}
|
|
123
136
|
async function reportPagesIsrCacheWriteError(error, cacheKey, routePattern) {
|
|
124
|
-
|
|
137
|
+
logRequestError(`[vinext] Pages ISR cache write failed for ${cacheKey}:`, error);
|
|
125
138
|
try {
|
|
126
139
|
await reportRequestError(error, {
|
|
127
140
|
path: cacheKey,
|
|
@@ -284,12 +297,13 @@ async function renderPagesPageResponse(options) {
|
|
|
284
297
|
cacheControl: buildMissIsrCacheControl(options.isrRevalidateSeconds, options.expireSeconds),
|
|
285
298
|
tags: [pathTag]
|
|
286
299
|
});
|
|
300
|
+
applyPagesPrerenderRevalidateHeader(responseHeaders, options.isrRevalidateSeconds);
|
|
287
301
|
if (options.isOnDemandRevalidate) {
|
|
288
302
|
responseHeaders.set(NEXTJS_CACHE_HEADER, "REVALIDATED");
|
|
289
303
|
responseHeaders.set(VINEXT_REVALIDATED_CACHE_TAG_HEADER, encodeURIComponent(pathTag));
|
|
290
304
|
} else setCacheStateHeaders(responseHeaders, "MISS");
|
|
291
305
|
} else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
|
|
292
|
-
else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
|
|
306
|
+
else if ((options.gsspRes || options.isInitialPropsRender || options.isFallback) && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
|
|
293
307
|
if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
|
|
294
308
|
if (options.userAgent && isPagesStreamingBot(options.userAgent)) {
|
|
295
309
|
const fullHtml = await readStreamAsText(compositeStream);
|
|
@@ -314,4 +328,4 @@ async function renderPagesPageResponse(options) {
|
|
|
314
328
|
}), { __vinextStreamedHtmlResponse: true });
|
|
315
329
|
}
|
|
316
330
|
//#endregion
|
|
317
|
-
export { buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, rendersPagesNotModified };
|
|
331
|
+
export { applyPagesPrerenderRevalidateHeader, buildPagesNextDataScript, generatePagesETag, isPagesStreamingBot, renderPagesPageResponse, rendersPagesNotModified };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
|
|
2
|
+
import { applyMiddlewareResponseHeadersToRequest } from "../utils/middleware-request-headers.js";
|
|
2
3
|
import { requestContextFromRequest } from "../config/request-context.js";
|
|
3
4
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
4
5
|
import { VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./headers.js";
|
|
5
6
|
import { notFoundStaticAssetResponse } from "./http-error-responses.js";
|
|
6
|
-
import { attachRequestCfMetadata, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl, haveSameHeaders, withRequestHeaders } from "./request-pipeline.js";
|
|
7
|
+
import { attachRequestCfMetadata, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl, haveSameHeaders, stripRequestUrlFragment, withRequestHeaders } from "./request-pipeline.js";
|
|
7
8
|
import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js";
|
|
8
9
|
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
9
10
|
import { runWithExecutionContext } from "../shims/request-context.js";
|
|
@@ -35,6 +36,15 @@ import * as pagesEntry from "virtual:vinext-pages-request-entry";
|
|
|
35
36
|
* The public pages-router-entry delegates here. Multi-stage hosts import the
|
|
36
37
|
* named request handler directly and provide their response-stage dispatcher.
|
|
37
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* Whether middleware's request-header overrides changed the request. Next.js
|
|
41
|
+
* also copies ordinary middleware response headers onto the request; like a
|
|
42
|
+
* client's own headers, those do not opt the render out of the shared stage.
|
|
43
|
+
*/
|
|
44
|
+
function hasRequestHeaderOverrides(originalHeaders, requestHeaders, middlewareResponseHeaders) {
|
|
45
|
+
const expectedHeaders = middlewareResponseHeaders ? applyMiddlewareResponseHeadersToRequest(originalHeaders, middlewareResponseHeaders) : originalHeaders;
|
|
46
|
+
return !haveSameHeaders(expectedHeaders, requestHeaders);
|
|
47
|
+
}
|
|
38
48
|
function captureOuterResponsePolicyHeaders(stagedHeaders, transportedPolicyHeaders) {
|
|
39
49
|
return captureCdnResponsePolicyOverrides(stagedHeaders, new Headers(transportedPolicyHeaders ?? []));
|
|
40
50
|
}
|
|
@@ -86,6 +96,7 @@ function handleRequestStageLocally(request, env, ctx, dispatchResponseStage) {
|
|
|
86
96
|
return handleRequest(request, env, ctx, (stageRequest, props, _options, stageEnv, stageCtx) => dispatchResponseStage(stageRequest, stageEnv, stageCtx, props), true, "worker", ctx?.assets).then((response) => applyCdnResponseIdentityHeaders(response, originalRequest));
|
|
87
97
|
}
|
|
88
98
|
function handleRequest(request, env, platformCtx, dispatchResponseStage, forceCacheBypass, defaultHostRuntime, assets) {
|
|
99
|
+
request = stripRequestUrlFragment(request);
|
|
89
100
|
const url = new URL(request.url);
|
|
90
101
|
const trace = async () => {
|
|
91
102
|
await pagesEntry.__ensureInstrumentation?.(platformCtx);
|
|
@@ -208,6 +219,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
208
219
|
requestContext: requestContextFromRequest(request)
|
|
209
220
|
});
|
|
210
221
|
let speculativeRenderRequest = null;
|
|
222
|
+
let middlewareResponseHeaders;
|
|
211
223
|
const deps = {
|
|
212
224
|
assetPrefix: vinextConfig?.assetPrefix,
|
|
213
225
|
basePath,
|
|
@@ -229,7 +241,15 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
229
241
|
authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
|
|
230
242
|
matchApiRoute: typeof matchApiRoute === "function" ? matchApiRoute : null,
|
|
231
243
|
matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
|
|
232
|
-
runMiddleware: typeof runMiddleware === "function" ?
|
|
244
|
+
runMiddleware: typeof runMiddleware === "function" ? async (middlewareInput, middlewareCtx, middlewareOptions) => {
|
|
245
|
+
const result = await wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath)(middlewareInput, middlewareCtx, middlewareOptions);
|
|
246
|
+
middlewareResponseHeaders = void 0;
|
|
247
|
+
if (result.responseHeaders) {
|
|
248
|
+
middlewareResponseHeaders = new Headers();
|
|
249
|
+
for (const [key, value] of result.responseHeaders) middlewareResponseHeaders.append(key, value);
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
} : null,
|
|
233
253
|
renderPage: (req, resolvedUrl, options, stagedHeaders) => {
|
|
234
254
|
if (options?.renderErrorPageOnMiss === false && req.body !== null && !req.bodyUsed) speculativeRenderRequest = req;
|
|
235
255
|
const stageRequest = speculativeRenderRequest === req && req.body !== null && !req.bodyUsed ? attachRequestCfMetadata(req.clone(), req) : req;
|
|
@@ -238,7 +258,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
238
258
|
authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
|
|
239
259
|
hasRequestAwareDocument: hasRequestAwareDocument === true,
|
|
240
260
|
request: req,
|
|
241
|
-
requestHeadersChanged:
|
|
261
|
+
requestHeadersChanged: hasRequestHeaderOverrides(filteredHeaders, req.headers, middlewareResponseHeaders),
|
|
242
262
|
routeDataKind: matchedPage?.route.dataKind,
|
|
243
263
|
stagedHeaders
|
|
244
264
|
});
|
|
@@ -306,7 +326,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
306
326
|
const cache = forceCacheBypass || probeMode ? "bypass" : getPagesResponseStageCacheDisposition({
|
|
307
327
|
authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
|
|
308
328
|
request: req,
|
|
309
|
-
requestHeadersChanged:
|
|
329
|
+
requestHeadersChanged: hasRequestHeaderOverrides(filteredHeaders, req.headers, middlewareResponseHeaders),
|
|
310
330
|
stagedHeaders
|
|
311
331
|
});
|
|
312
332
|
const dispatched = trackedDispatchResponseStage(req, responseStageProps(), { cache }, env, ctx);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM } from "./headers.js";
|
|
2
|
-
import { cloneRequestWithHeaders, cloneRequestWithUrl } from "./request-pipeline.js";
|
|
2
|
+
import { cloneRequestWithHeaders, cloneRequestWithUrl, stripRequestUrlFragment } from "./request-pipeline.js";
|
|
3
3
|
import { runWithExecutionContext } from "../shims/request-context.js";
|
|
4
4
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
5
5
|
import { validateCdnRequest } from "./cache-control.js";
|
|
@@ -52,7 +52,8 @@ async function handleSingleStageRequest(request, env, platformCtx) {
|
|
|
52
52
|
return finalize ? finalize(response, ctx) : response;
|
|
53
53
|
}
|
|
54
54
|
var pages_router_entry_default = { fetch(request, env, ctx) {
|
|
55
|
-
const
|
|
55
|
+
const strippedRequest = stripRequestUrlFragment(request);
|
|
56
|
+
const handle = () => handleSingleStageRequest(strippedRequest, env, ctx);
|
|
56
57
|
return ctx && typeof ctx.waitUntil === "function" ? runWithExecutionContext(ctx, handle) : handle();
|
|
57
58
|
} };
|
|
58
59
|
//#endregion
|
|
@@ -102,11 +102,11 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
|
|
|
102
102
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
103
103
|
* unlike the old sync existsSync/statSync approach).
|
|
104
104
|
*
|
|
105
|
-
* `
|
|
105
|
+
* `stagedHeaders` go on every response for the file. `representationHeaders` go
|
|
106
106
|
* only on responses that carry the file's bytes (200 and 206), never on a 304,
|
|
107
107
|
* 412, 416 or 405.
|
|
108
108
|
*/
|
|
109
|
-
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache,
|
|
109
|
+
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, stagedHeaders?: Record<string, string | string[]>, statusCode?: number, representationHeaders?: Record<string, string>): Promise<boolean>;
|
|
110
110
|
/**
|
|
111
111
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
112
112
|
*
|
|
@@ -8,7 +8,7 @@ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedire
|
|
|
8
8
|
import { readStaticFileSignal } from "./static-file-signal.js";
|
|
9
9
|
import { canonicalizeRequestPathname, filterInternalHeaders } from "./request-pipeline.js";
|
|
10
10
|
import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix, isAssetPrefixBuildAsset } from "../utils/asset-prefix.js";
|
|
11
|
-
import { StaticFileCache, contentTypeForPath, etagFromFilenameHash } from "./static-file-cache.js";
|
|
11
|
+
import { StaticFileCache, contentTypeForPath, etagFromFilenameHash, staticFileCacheControl } from "./static-file-cache.js";
|
|
12
12
|
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
13
13
|
import { resolveRequestHost, resolveRequestProtocol, resolveTrustedForwardedHost, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
14
14
|
import { normalizePath } from "./normalize-path.js";
|
|
@@ -19,7 +19,7 @@ import { parseHttpDate } from "./http-date.js";
|
|
|
19
19
|
import { evaluateStaticPreconditions } from "./http-conditional.js";
|
|
20
20
|
import { setPagesClientAssets } from "./pages-client-assets.js";
|
|
21
21
|
import { signalFromNodeResponse } from "./node-response-signal.js";
|
|
22
|
-
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, parseImageParams, readImageOptimizationSignal } from "./image-optimization.js";
|
|
22
|
+
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, parseImageParams, readImageOptimizationSignal } from "./image-optimization.js";
|
|
23
23
|
import { installSocketErrorBackstop } from "./socket-error-backstop.js";
|
|
24
24
|
import { collectInlineCssManifest } from "../build/inline-css.js";
|
|
25
25
|
import { readTrustedRevalidationHostname } from "./revalidation-host.js";
|
|
@@ -284,6 +284,16 @@ function readHeaderCaseInsensitive(headersRecord, name) {
|
|
|
284
284
|
const value = headersRecord[key];
|
|
285
285
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
286
286
|
}
|
|
287
|
+
/** Spell a header's name as `name`, so spreading the record replaces that key. */
|
|
288
|
+
function withHeaderName(headersRecord, name) {
|
|
289
|
+
const lowerName = name.toLowerCase();
|
|
290
|
+
const key = Object.keys(headersRecord).find((entry) => entry !== name && entry.toLowerCase() === lowerName);
|
|
291
|
+
if (key === void 0) return headersRecord;
|
|
292
|
+
const { [key]: value, ...rest } = headersRecord;
|
|
293
|
+
const existing = rest[name];
|
|
294
|
+
rest[name] = existing === void 0 ? value : [existing, value].flat();
|
|
295
|
+
return rest;
|
|
296
|
+
}
|
|
287
297
|
/** Remove one field name from a Vary header, dropping the header if it empties. */
|
|
288
298
|
function omitVaryToken(headers, token) {
|
|
289
299
|
const varyKey = Object.keys(headers).find((key) => key.toLowerCase() === "vary");
|
|
@@ -464,6 +474,18 @@ function staticCacheLookupPath(pathname) {
|
|
|
464
474
|
if (lookupPath.startsWith("/.vite/") || lookupPath === "/.vite") return null;
|
|
465
475
|
return lookupPath;
|
|
466
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Give a Node image passthrough Next.js's image Cache-Control instead of the
|
|
479
|
+
* public-file default: a hashed build asset keeps its immutable default, and
|
|
480
|
+
* any other source is revalidated after the default `images.minimumCacheTTL`.
|
|
481
|
+
*/
|
|
482
|
+
function withImageSourceCacheControl(headers, sourcePath) {
|
|
483
|
+
if (sourcePath.includes(`/_next/static/`)) return headers;
|
|
484
|
+
return {
|
|
485
|
+
...headers,
|
|
486
|
+
"Cache-Control": MUTABLE_IMAGE_SOURCE_CACHE_CONTROL
|
|
487
|
+
};
|
|
488
|
+
}
|
|
467
489
|
/** Pathnames the Pages Router serves from routes rather than the client directory. */
|
|
468
490
|
function isPagesFilesystemExcludedPath(pathname, phase) {
|
|
469
491
|
return pathname === "/" || pathname === "/api" || pathname.startsWith("/api/") || phase === "direct" && pathname.startsWith(`/_next/static/`);
|
|
@@ -479,12 +501,13 @@ function isPagesFilesystemExcludedPath(pathname, phase) {
|
|
|
479
501
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
480
502
|
* unlike the old sync existsSync/statSync approach).
|
|
481
503
|
*
|
|
482
|
-
* `
|
|
504
|
+
* `stagedHeaders` go on every response for the file. `representationHeaders` go
|
|
483
505
|
* only on responses that carry the file's bytes (200 and 206), never on a 304,
|
|
484
506
|
* 412, 416 or 405.
|
|
485
507
|
*/
|
|
486
|
-
async function tryServeStatic(req, res, clientDir, pathname, compress, cache,
|
|
508
|
+
async function tryServeStatic(req, res, clientDir, pathname, compress, cache, stagedHeaders, statusCode, representationHeaders) {
|
|
487
509
|
if (pathname === "/") return false;
|
|
510
|
+
const extraHeaders = stagedHeaders && withHeaderName(stagedHeaders, "Cache-Control");
|
|
488
511
|
const responseStatus = statusCode ?? 200;
|
|
489
512
|
const omitBody = isNoBodyResponseStatus(responseStatus);
|
|
490
513
|
const requestRange = req.method === "GET" || req.method === "HEAD" ? req.headers.range : void 0;
|
|
@@ -610,7 +633,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
610
633
|
const ext = path.extname(resolved.path);
|
|
611
634
|
const ct = contentTypeForPath(resolved.path);
|
|
612
635
|
const isHashed = pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`);
|
|
613
|
-
const cacheControl = isHashed
|
|
636
|
+
const cacheControl = staticFileCacheControl(isHashed);
|
|
614
637
|
const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
|
|
615
638
|
const baseHeaders = {
|
|
616
639
|
"Content-Type": ct,
|
|
@@ -798,6 +821,15 @@ async function statIfFile(filePath) {
|
|
|
798
821
|
}
|
|
799
822
|
}
|
|
800
823
|
/**
|
|
824
|
+
* Node's parser keeps a `#fragment` sent on the request line in `req.url`.
|
|
825
|
+
* Next.js rebuilds request URLs from pathname + search, so neither routing,
|
|
826
|
+
* middleware nor handlers observe one; drop it before anything reads the URL.
|
|
827
|
+
*/
|
|
828
|
+
function stripNodeRequestUrlFragment(req) {
|
|
829
|
+
const hashIndex = req.url?.indexOf("#") ?? -1;
|
|
830
|
+
if (hashIndex !== -1) req.url = req.url?.slice(0, hashIndex);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
801
833
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
802
834
|
*
|
|
803
835
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
@@ -1182,7 +1214,7 @@ async function startAppRouterServer(options) {
|
|
|
1182
1214
|
"X-Content-Type-Options": "nosniff",
|
|
1183
1215
|
"Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
1184
1216
|
};
|
|
1185
|
-
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1217
|
+
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1186
1218
|
const sourceUrl = new URL(params.imageUrl, request.url);
|
|
1187
1219
|
let sourcePathname = null;
|
|
1188
1220
|
try {
|
|
@@ -1299,6 +1331,7 @@ async function startAppRouterServer(options) {
|
|
|
1299
1331
|
}
|
|
1300
1332
|
};
|
|
1301
1333
|
const handleRequest = async (req, res) => {
|
|
1334
|
+
stripNodeRequestUrlFragment(req);
|
|
1302
1335
|
try {
|
|
1303
1336
|
await ensureInstrumentation();
|
|
1304
1337
|
} catch (error) {
|
|
@@ -1319,7 +1352,7 @@ async function startAppRouterServer(options) {
|
|
|
1319
1352
|
},
|
|
1320
1353
|
getStatus: () => res.statusCode,
|
|
1321
1354
|
headers,
|
|
1322
|
-
isRsc:
|
|
1355
|
+
isRsc: headers.get("RSC") === "1",
|
|
1323
1356
|
method: req.method ?? "GET",
|
|
1324
1357
|
target
|
|
1325
1358
|
});
|
|
@@ -1434,6 +1467,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1434
1467
|
});
|
|
1435
1468
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
1436
1469
|
const handleRequest = async (req, res) => {
|
|
1470
|
+
stripNodeRequestUrlFragment(req);
|
|
1437
1471
|
try {
|
|
1438
1472
|
await ensureInstrumentation();
|
|
1439
1473
|
} catch (error) {
|
|
@@ -1671,7 +1705,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1671
1705
|
"X-Content-Type-Options": "nosniff",
|
|
1672
1706
|
"Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
1673
1707
|
};
|
|
1674
|
-
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1708
|
+
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1675
1709
|
const sourceUrl = new URL(params.imageUrl, webRequest.url);
|
|
1676
1710
|
let sourcePathname = null;
|
|
1677
1711
|
try {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/server/request-error-log.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Logs an error vinext also passes to instrumentation's `onRequestError`, as
|
|
4
|
+
* `next start` logs it. `VINEXT_QUIET=1` turns these logs off, like Next.js'
|
|
5
|
+
* `quiet` server option, so an `onRequestError` that redacts errors before
|
|
6
|
+
* recording them is the only place they reach.
|
|
7
|
+
*/
|
|
8
|
+
export declare function logRequestError(...args: unknown[]): void;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/server/request-error-log.ts
|
|
2
|
+
/**
|
|
3
|
+
* Logs an error vinext also passes to instrumentation's `onRequestError`, as
|
|
4
|
+
* `next start` logs it. `VINEXT_QUIET=1` turns these logs off, like Next.js'
|
|
5
|
+
* `quiet` server option, so an `onRequestError` that redacts errors before
|
|
6
|
+
* recording them is the only place they reach.
|
|
7
|
+
*/
|
|
8
|
+
function logRequestError(...args) {
|
|
9
|
+
if (process.env.VINEXT_QUIET === "1") return;
|
|
10
|
+
console.error(...args);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { logRequestError };
|
|
@@ -73,7 +73,6 @@ type ResolvePublicFileRouteOptions = {
|
|
|
73
73
|
middlewareContext: StaticFileSignalContext & {
|
|
74
74
|
requestHeaders?: Headers | null;
|
|
75
75
|
};
|
|
76
|
-
pathname: string;
|
|
77
76
|
publicFiles: ReadonlySet<string>;
|
|
78
77
|
request: Request;
|
|
79
78
|
};
|
|
@@ -82,7 +81,7 @@ type ResolvePublicFileRouteOptions = {
|
|
|
82
81
|
*
|
|
83
82
|
* Public files are checked after middleware and before afterFiles/fallback
|
|
84
83
|
* rewrites. The generated App Router entry provides the public-file set; this
|
|
85
|
-
* helper owns
|
|
84
|
+
* helper owns existence-first method enforcement and
|
|
86
85
|
* static-file signaling. Missing mutation targets continue through routing.
|
|
87
86
|
*/
|
|
88
87
|
export declare function resolvePublicFileRoute(options: ResolvePublicFileRouteOptions): Response | null;
|
|
@@ -91,8 +90,8 @@ export declare function normalizeTrailingSlashPathname(pathname: string, trailin
|
|
|
91
90
|
* Check if the pathname needs a trailing slash redirect, and return the
|
|
92
91
|
* redirect Response if so.
|
|
93
92
|
*
|
|
94
|
-
* Follows Next.js behavior
|
|
95
|
-
*
|
|
93
|
+
* Follows Next.js behavior, whose internal trailing-slash redirects cover
|
|
94
|
+
* every path, `/api` included:
|
|
96
95
|
* - The root path `/` is never redirected
|
|
97
96
|
* - If `trailingSlash` is true, redirect `/about` → `/about/`
|
|
98
97
|
* - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
|
|
@@ -200,5 +199,11 @@ export declare function withRequestHeaders(request: Request, headers: Headers):
|
|
|
200
199
|
* exists.
|
|
201
200
|
*/
|
|
202
201
|
export declare function cloneRequestWithUrl(request: Request, url: string): Request;
|
|
202
|
+
/**
|
|
203
|
+
* Drop a `#fragment` the host kept from the request line. Next.js rebuilds
|
|
204
|
+
* request URLs from pathname + search, so neither middleware nor handlers
|
|
205
|
+
* ever observe one, and pathname-keyed cache entries must not either.
|
|
206
|
+
*/
|
|
207
|
+
export declare function stripRequestUrlFragment(request: Request): Request;
|
|
203
208
|
//#endregion
|
|
204
209
|
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, createStaticFileSignal, getRepeatedSlashRedirect, hasBasePath, isOpenRedirectShaped, sendRepeatedSlashRedirect, stripBasePath };
|
|
@@ -84,7 +84,8 @@ function guardProtocolRelativeUrl(rawPathname, search = "") {
|
|
|
84
84
|
if (isOpenRedirectShaped(rawPathname)) return notFoundResponse();
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
|
-
const
|
|
87
|
+
const SLASHED_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/]+\.\w+\/$/;
|
|
88
|
+
const UNSLASHED_NON_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/.]+$/;
|
|
88
89
|
function isWellKnownPathname(pathname) {
|
|
89
90
|
return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
|
|
90
91
|
}
|
|
@@ -93,37 +94,32 @@ function isWellKnownPathname(pathname) {
|
|
|
93
94
|
*
|
|
94
95
|
* Public files are checked after middleware and before afterFiles/fallback
|
|
95
96
|
* rewrites. The generated App Router entry provides the public-file set; this
|
|
96
|
-
* helper owns
|
|
97
|
+
* helper owns existence-first method enforcement and
|
|
97
98
|
* static-file signaling. Missing mutation targets continue through routing.
|
|
98
99
|
*/
|
|
99
100
|
function resolvePublicFileRoute(options) {
|
|
100
|
-
if (options.pathname.endsWith(".rsc")) return null;
|
|
101
101
|
if (!options.publicFiles.has(options.cleanPathname)) return null;
|
|
102
102
|
if (options.request.method !== "GET" && options.request.method !== "HEAD") return methodNotAllowedResponse("GET, HEAD", { headers: options.middlewareContext.headers ?? void 0 });
|
|
103
103
|
const { requestHeaders } = options.middlewareContext;
|
|
104
104
|
return createStaticFileSignal(options.cleanPathname, options.middlewareContext, requestHeaders ? buildRequestHeadersFromMiddlewareResponse(options.request.headers, requestHeaders) : null);
|
|
105
105
|
}
|
|
106
106
|
function normalizeTrailingSlashPathname(pathname, trailingSlash) {
|
|
107
|
-
if (pathname === "/"
|
|
108
|
-
const hasTrailing = pathname.endsWith("/");
|
|
107
|
+
if (pathname === "/") return null;
|
|
109
108
|
if (trailingSlash) {
|
|
110
109
|
if (isWellKnownPathname(pathname)) return null;
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
return normalized === pathname ? null : normalized;
|
|
114
|
-
}
|
|
115
|
-
if (!hasTrailing && !pathname.endsWith(".rsc")) return `${pathname}/`;
|
|
110
|
+
if (SLASHED_FILE_PATHNAME_RE.test(pathname)) return pathname.slice(0, -1);
|
|
111
|
+
if (UNSLASHED_NON_FILE_PATHNAME_RE.test(pathname)) return `${pathname}/`;
|
|
116
112
|
return null;
|
|
117
113
|
}
|
|
118
|
-
if (
|
|
114
|
+
if (pathname.endsWith("/")) return removeTrailingSlash(pathname);
|
|
119
115
|
return null;
|
|
120
116
|
}
|
|
121
117
|
/**
|
|
122
118
|
* Check if the pathname needs a trailing slash redirect, and return the
|
|
123
119
|
* redirect Response if so.
|
|
124
120
|
*
|
|
125
|
-
* Follows Next.js behavior
|
|
126
|
-
*
|
|
121
|
+
* Follows Next.js behavior, whose internal trailing-slash redirects cover
|
|
122
|
+
* every path, `/api` included:
|
|
127
123
|
* - The root path `/` is never redirected
|
|
128
124
|
* - If `trailingSlash` is true, redirect `/about` → `/about/`
|
|
129
125
|
* - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
|
|
@@ -137,7 +133,7 @@ function normalizeTrailingSlashPathname(pathname, trailingSlash) {
|
|
|
137
133
|
* @returns A 308 redirect Response, or null if no redirect is needed
|
|
138
134
|
*/
|
|
139
135
|
function normalizeTrailingSlash(pathname, basePath, trailingSlash, search) {
|
|
140
|
-
if (pathname === "/"
|
|
136
|
+
if (pathname === "/") return null;
|
|
141
137
|
if (isOpenRedirectShaped(pathname)) return notFoundResponse();
|
|
142
138
|
const normalizedPathname = normalizeTrailingSlashPathname(pathname, trailingSlash);
|
|
143
139
|
if (normalizedPathname === null) return null;
|
|
@@ -463,5 +459,17 @@ function cloneRequestWithUrl(request, url) {
|
|
|
463
459
|
}
|
|
464
460
|
return attachRequestCfMetadata(cloned, request);
|
|
465
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Drop a `#fragment` the host kept from the request line. Next.js rebuilds
|
|
464
|
+
* request URLs from pathname + search, so neither middleware nor handlers
|
|
465
|
+
* ever observe one, and pathname-keyed cache entries must not either.
|
|
466
|
+
*/
|
|
467
|
+
function stripRequestUrlFragment(request) {
|
|
468
|
+
const hashIndex = request.url.indexOf("#");
|
|
469
|
+
if (hashIndex === -1) return request;
|
|
470
|
+
const signal = request.signal;
|
|
471
|
+
const stripped = cloneRequestWithUrl(request, request.url.slice(0, hashIndex));
|
|
472
|
+
return attachRequestCfMetadata(new Request(stripped, { signal }), request);
|
|
473
|
+
}
|
|
466
474
|
//#endregion
|
|
467
|
-
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
|
|
475
|
+
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
|
|
@@ -88,9 +88,11 @@ function extractRscCompletionMetadata(buffer) {
|
|
|
88
88
|
}
|
|
89
89
|
function appendRscCompletionMetadata(source, getMetadata) {
|
|
90
90
|
const reader = source.getReader();
|
|
91
|
+
let cancelled = false;
|
|
91
92
|
return new ReadableStream({
|
|
92
93
|
async pull(controller) {
|
|
93
94
|
const next = await reader.read();
|
|
95
|
+
if (cancelled) return;
|
|
94
96
|
if (!next.done) {
|
|
95
97
|
controller.enqueue(escapeFlightChunk(next.value));
|
|
96
98
|
return;
|
|
@@ -100,6 +102,7 @@ function appendRscCompletionMetadata(source, getMetadata) {
|
|
|
100
102
|
controller.close();
|
|
101
103
|
},
|
|
102
104
|
cancel(reason) {
|
|
105
|
+
cancelled = true;
|
|
103
106
|
return reader.cancel(reason);
|
|
104
107
|
}
|
|
105
108
|
});
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
export declare const CONTENT_TYPES: Record<string, string>;
|
|
4
4
|
/** Resolve a path's MIME type with case-insensitive extension matching. */
|
|
5
5
|
export declare function contentTypeForPath(filePath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Default `Cache-Control` for a file in the client build directory. Hashed
|
|
8
|
+
* build assets never change at their URL. Every other file (`public/`) must be
|
|
9
|
+
* revalidated on each reuse, as Next.js's `send` serves it with `max-age=0`:
|
|
10
|
+
* middleware may admit it per request, so a shared cache must not reuse it
|
|
11
|
+
* without asking the origin. A `Cache-Control` set by middleware or config
|
|
12
|
+
* headers replaces this default.
|
|
13
|
+
*/
|
|
14
|
+
export declare function staticFileCacheControl(isHashed: boolean): string;
|
|
6
15
|
/** A servable file variant with pre-computed response headers. */
|
|
7
16
|
type FileVariant = {
|
|
8
17
|
/** Absolute file path (used for streaming large files). */
|
|
@@ -57,6 +57,17 @@ function contentTypeForPath(filePath) {
|
|
|
57
57
|
return CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
+
* Default `Cache-Control` for a file in the client build directory. Hashed
|
|
61
|
+
* build assets never change at their URL. Every other file (`public/`) must be
|
|
62
|
+
* revalidated on each reuse, as Next.js's `send` serves it with `max-age=0`:
|
|
63
|
+
* middleware may admit it per request, so a shared cache must not reuse it
|
|
64
|
+
* without asking the origin. A `Cache-Control` set by middleware or config
|
|
65
|
+
* headers replaces this default.
|
|
66
|
+
*/
|
|
67
|
+
function staticFileCacheControl(isHashed) {
|
|
68
|
+
return isHashed ? "public, max-age=31536000, immutable" : "public, max-age=0";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
60
71
|
* Files below this size are buffered in memory at startup for zero-syscall
|
|
61
72
|
* serving via res.end(buffer). Above this, files stream via createReadStream.
|
|
62
73
|
* 64KB covers virtually all precompressed assets (a 200KB JS bundle compresses
|
|
@@ -98,7 +109,7 @@ var StaticFileCache = class StaticFileCache {
|
|
|
98
109
|
if (isHashed && PRECOMPRESSION_TEMP_FILE_RE.test(relativePath)) continue;
|
|
99
110
|
const ext = path.extname(relativePath);
|
|
100
111
|
const contentType = contentTypeForPath(relativePath);
|
|
101
|
-
const cacheControl = isHashed
|
|
112
|
+
const cacheControl = staticFileCacheControl(isHashed);
|
|
102
113
|
const etag = isHashed && etagFromFilenameHash(relativePath, ext) || `W/"${fileInfo.size}-${Math.floor(fileInfo.mtimeMs / 1e3)}"`;
|
|
103
114
|
const lastModified = new Date(fileInfo.mtimeMs).toUTCString();
|
|
104
115
|
const baseHeaders = {
|
|
@@ -266,4 +277,4 @@ async function* walkFilesWithStats(dir, base = dir) {
|
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
//#endregion
|
|
269
|
-
export { CONTENT_TYPES, StaticFileCache, contentTypeForPath, etagFromFilenameHash };
|
|
280
|
+
export { CONTENT_TYPES, StaticFileCache, contentTypeForPath, etagFromFilenameHash, staticFileCacheControl };
|