vinext 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -21
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +4 -7
- 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 +4 -2
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +55 -2
- package/dist/config/next-config.d.ts +22 -1
- package/dist/config/next-config.js +49 -4
- package/dist/entries/app-rsc-entry.js +41 -19
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +5 -1
- package/dist/index.js +44 -32
- package/dist/init-cloudflare.js +18 -2
- package/dist/plugins/css-target.d.ts +18 -0
- package/dist/plugins/css-target.js +93 -0
- package/dist/plugins/strip-server-exports.js +2 -1
- package/dist/routing/app-route-graph.d.ts +10 -0
- package/dist/routing/app-route-graph.js +15 -1
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +5 -6
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +28 -7
- 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-elements.d.ts +3 -0
- package/dist/server/app-elements.js +14 -1
- package/dist/server/app-fallback-renderer.js +2 -2
- package/dist/server/app-optimistic-routing.d.ts +12 -1
- package/dist/server/app-optimistic-routing.js +40 -19
- package/dist/server/app-page-boundary-render.js +7 -12
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -2
- package/dist/server/app-page-dispatch.js +73 -31
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +40 -52
- package/dist/server/app-page-execution.js +9 -6
- package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
- package/dist/server/app-page-intercept-segment-config.js +90 -0
- package/dist/server/app-page-navigation-params.d.ts +23 -0
- package/dist/server/app-page-navigation-params.js +49 -0
- package/dist/server/app-page-render.d.ts +5 -3
- package/dist/server/app-page-render.js +31 -17
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +10 -0
- package/dist/server/app-page-route-wiring.js +34 -13
- package/dist/server/app-page-segment-plan.js +9 -1
- package/dist/server/app-page-segment-state.d.ts +9 -0
- package/dist/server/app-page-segment-state.js +54 -1
- 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-response-stage-entry.js +1 -0
- 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-route-module-loader.d.ts +2 -0
- package/dist/server/app-route-module-loader.js +1 -0
- 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.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +126 -103
- 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-finalizer.d.ts +3 -0
- package/dist/server/app-rsc-response-finalizer.js +11 -1
- package/dist/server/app-rsc-response-stage.js +4 -12
- package/dist/server/app-rsc-route-matching.d.ts +14 -0
- package/dist/server/app-rsc-route-matching.js +42 -18
- package/dist/server/app-server-action-execution.js +24 -14
- 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/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -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-local-patterns.d.ts +21 -0
- package/dist/server/image-local-patterns.js +59 -0
- package/dist/server/image-optimization.d.ts +28 -22
- package/dist/server/image-optimization.js +17 -2
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +25 -7
- 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/nitro-public-files-plugin.js +20 -8
- package/dist/server/pages-data-route.d.ts +5 -1
- package/dist/server/pages-data-route.js +14 -6
- 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-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +25 -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 +59 -17
- 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 +14 -4
- package/dist/server/request-pipeline.js +33 -15
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- 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/server/worker-image-optimization.d.ts +2 -2
- package/dist/server/worker-image-optimization.js +18 -3
- package/dist/server/worker-utils.d.ts +8 -0
- package/dist/server/worker-utils.js +19 -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/image-props.d.ts +6 -0
- package/dist/shims/image-props.js +39 -3
- package/dist/shims/image.js +3 -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 +9 -2
- package/dist/shims/slot.js +76 -11
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- 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,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";
|
|
@@ -270,6 +270,7 @@ const OMIT_STATIC_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
|
270
270
|
"content-type",
|
|
271
271
|
VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
|
|
272
272
|
]);
|
|
273
|
+
const IMAGE_ERROR_SIGNAL_HEADERS = ["content-type", "content-encoding"];
|
|
273
274
|
function omitHeadersCaseInsensitive(headersRecord, targets) {
|
|
274
275
|
const filtered = {};
|
|
275
276
|
for (const [key, value] of Object.entries(headersRecord)) {
|
|
@@ -284,6 +285,16 @@ function readHeaderCaseInsensitive(headersRecord, name) {
|
|
|
284
285
|
const value = headersRecord[key];
|
|
285
286
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
286
287
|
}
|
|
288
|
+
/** Spell a header's name as `name`, so spreading the record replaces that key. */
|
|
289
|
+
function withHeaderName(headersRecord, name) {
|
|
290
|
+
const lowerName = name.toLowerCase();
|
|
291
|
+
const key = Object.keys(headersRecord).find((entry) => entry !== name && entry.toLowerCase() === lowerName);
|
|
292
|
+
if (key === void 0) return headersRecord;
|
|
293
|
+
const { [key]: value, ...rest } = headersRecord;
|
|
294
|
+
const existing = rest[name];
|
|
295
|
+
rest[name] = existing === void 0 ? value : [existing, value].flat();
|
|
296
|
+
return rest;
|
|
297
|
+
}
|
|
287
298
|
/** Remove one field name from a Vary header, dropping the header if it empties. */
|
|
288
299
|
function omitVaryToken(headers, token) {
|
|
289
300
|
const varyKey = Object.keys(headers).find((key) => key.toLowerCase() === "vary");
|
|
@@ -464,6 +475,18 @@ function staticCacheLookupPath(pathname) {
|
|
|
464
475
|
if (lookupPath.startsWith("/.vite/") || lookupPath === "/.vite") return null;
|
|
465
476
|
return lookupPath;
|
|
466
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Give a Node image passthrough Next.js's image Cache-Control instead of the
|
|
480
|
+
* public-file default: a hashed build asset keeps its immutable default, and
|
|
481
|
+
* any other source is revalidated after the default `images.minimumCacheTTL`.
|
|
482
|
+
*/
|
|
483
|
+
function withImageSourceCacheControl(headers, sourcePath) {
|
|
484
|
+
if (sourcePath.includes(`/_next/static/`)) return headers;
|
|
485
|
+
return {
|
|
486
|
+
...headers,
|
|
487
|
+
"Cache-Control": MUTABLE_IMAGE_SOURCE_CACHE_CONTROL
|
|
488
|
+
};
|
|
489
|
+
}
|
|
467
490
|
/** Pathnames the Pages Router serves from routes rather than the client directory. */
|
|
468
491
|
function isPagesFilesystemExcludedPath(pathname, phase) {
|
|
469
492
|
return pathname === "/" || pathname === "/api" || pathname.startsWith("/api/") || phase === "direct" && pathname.startsWith(`/_next/static/`);
|
|
@@ -479,12 +502,13 @@ function isPagesFilesystemExcludedPath(pathname, phase) {
|
|
|
479
502
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
480
503
|
* unlike the old sync existsSync/statSync approach).
|
|
481
504
|
*
|
|
482
|
-
* `
|
|
505
|
+
* `stagedHeaders` go on every response for the file. `representationHeaders` go
|
|
483
506
|
* only on responses that carry the file's bytes (200 and 206), never on a 304,
|
|
484
507
|
* 412, 416 or 405.
|
|
485
508
|
*/
|
|
486
|
-
async function tryServeStatic(req, res, clientDir, pathname, compress, cache,
|
|
509
|
+
async function tryServeStatic(req, res, clientDir, pathname, compress, cache, stagedHeaders, statusCode, representationHeaders) {
|
|
487
510
|
if (pathname === "/") return false;
|
|
511
|
+
const extraHeaders = stagedHeaders && withHeaderName(stagedHeaders, "Cache-Control");
|
|
488
512
|
const responseStatus = statusCode ?? 200;
|
|
489
513
|
const omitBody = isNoBodyResponseStatus(responseStatus);
|
|
490
514
|
const requestRange = req.method === "GET" || req.method === "HEAD" ? req.headers.range : void 0;
|
|
@@ -610,7 +634,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
610
634
|
const ext = path.extname(resolved.path);
|
|
611
635
|
const ct = contentTypeForPath(resolved.path);
|
|
612
636
|
const isHashed = pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`);
|
|
613
|
-
const cacheControl = isHashed
|
|
637
|
+
const cacheControl = staticFileCacheControl(isHashed);
|
|
614
638
|
const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
|
|
615
639
|
const baseHeaders = {
|
|
616
640
|
"Content-Type": ct,
|
|
@@ -798,6 +822,15 @@ async function statIfFile(filePath) {
|
|
|
798
822
|
}
|
|
799
823
|
}
|
|
800
824
|
/**
|
|
825
|
+
* Node's parser keeps a `#fragment` sent on the request line in `req.url`.
|
|
826
|
+
* Next.js rebuilds request URLs from pathname + search, so neither routing,
|
|
827
|
+
* middleware nor handlers observe one; drop it before anything reads the URL.
|
|
828
|
+
*/
|
|
829
|
+
function stripNodeRequestUrlFragment(req) {
|
|
830
|
+
const hashIndex = req.url?.indexOf("#") ?? -1;
|
|
831
|
+
if (hashIndex !== -1) req.url = req.url?.slice(0, hashIndex);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
801
834
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
802
835
|
*
|
|
803
836
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
@@ -1163,18 +1196,25 @@ async function startAppRouterServer(options) {
|
|
|
1163
1196
|
if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
|
|
1164
1197
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
1165
1198
|
const serveImageOptimization = async (req, res, request, signal) => {
|
|
1166
|
-
const
|
|
1199
|
+
const signalHeaders = mergeResponseHeaders({}, new Response(null, { headers: signal.middlewareHeaders }));
|
|
1200
|
+
const stagedHeaders = omitHeadersCaseInsensitive(signalHeaders, OMIT_STATIC_RESPONSE_HEADERS);
|
|
1167
1201
|
for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
|
|
1202
|
+
const sendBadRequest = (body) => {
|
|
1203
|
+
for (const name of IMAGE_ERROR_SIGNAL_HEADERS) {
|
|
1204
|
+
const value = signalHeaders[name];
|
|
1205
|
+
if (value !== void 0) res.setHeader(name, value);
|
|
1206
|
+
}
|
|
1207
|
+
res.writeHead(400);
|
|
1208
|
+
res.end(body);
|
|
1209
|
+
};
|
|
1168
1210
|
const parsedUrl = new URL(`/${signal.search}`, "http://localhost");
|
|
1169
|
-
const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities);
|
|
1211
|
+
const params = parseImageParams(parsedUrl, appImageAllowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
|
|
1170
1212
|
if (!params) {
|
|
1171
|
-
|
|
1172
|
-
res.end("Bad Request");
|
|
1213
|
+
sendBadRequest("Bad Request");
|
|
1173
1214
|
return;
|
|
1174
1215
|
}
|
|
1175
1216
|
const sendUnsafeContentType = () => {
|
|
1176
|
-
|
|
1177
|
-
res.end("The requested resource is not an allowed image type");
|
|
1217
|
+
sendBadRequest("The requested resource is not an allowed image type");
|
|
1178
1218
|
};
|
|
1179
1219
|
const isSafeSourcePath = (sourcePath) => isSafeImageContentType(contentTypeForPath(sourcePath), imageConfig?.dangerouslyAllowSVG);
|
|
1180
1220
|
const imageSecurityHeaders = {
|
|
@@ -1182,7 +1222,7 @@ async function startAppRouterServer(options) {
|
|
|
1182
1222
|
"X-Content-Type-Options": "nosniff",
|
|
1183
1223
|
"Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
1184
1224
|
};
|
|
1185
|
-
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1225
|
+
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1186
1226
|
const sourceUrl = new URL(params.imageUrl, request.url);
|
|
1187
1227
|
let sourcePathname = null;
|
|
1188
1228
|
try {
|
|
@@ -1211,8 +1251,7 @@ async function startAppRouterServer(options) {
|
|
|
1211
1251
|
if (await serveSource(sourcePath)) return;
|
|
1212
1252
|
}
|
|
1213
1253
|
}
|
|
1214
|
-
|
|
1215
|
-
res.end(INVALID_IMAGE_SOURCE_MESSAGE);
|
|
1254
|
+
sendBadRequest(INVALID_IMAGE_SOURCE_MESSAGE);
|
|
1216
1255
|
};
|
|
1217
1256
|
const clientBuildFiles = new Set(Array.isArray(rscModule.__clientBuildFiles) ? rscModule.__clientBuildFiles : []);
|
|
1218
1257
|
const handleRequestImpl = async (req, res) => {
|
|
@@ -1299,6 +1338,7 @@ async function startAppRouterServer(options) {
|
|
|
1299
1338
|
}
|
|
1300
1339
|
};
|
|
1301
1340
|
const handleRequest = async (req, res) => {
|
|
1341
|
+
stripNodeRequestUrlFragment(req);
|
|
1302
1342
|
try {
|
|
1303
1343
|
await ensureInstrumentation();
|
|
1304
1344
|
} catch (error) {
|
|
@@ -1319,7 +1359,7 @@ async function startAppRouterServer(options) {
|
|
|
1319
1359
|
},
|
|
1320
1360
|
getStatus: () => res.statusCode,
|
|
1321
1361
|
headers,
|
|
1322
|
-
isRsc:
|
|
1362
|
+
isRsc: headers.get("RSC") === "1",
|
|
1323
1363
|
method: req.method ?? "GET",
|
|
1324
1364
|
target
|
|
1325
1365
|
});
|
|
@@ -1422,6 +1462,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1422
1462
|
dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
|
|
1423
1463
|
dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
|
|
1424
1464
|
qualities: vinextConfig.images.qualities,
|
|
1465
|
+
localPatterns: vinextConfig.images.localPatterns,
|
|
1425
1466
|
contentDispositionType: vinextConfig.images.contentDispositionType,
|
|
1426
1467
|
contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
|
|
1427
1468
|
} : void 0;
|
|
@@ -1434,6 +1475,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1434
1475
|
});
|
|
1435
1476
|
const staticCache = await StaticFileCache.create(clientDir);
|
|
1436
1477
|
const handleRequest = async (req, res) => {
|
|
1478
|
+
stripNodeRequestUrlFragment(req);
|
|
1437
1479
|
try {
|
|
1438
1480
|
await ensureInstrumentation();
|
|
1439
1481
|
} catch (error) {
|
|
@@ -1655,7 +1697,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1655
1697
|
if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
|
|
1656
1698
|
for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
|
|
1657
1699
|
const parsedUrl = new URL(resolvedUrl, "http://localhost");
|
|
1658
|
-
const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities);
|
|
1700
|
+
const params = parseImageParams(parsedUrl, allowedImageWidths, pagesImageConfig?.qualities, { localPatterns: pagesImageConfig?.localPatterns });
|
|
1659
1701
|
if (!params) {
|
|
1660
1702
|
res.writeHead(400);
|
|
1661
1703
|
res.end("Bad Request");
|
|
@@ -1671,7 +1713,7 @@ async function createPagesRouterRequestHandler(options) {
|
|
|
1671
1713
|
"X-Content-Type-Options": "nosniff",
|
|
1672
1714
|
"Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
|
|
1673
1715
|
};
|
|
1674
|
-
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1716
|
+
const serveSource = (sourcePath) => tryServeStatic(req, res, clientDir, sourcePath, false, staticCache, withImageSourceCacheControl(imageSecurityHeaders, sourcePath), void 0, IMAGE_REPRESENTATION_HEADERS);
|
|
1675
1717
|
const sourceUrl = new URL(params.imageUrl, webRequest.url);
|
|
1676
1718
|
let sourcePathname = null;
|
|
1677
1719
|
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 };
|
|
@@ -16,6 +16,11 @@ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedire
|
|
|
16
16
|
export declare function canonicalizeRequestPathname(pathname: string): string;
|
|
17
17
|
/** Canonicalize only the pathname portion while preserving the raw query. */
|
|
18
18
|
export declare function canonicalizeRequestUrlPathname(url: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the pathname has a segment the URL parser resolves as `.` or `..`,
|
|
21
|
+
* literally or percent-encoded. Such a pathname changes when it becomes a URL.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasUrlParserDotSegment(pathname: string): boolean;
|
|
19
24
|
/**
|
|
20
25
|
* Shared request pipeline utilities.
|
|
21
26
|
*
|
|
@@ -73,7 +78,6 @@ type ResolvePublicFileRouteOptions = {
|
|
|
73
78
|
middlewareContext: StaticFileSignalContext & {
|
|
74
79
|
requestHeaders?: Headers | null;
|
|
75
80
|
};
|
|
76
|
-
pathname: string;
|
|
77
81
|
publicFiles: ReadonlySet<string>;
|
|
78
82
|
request: Request;
|
|
79
83
|
};
|
|
@@ -82,7 +86,7 @@ type ResolvePublicFileRouteOptions = {
|
|
|
82
86
|
*
|
|
83
87
|
* Public files are checked after middleware and before afterFiles/fallback
|
|
84
88
|
* rewrites. The generated App Router entry provides the public-file set; this
|
|
85
|
-
* helper owns
|
|
89
|
+
* helper owns existence-first method enforcement and
|
|
86
90
|
* static-file signaling. Missing mutation targets continue through routing.
|
|
87
91
|
*/
|
|
88
92
|
export declare function resolvePublicFileRoute(options: ResolvePublicFileRouteOptions): Response | null;
|
|
@@ -91,8 +95,8 @@ export declare function normalizeTrailingSlashPathname(pathname: string, trailin
|
|
|
91
95
|
* Check if the pathname needs a trailing slash redirect, and return the
|
|
92
96
|
* redirect Response if so.
|
|
93
97
|
*
|
|
94
|
-
* Follows Next.js behavior
|
|
95
|
-
*
|
|
98
|
+
* Follows Next.js behavior, whose internal trailing-slash redirects cover
|
|
99
|
+
* every path, `/api` included:
|
|
96
100
|
* - The root path `/` is never redirected
|
|
97
101
|
* - If `trailingSlash` is true, redirect `/about` → `/about/`
|
|
98
102
|
* - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
|
|
@@ -200,5 +204,11 @@ export declare function withRequestHeaders(request: Request, headers: Headers):
|
|
|
200
204
|
* exists.
|
|
201
205
|
*/
|
|
202
206
|
export declare function cloneRequestWithUrl(request: Request, url: string): Request;
|
|
207
|
+
/**
|
|
208
|
+
* Drop a `#fragment` the host kept from the request line. Next.js rebuilds
|
|
209
|
+
* request URLs from pathname + search, so neither middleware nor handlers
|
|
210
|
+
* ever observe one, and pathname-keyed cache entries must not either.
|
|
211
|
+
*/
|
|
212
|
+
export declare function stripRequestUrlFragment(request: Request): Request;
|
|
203
213
|
//#endregion
|
|
204
214
|
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, createStaticFileSignal, getRepeatedSlashRedirect, hasBasePath, isOpenRedirectShaped, sendRepeatedSlashRedirect, stripBasePath };
|
|
@@ -30,6 +30,16 @@ function canonicalizeRequestUrlPathname(url) {
|
|
|
30
30
|
return canonicalizeRequestPathname(pathname) + search;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
+
* Whether the pathname has a segment the URL parser resolves as `.` or `..`,
|
|
34
|
+
* literally or percent-encoded. Such a pathname changes when it becomes a URL.
|
|
35
|
+
*/
|
|
36
|
+
function hasUrlParserDotSegment(pathname) {
|
|
37
|
+
return pathname.split("/").some((segment) => {
|
|
38
|
+
const decodedDots = segment.replaceAll(/%2e/gi, ".");
|
|
39
|
+
return decodedDots === "." || decodedDots === "..";
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
33
43
|
* Shared request pipeline utilities.
|
|
34
44
|
*
|
|
35
45
|
* Extracted from generated entries and server hot paths to keep codegen focused
|
|
@@ -84,7 +94,8 @@ function guardProtocolRelativeUrl(rawPathname, search = "") {
|
|
|
84
94
|
if (isOpenRedirectShaped(rawPathname)) return notFoundResponse();
|
|
85
95
|
return null;
|
|
86
96
|
}
|
|
87
|
-
const
|
|
97
|
+
const SLASHED_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/]+\.\w+\/$/;
|
|
98
|
+
const UNSLASHED_NON_FILE_PATHNAME_RE = /^\/(?:[^/]+\/)*[^/.]+$/;
|
|
88
99
|
function isWellKnownPathname(pathname) {
|
|
89
100
|
return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
|
|
90
101
|
}
|
|
@@ -93,37 +104,32 @@ function isWellKnownPathname(pathname) {
|
|
|
93
104
|
*
|
|
94
105
|
* Public files are checked after middleware and before afterFiles/fallback
|
|
95
106
|
* rewrites. The generated App Router entry provides the public-file set; this
|
|
96
|
-
* helper owns
|
|
107
|
+
* helper owns existence-first method enforcement and
|
|
97
108
|
* static-file signaling. Missing mutation targets continue through routing.
|
|
98
109
|
*/
|
|
99
110
|
function resolvePublicFileRoute(options) {
|
|
100
|
-
if (options.pathname.endsWith(".rsc")) return null;
|
|
101
111
|
if (!options.publicFiles.has(options.cleanPathname)) return null;
|
|
102
112
|
if (options.request.method !== "GET" && options.request.method !== "HEAD") return methodNotAllowedResponse("GET, HEAD", { headers: options.middlewareContext.headers ?? void 0 });
|
|
103
113
|
const { requestHeaders } = options.middlewareContext;
|
|
104
114
|
return createStaticFileSignal(options.cleanPathname, options.middlewareContext, requestHeaders ? buildRequestHeadersFromMiddlewareResponse(options.request.headers, requestHeaders) : null);
|
|
105
115
|
}
|
|
106
116
|
function normalizeTrailingSlashPathname(pathname, trailingSlash) {
|
|
107
|
-
if (pathname === "/"
|
|
108
|
-
const hasTrailing = pathname.endsWith("/");
|
|
117
|
+
if (pathname === "/") return null;
|
|
109
118
|
if (trailingSlash) {
|
|
110
119
|
if (isWellKnownPathname(pathname)) return null;
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
return normalized === pathname ? null : normalized;
|
|
114
|
-
}
|
|
115
|
-
if (!hasTrailing && !pathname.endsWith(".rsc")) return `${pathname}/`;
|
|
120
|
+
if (SLASHED_FILE_PATHNAME_RE.test(pathname)) return pathname.slice(0, -1);
|
|
121
|
+
if (UNSLASHED_NON_FILE_PATHNAME_RE.test(pathname)) return `${pathname}/`;
|
|
116
122
|
return null;
|
|
117
123
|
}
|
|
118
|
-
if (
|
|
124
|
+
if (pathname.endsWith("/")) return removeTrailingSlash(pathname);
|
|
119
125
|
return null;
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
128
|
* Check if the pathname needs a trailing slash redirect, and return the
|
|
123
129
|
* redirect Response if so.
|
|
124
130
|
*
|
|
125
|
-
* Follows Next.js behavior
|
|
126
|
-
*
|
|
131
|
+
* Follows Next.js behavior, whose internal trailing-slash redirects cover
|
|
132
|
+
* every path, `/api` included:
|
|
127
133
|
* - The root path `/` is never redirected
|
|
128
134
|
* - If `trailingSlash` is true, redirect `/about` → `/about/`
|
|
129
135
|
* - If `trailingSlash` is true, redirect file-looking `/file.ext/` → `/file.ext`
|
|
@@ -137,7 +143,7 @@ function normalizeTrailingSlashPathname(pathname, trailingSlash) {
|
|
|
137
143
|
* @returns A 308 redirect Response, or null if no redirect is needed
|
|
138
144
|
*/
|
|
139
145
|
function normalizeTrailingSlash(pathname, basePath, trailingSlash, search) {
|
|
140
|
-
if (pathname === "/"
|
|
146
|
+
if (pathname === "/") return null;
|
|
141
147
|
if (isOpenRedirectShaped(pathname)) return notFoundResponse();
|
|
142
148
|
const normalizedPathname = normalizeTrailingSlashPathname(pathname, trailingSlash);
|
|
143
149
|
if (normalizedPathname === null) return null;
|
|
@@ -463,5 +469,17 @@ function cloneRequestWithUrl(request, url) {
|
|
|
463
469
|
}
|
|
464
470
|
return attachRequestCfMetadata(cloned, request);
|
|
465
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Drop a `#fragment` the host kept from the request line. Next.js rebuilds
|
|
474
|
+
* request URLs from pathname + search, so neither middleware nor handlers
|
|
475
|
+
* ever observe one, and pathname-keyed cache entries must not either.
|
|
476
|
+
*/
|
|
477
|
+
function stripRequestUrlFragment(request) {
|
|
478
|
+
const hashIndex = request.url.indexOf("#");
|
|
479
|
+
if (hashIndex === -1) return request;
|
|
480
|
+
const signal = request.signal;
|
|
481
|
+
const stripped = cloneRequestWithUrl(request, request.url.slice(0, hashIndex));
|
|
482
|
+
return attachRequestCfMetadata(new Request(stripped, { signal }), request);
|
|
483
|
+
}
|
|
466
484
|
//#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 };
|
|
485
|
+
export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, hasUrlParserDotSegment, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, stripRequestUrlFragment, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
|
|
@@ -23,6 +23,13 @@ export type ResponseStageCacheabilityOptions = {
|
|
|
23
23
|
* shared response drops them before admission and storage.
|
|
24
24
|
*/
|
|
25
25
|
recomposesRequestScopedHeaders?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Keeps `X-Vinext-Params` when the request scoped headers are dropped. An
|
|
28
|
+
* intercepted response's params include its target's, which the request
|
|
29
|
+
* stage doesn't match. Its identity holds the interception context, so the
|
|
30
|
+
* stored params are its own.
|
|
31
|
+
*/
|
|
32
|
+
keepsParamsHeader?: boolean;
|
|
26
33
|
/** Generated adapter registration, deferred until the response stage executes. */
|
|
27
34
|
registerCacheAdapters(): void;
|
|
28
35
|
request: Request;
|
|
@@ -24,13 +24,13 @@ async function withResponseStageCacheability(options, render) {
|
|
|
24
24
|
const stripsRequestScopedHeaders = options.recomposesRequestScopedHeaders === true && options.cache === "shared" && !options.probeMode;
|
|
25
25
|
if (!cacheability) {
|
|
26
26
|
const rendered = await render(context);
|
|
27
|
-
return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered) : rendered;
|
|
27
|
+
return stripsRequestScopedHeaders ? withoutRequestScopedHeaders(rendered, options.keepsParamsHeader === true) : rendered;
|
|
28
28
|
}
|
|
29
29
|
if (options.policyHeadersAppliedBeforeRender) cacheability.recordResponseStageCachePolicy(context, options.policyHeaders);
|
|
30
30
|
const state = Reflect.get(context, CACHEABILITY_REQUEST_STATE);
|
|
31
31
|
if (options.probeMode && options.forceDynamic && !options.policyHeaders?.length && state) state.patternDynamicReason = "dynamic = \"force-dynamic\"";
|
|
32
32
|
const renderedResponse = await render(context);
|
|
33
|
-
const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse) : renderedResponse;
|
|
33
|
+
const rendered = stripsRequestScopedHeaders ? withoutRequestScopedHeaders(renderedResponse, options.keepsParamsHeader === true) : renderedResponse;
|
|
34
34
|
const response = options.policyHeadersAppliedBeforeRender ? rendered : cacheability.applyResponseStageCachePolicy(rendered, context, options.policyHeaders);
|
|
35
35
|
const complete = (candidate) => cacheability.finalizeWorkerCacheabilityResponse(candidate, context);
|
|
36
36
|
const route = state?.route;
|
|
@@ -40,10 +40,11 @@ async function withResponseStageCacheability(options, render) {
|
|
|
40
40
|
}
|
|
41
41
|
return complete(response);
|
|
42
42
|
}
|
|
43
|
-
function withoutRequestScopedHeaders(response) {
|
|
44
|
-
|
|
43
|
+
function withoutRequestScopedHeaders(response, keepsParams) {
|
|
44
|
+
const dropsParams = !keepsParams && response.headers.has("X-Vinext-Params");
|
|
45
|
+
if (!dropsParams && !response.headers.has("X-Vinext-Rendered-Path-And-Search")) return response;
|
|
45
46
|
const headers = new Headers(response.headers);
|
|
46
|
-
headers.delete(VINEXT_PARAMS_HEADER);
|
|
47
|
+
if (dropsParams) headers.delete(VINEXT_PARAMS_HEADER);
|
|
47
48
|
headers.delete(VINEXT_RENDERED_PATH_AND_SEARCH_HEADER);
|
|
48
49
|
return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
|
|
49
50
|
headers,
|
|
@@ -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 };
|
|
@@ -27,8 +27,8 @@ export declare function fetchWorkerImageSource(imageUrl: string, request: Reques
|
|
|
27
27
|
export declare function fetchSignalledPublicFile(response: Response, sourceRequest: Request, assets: VinextAssetFetcher): Promise<Response> | null;
|
|
28
28
|
/**
|
|
29
29
|
* Serve the App Router image endpoint once the request handler admitted it
|
|
30
|
-
* through middleware and rewrites.
|
|
31
|
-
* the image, as Next.js does.
|
|
30
|
+
* through middleware and rewrites. Config and middleware response headers are
|
|
31
|
+
* sent with the image, as Next.js does.
|
|
32
32
|
*/
|
|
33
33
|
export declare function serveWorkerImageOptimization(request: Request, signal: ImageOptimizationSignal, allowedWidths: number[] | undefined, imageConfig: ImageConfig | undefined, options: WorkerImageSourceOptions): Promise<Response>;
|
|
34
34
|
//#endregion
|
|
@@ -78,8 +78,8 @@ function fetchSignalledPublicFile(response, sourceRequest, assets) {
|
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Serve the App Router image endpoint once the request handler admitted it
|
|
81
|
-
* through middleware and rewrites.
|
|
82
|
-
* the image, as Next.js does.
|
|
81
|
+
* through middleware and rewrites. Config and middleware response headers are
|
|
82
|
+
* sent with the image, as Next.js does.
|
|
83
83
|
*/
|
|
84
84
|
async function serveWorkerImageOptimization(request, signal, allowedWidths, imageConfig, options) {
|
|
85
85
|
const imageUrl = new URL(request.url);
|
|
@@ -93,7 +93,22 @@ async function serveWorkerImageOptimization(request, signal, allowedWidths, imag
|
|
|
93
93
|
});
|
|
94
94
|
const cookies = signal.middlewareHeaders.getSetCookie();
|
|
95
95
|
if (cookies.length > 0) middlewareHeaders["set-cookie"] = cookies;
|
|
96
|
-
|
|
96
|
+
const merged = mergeHeaders(response, middlewareHeaders);
|
|
97
|
+
if (response.status < 400) return merged;
|
|
98
|
+
const errorHeaders = new Headers(merged.headers);
|
|
99
|
+
let replaced = false;
|
|
100
|
+
for (const name of ["content-type", "content-encoding"]) {
|
|
101
|
+
const value = signal.middlewareHeaders.get(name);
|
|
102
|
+
if (value === null) continue;
|
|
103
|
+
errorHeaders.set(name, value);
|
|
104
|
+
replaced = true;
|
|
105
|
+
}
|
|
106
|
+
if (!replaced) return merged;
|
|
107
|
+
return new Response(merged.body, {
|
|
108
|
+
headers: errorHeaders,
|
|
109
|
+
status: merged.status,
|
|
110
|
+
statusText: merged.statusText
|
|
111
|
+
});
|
|
97
112
|
}
|
|
98
113
|
//#endregion
|
|
99
114
|
export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
//#region src/server/worker-utils.d.ts
|
|
2
2
|
export declare function finalizeMissingStaticAssetResponse(response: Response, missingBuildAsset: boolean): Response;
|
|
3
3
|
export declare function mergeHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
|
|
4
|
+
/**
|
|
5
|
+
* Merge middleware/config headers into a file served from the asset layer.
|
|
6
|
+
* Its Cache-Control is only the layer's default (Workers Static Assets sends
|
|
7
|
+
* `public, max-age=0, must-revalidate` with every file), so one set by
|
|
8
|
+
* middleware or `headers()` wins, as Next.js's static file server sets its
|
|
9
|
+
* own only when the response has none.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeStaticFileHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
|
|
4
12
|
export declare function resolveStaticAssetSignal(signalResponse: Response, options: {
|
|
5
13
|
/** `requestHeaders` are the post-middleware request headers, when middleware set any. */
|
|
6
14
|
fetchAsset(path: string, requestHeaders: Headers | null): Promise<Response>;
|
|
@@ -82,6 +82,23 @@ function mergeHeaders(response, extraHeaders, statusOverride) {
|
|
|
82
82
|
headers: merged
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Merge middleware/config headers into a file served from the asset layer.
|
|
87
|
+
* Its Cache-Control is only the layer's default (Workers Static Assets sends
|
|
88
|
+
* `public, max-age=0, must-revalidate` with every file), so one set by
|
|
89
|
+
* middleware or `headers()` wins, as Next.js's static file server sets its
|
|
90
|
+
* own only when the response has none.
|
|
91
|
+
*/
|
|
92
|
+
function mergeStaticFileHeaders(response, extraHeaders, statusOverride) {
|
|
93
|
+
const merged = mergeHeaders(response, extraHeaders, statusOverride);
|
|
94
|
+
for (const [name, value] of Object.entries(extraHeaders)) {
|
|
95
|
+
if (name.toLowerCase() !== "cache-control") continue;
|
|
96
|
+
const cacheControl = (Array.isArray(value) ? value.join(", ") : value).trim();
|
|
97
|
+
if (!cacheControl) continue;
|
|
98
|
+
merged.headers.set(name, cacheControl);
|
|
99
|
+
}
|
|
100
|
+
return merged;
|
|
101
|
+
}
|
|
85
102
|
async function resolveStaticAssetSignal(signalResponse, options) {
|
|
86
103
|
const signal = readStaticFileSignal(signalResponse);
|
|
87
104
|
if (!signal) return null;
|
|
@@ -100,7 +117,7 @@ async function resolveStaticAssetSignal(signalResponse, options) {
|
|
|
100
117
|
const requestHeaders = readStaticFileSignalRequestHeaders(signalResponse);
|
|
101
118
|
cancelResponseBody(signalResponse);
|
|
102
119
|
const assetResponse = await options.fetchAsset(assetPath, requestHeaders);
|
|
103
|
-
return
|
|
120
|
+
return mergeStaticFileHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
|
|
104
121
|
}
|
|
105
122
|
/**
|
|
106
123
|
* Retarget a Worker asset request without dropping its conditional/range
|
|
@@ -116,4 +133,4 @@ function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
|
|
|
116
133
|
});
|
|
117
134
|
}
|
|
118
135
|
//#endregion
|
|
119
|
-
export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, resolveStaticAssetSignal };
|
|
136
|
+
export { createStaticAssetRequest, finalizeMissingStaticAssetResponse, mergeHeaders, mergeStaticFileHeaders, resolveStaticAssetSignal };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { CacheLifeConfig } from "../utils/cache-life-profiles.js";
|
|
1
2
|
import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
|
|
2
3
|
//#region src/shims/cache-request-state.d.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
4
|
+
/**
|
|
5
|
+
* The built-in profiles merged with next.config `cacheLife`, baked in at
|
|
6
|
+
* build time like Next.js's `process.env.__NEXT_CACHE_LIFE` define.
|
|
7
|
+
*/
|
|
8
8
|
export declare const cacheLifeProfiles: Record<string, CacheLifeConfig>;
|
|
9
9
|
export type UnstableCacheRevalidationMode = "foreground" | "background";
|
|
10
10
|
export type ActionRevalidationKind = 0 | 1 | 2;
|
|
@@ -121,4 +121,4 @@ export declare function _captureRequestScopedCacheLifeAccessors(): {
|
|
|
121
121
|
export declare function runWithDetachedCacheObservations<T>(fn: () => Promise<T>): Promise<T>;
|
|
122
122
|
export declare function shouldServeStaleUnstableCacheEntry(): boolean;
|
|
123
123
|
//#endregion
|
|
124
|
-
export { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext };
|
|
124
|
+
export { type CacheLifeConfig, _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext };
|