vinext 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/dist/build/client-build-config.d.ts +10 -0
- package/dist/build/client-build-config.js +18 -2
- package/dist/build/pages-client-assets-module.d.ts +8 -0
- package/dist/build/pages-client-assets-module.js +21 -1
- package/dist/build/prerender-paths.js +5 -3
- package/dist/build/run-prerender.js +1 -1
- package/dist/build/ssr-manifest.d.ts +2 -0
- package/dist/build/ssr-manifest.js +6 -2
- package/dist/check.js +7 -6
- package/dist/cli.js +2 -1
- package/dist/client/client-redirect-matcher.d.ts +14 -0
- package/dist/client/client-redirect-matcher.js +37 -0
- package/dist/client/client-redirects.d.ts +27 -0
- package/dist/client/client-redirects.js +28 -0
- package/dist/client/client-rewrites.d.ts +2 -1
- package/dist/client/client-rewrites.js +1 -1
- package/dist/config/config-matchers.d.ts +26 -11
- package/dist/config/config-matchers.js +78 -81
- package/dist/config/destination-params.d.ts +47 -0
- package/dist/config/destination-params.js +116 -0
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +3 -0
- package/dist/entries/app-rsc-entry.d.ts +4 -0
- package/dist/entries/app-rsc-entry.js +20 -6
- package/dist/entries/pages-client-entry.js +3 -1
- package/dist/entries/pages-server-entry.js +13 -37
- package/dist/index.d.ts +3 -1
- package/dist/index.js +134 -43
- package/dist/init-cloudflare.d.ts +9 -0
- package/dist/init-cloudflare.js +107 -31
- package/dist/init-platform.d.ts +4 -0
- package/dist/init-platform.js +27 -2
- package/dist/init.d.ts +1 -0
- package/dist/init.js +67 -3
- package/dist/plugins/ast-scope.d.ts +7 -0
- package/dist/plugins/ast-scope.js +59 -3
- package/dist/plugins/cache-flight-codec.d.ts +1 -1
- package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
- package/dist/plugins/dynamic-preload-metadata.js +9 -8
- package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
- package/dist/plugins/ignore-dynamic-requests.js +3 -4
- package/dist/plugins/middleware-server-only.d.ts +5 -4
- package/dist/plugins/middleware-server-only.js +8 -5
- package/dist/plugins/pages-node-externals.js +1 -1
- package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
- package/dist/plugins/rsc-scan-glob-imports.js +50 -0
- package/dist/plugins/strip-server-exports.js +262 -98
- package/dist/plugins/transform-result.d.ts +9 -5
- package/dist/plugins/transitive-externals.js +1 -1
- package/dist/plugins/typeof-window.d.ts +2 -2
- package/dist/plugins/use-cache-callable.js +8 -2
- package/dist/plugins/worker-server-only-imports.d.ts +20 -0
- package/dist/plugins/worker-server-only-imports.js +31 -0
- package/dist/plugins/worker-use-server.d.ts +25 -0
- package/dist/plugins/worker-use-server.js +129 -0
- package/dist/server/api-handler.d.ts +1 -0
- package/dist/server/api-handler.js +2 -1
- package/dist/server/app-browser-action-result.d.ts +1 -1
- package/dist/server/app-browser-action-result.js +9 -2
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-server-action-client.js +1 -1
- package/dist/server/app-middleware.d.ts +11 -0
- package/dist/server/app-middleware.js +17 -2
- package/dist/server/app-page-cache.d.ts +1 -1
- package/dist/server/app-page-cache.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +3 -1
- package/dist/server/app-page-dispatch.js +4 -3
- package/dist/server/app-page-ppr-runtime.js +1 -1
- package/dist/server/app-page-render-observation.js +1 -1
- package/dist/server/app-page-request.js +53 -15
- package/dist/server/app-page-route-wiring.js +2 -2
- package/dist/server/app-pages-bridge.d.ts +1 -1
- package/dist/server/app-pages-bridge.js +2 -3
- package/dist/server/app-prerender-endpoints.js +9 -6
- package/dist/server/app-request-stage-dispatch.js +6 -2
- package/dist/server/app-request-stage-independent-entry.js +25 -5
- package/dist/server/app-response-stage-entry.js +10 -2
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +5 -2
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +4 -1
- package/dist/server/app-route-handler-runtime.js +45 -24
- package/dist/server/app-router-entry.js +24 -5
- package/dist/server/app-rsc-cache-busting.js +1 -1
- package/dist/server/app-rsc-errors.js +20 -16
- package/dist/server/app-rsc-handler.d.ts +7 -0
- package/dist/server/app-rsc-handler.js +85 -35
- package/dist/server/app-rsc-response-stage.js +4 -2
- package/dist/server/app-segment-config.d.ts +2 -1
- package/dist/server/app-server-action-execution.d.ts +2 -1
- package/dist/server/app-server-action-execution.js +61 -22
- package/dist/server/app-ssr-entry.js +2 -1
- package/dist/server/app-worker-stages.d.ts +5 -0
- package/dist/server/app-worker-stages.js +1 -1
- package/dist/server/cacheability-request.js +2 -2
- package/dist/server/client-build-files.d.ts +16 -0
- package/dist/server/client-build-files.js +36 -0
- package/dist/server/client-reuse-manifest.js +1 -1
- package/dist/server/dev-server.js +38 -27
- package/dist/server/dev-stack-sourcemap.js +18 -23
- package/dist/server/document-initial-head.d.ts +0 -11
- package/dist/server/document-initial-head.js +1 -1
- package/dist/server/forwarded-middleware-context.d.ts +5 -0
- package/dist/server/forwarded-middleware-context.js +22 -0
- package/dist/server/headers.d.ts +4 -1
- package/dist/server/headers.js +4 -1
- package/dist/server/http-conditional.d.ts +11 -0
- package/dist/server/http-conditional.js +10 -1
- package/dist/server/http-error-responses.d.ts +1 -1
- package/dist/server/image-optimization.d.ts +38 -0
- package/dist/server/image-optimization.js +93 -4
- package/dist/server/isr-cache.d.ts +10 -0
- package/dist/server/isr-cache.js +41 -6
- package/dist/server/metadata-route-response.js +9 -8
- package/dist/server/middleware-matcher.d.ts +2 -0
- package/dist/server/middleware-matcher.js +23 -2
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +33 -20
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +3 -1
- package/dist/server/multi-stage.d.ts +11 -0
- package/dist/server/normalize-path.d.ts +0 -13
- package/dist/server/normalize-path.js +1 -34
- package/dist/server/pages-api-route.d.ts +3 -2
- package/dist/server/pages-api-route.js +4 -3
- package/dist/server/pages-cache-tag.d.ts +12 -0
- package/dist/server/pages-cache-tag.js +19 -0
- package/dist/server/pages-client-assets.d.ts +2 -0
- package/dist/server/pages-data-route.d.ts +10 -0
- package/dist/server/pages-data-route.js +19 -2
- package/dist/server/pages-document-asset-props.d.ts +5 -1
- package/dist/server/pages-document-asset-props.js +13 -5
- package/dist/server/pages-document-splice.d.ts +19 -0
- package/dist/server/pages-document-splice.js +55 -0
- package/dist/server/pages-i18n.d.ts +12 -1
- package/dist/server/pages-i18n.js +18 -4
- package/dist/server/pages-node-compat.d.ts +16 -5
- package/dist/server/pages-node-compat.js +20 -12
- package/dist/server/pages-page-data.d.ts +32 -24
- package/dist/server/pages-page-data.js +50 -120
- package/dist/server/pages-page-handler.d.ts +0 -6
- package/dist/server/pages-page-handler.js +188 -126
- package/dist/server/pages-page-response.d.ts +12 -6
- package/dist/server/pages-page-response.js +41 -34
- package/dist/server/pages-request-pipeline.d.ts +10 -2
- package/dist/server/pages-request-pipeline.js +37 -18
- package/dist/server/pages-request-stage-entry.d.ts +6 -0
- package/dist/server/pages-request-stage-entry.js +71 -12
- package/dist/server/pages-response-stage-entry.js +4 -0
- package/dist/server/pages-revalidate.js +1 -1
- package/dist/server/pages-router-entry.js +4 -3
- package/dist/server/prerender-manifest.d.ts +3 -3
- package/dist/server/prerender-manifest.js +4 -3
- package/dist/server/prerender-work-unit-setup.js +4 -0
- package/dist/server/prod-server.d.ts +12 -0
- package/dist/server/prod-server.js +199 -69
- package/dist/server/proper-error.d.ts +4 -0
- package/dist/server/proper-error.js +31 -0
- package/dist/server/request-pipeline.d.ts +4 -1
- package/dist/server/request-pipeline.js +3 -2
- package/dist/server/revalidation-request.d.ts +1 -0
- package/dist/server/revalidation-request.js +1 -1
- package/dist/server/rsc-stream-hints.js +31 -2
- package/dist/server/seed-cache.d.ts +1 -0
- package/dist/server/seed-cache.js +5 -4
- package/dist/server/server-reference-loader.d.ts +4 -0
- package/dist/server/server-reference-loader.js +29 -0
- package/dist/server/socket-error-backstop.d.ts +9 -1
- package/dist/server/socket-error-backstop.js +46 -17
- package/dist/server/static-file-signal.d.ts +15 -4
- package/dist/server/static-file-signal.js +46 -12
- package/dist/server/worker-image-optimization.d.ts +34 -0
- package/dist/server/worker-image-optimization.js +86 -0
- package/dist/server/worker-stages.d.ts +1 -1
- package/dist/server/worker-stages.js +2 -2
- package/dist/server/worker-utils.d.ts +8 -3
- package/dist/server/worker-utils.js +12 -7
- package/dist/shims/app-router-scroll.js +4 -1
- package/dist/shims/app-router-tree-context.d.ts +5 -0
- package/dist/shims/app-router-tree-context.js +27 -0
- package/dist/shims/cache-callable-runtime.d.ts +4 -2
- package/dist/shims/cache-callable-runtime.js +21 -12
- package/dist/shims/cache-handler.js +1 -9
- package/dist/shims/cache-runtime.d.ts +6 -1
- package/dist/shims/cache-runtime.js +2 -1
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +40 -13
- package/dist/shims/cdn-cache.d.ts +8 -0
- package/dist/shims/cdn-cache.js +17 -1
- package/dist/shims/disable-smooth-scroll.d.ts +3 -0
- package/dist/shims/disable-smooth-scroll.js +36 -0
- package/dist/shims/document-placeholders.d.ts +19 -0
- package/dist/shims/document-placeholders.js +45 -0
- package/dist/shims/document.js +21 -5
- package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
- package/dist/shims/dynamic-preload-chunks.js +13 -9
- package/dist/shims/dynamic.d.ts +1 -1
- package/dist/shims/dynamic.js +60 -8
- package/dist/shims/fetch-cache-state.d.ts +66 -0
- package/dist/shims/fetch-cache-state.js +92 -0
- package/dist/shims/fetch-cache.d.ts +3 -67
- package/dist/shims/fetch-cache.js +160 -125
- package/dist/shims/head.d.ts +10 -6
- package/dist/shims/head.js +15 -13
- package/dist/shims/image.js +2 -1
- package/dist/shims/internal/work-store-scope.d.ts +5 -0
- package/dist/shims/internal/work-store-scope.js +24 -0
- package/dist/shims/navigation.js +31 -5
- package/dist/shims/request-context.d.ts +2 -0
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/router.js +12 -20
- package/dist/shims/server.d.ts +16 -0
- package/dist/shims/server.js +24 -4
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/url-utils.d.ts +2 -0
- package/dist/shims/url-utils.js +3 -1
- package/dist/utils/client-runtime-metadata.js +1 -1
- package/dist/utils/hash.d.ts +7 -0
- package/dist/utils/hash.js +11 -1
- package/dist/utils/package-name.d.ts +6 -0
- package/dist/utils/package-name.js +12 -1
- package/dist/utils/plugin-rsc-version.d.ts +4 -0
- package/dist/utils/plugin-rsc-version.js +15 -0
- package/dist/utils/prerender-output-paths.js +15 -4
- package/dist/utils/public-file-path.d.ts +10 -2
- package/dist/utils/public-file-path.js +13 -9
- package/dist/utils/public-routes.js +2 -2
- package/dist/utils/react-version.d.ts +3 -0
- package/dist/utils/react-version.js +1 -1
- package/package.json +7 -3
|
@@ -78,6 +78,93 @@ function resolveDevImageRedirect(requestUrl, allowedWidths = [...DEFAULT_DEVICE_
|
|
|
78
78
|
if (resolved.origin !== requestUrl.origin) return null;
|
|
79
79
|
return resolved.pathname + resolved.search;
|
|
80
80
|
}
|
|
81
|
+
const IMAGE_OPTIMIZATION_SIGNAL = Symbol.for("vinext.image-optimization-signal");
|
|
82
|
+
/**
|
|
83
|
+
* Mark the App Router's image endpoint response once the request has passed
|
|
84
|
+
* middleware and rewrites. A host that serves images itself (the Node
|
|
85
|
+
* production server) replaces the response with the image; other hosts send
|
|
86
|
+
* the response unchanged. The symbol carries the signal across the built RSC
|
|
87
|
+
* module boundary, so response headers cannot forge it.
|
|
88
|
+
*/
|
|
89
|
+
function markImageOptimizationSignal(response, signal) {
|
|
90
|
+
Object.defineProperty(response, IMAGE_OPTIMIZATION_SIGNAL, { value: signal });
|
|
91
|
+
return response;
|
|
92
|
+
}
|
|
93
|
+
/** Return the signal of a framework-created image endpoint response. */
|
|
94
|
+
function readImageOptimizationSignal(response) {
|
|
95
|
+
const signal = Reflect.get(response, IMAGE_OPTIMIZATION_SIGNAL);
|
|
96
|
+
if (typeof signal !== "object" || signal === null) return null;
|
|
97
|
+
const { middlewareHeaders, requestHeaders, search } = signal;
|
|
98
|
+
return middlewareHeaders instanceof Headers && (requestHeaders === null || requestHeaders instanceof Headers) && typeof search === "string" ? {
|
|
99
|
+
middlewareHeaders,
|
|
100
|
+
requestHeaders,
|
|
101
|
+
search
|
|
102
|
+
} : null;
|
|
103
|
+
}
|
|
104
|
+
const IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER = "x-vinext-stage-image-optimization";
|
|
105
|
+
function isHeaderEntries(value) {
|
|
106
|
+
return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Carry a signal through the response-stage transport, which drops the
|
|
110
|
+
* symbol. The signal rides in the body of the bodiless carrier, with the
|
|
111
|
+
* carrier's status in the token-authenticated marker.
|
|
112
|
+
*/
|
|
113
|
+
function serializeImageOptimizationSignalForTransport(response, token) {
|
|
114
|
+
const signal = readImageOptimizationSignal(response);
|
|
115
|
+
if (signal === null) return response;
|
|
116
|
+
const headers = new Headers(response.headers);
|
|
117
|
+
headers.set(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER, `${token}:${response.status}`);
|
|
118
|
+
return new Response(JSON.stringify({
|
|
119
|
+
middlewareHeaders: [...signal.middlewareHeaders],
|
|
120
|
+
requestHeaders: signal.requestHeaders ? [...signal.requestHeaders] : null,
|
|
121
|
+
search: signal.search
|
|
122
|
+
}), { headers });
|
|
123
|
+
}
|
|
124
|
+
/** Restore and consume a signal returned by the trusted response-stage wrapper. */
|
|
125
|
+
async function restoreImageOptimizationSignalFromTransport(response, token) {
|
|
126
|
+
const marker = response.headers.get(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
|
|
127
|
+
if (marker === null || response.status < 200 || response.status > 599) return response;
|
|
128
|
+
const headers = new Headers(response.headers);
|
|
129
|
+
headers.delete(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
|
|
130
|
+
const prefix = `${token}:`;
|
|
131
|
+
const status = marker.startsWith(prefix) ? Number(marker.slice(prefix.length)) : NaN;
|
|
132
|
+
if (!Number.isInteger(status) || status < 200 || status > 599) {
|
|
133
|
+
const body = response.status === 204 || response.status === 205 || response.status === 304 ? null : response.body;
|
|
134
|
+
return new Response(body, {
|
|
135
|
+
headers,
|
|
136
|
+
status: response.status,
|
|
137
|
+
statusText: response.statusText
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const { middlewareHeaders, requestHeaders, search } = JSON.parse(await response.text()) ?? {};
|
|
141
|
+
if (!isHeaderEntries(middlewareHeaders) || requestHeaders !== null && !isHeaderEntries(requestHeaders) || typeof search !== "string") throw new Error("Invalid image optimization signal from the response stage");
|
|
142
|
+
return markImageOptimizationSignal(new Response(null, {
|
|
143
|
+
headers,
|
|
144
|
+
status
|
|
145
|
+
}), {
|
|
146
|
+
middlewareHeaders: new Headers(middlewareHeaders),
|
|
147
|
+
requestHeaders: requestHeaders ? new Headers(requestHeaders) : null,
|
|
148
|
+
search
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Whether a local source's pipeline status carries the file's bytes. A no-body
|
|
153
|
+
* success such as 204 serves no file, which Next.js rejects as an invalid
|
|
154
|
+
* image.
|
|
155
|
+
*/
|
|
156
|
+
function isImageSourceBodyStatus(status) {
|
|
157
|
+
return status >= 200 && status < 300 && status !== 204 && status !== 205;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Body Next.js answers, with a 400, for a local source that yields no image:
|
|
161
|
+
* a missing file, or anything middleware or routing answers in its place.
|
|
162
|
+
*/
|
|
163
|
+
const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
|
|
164
|
+
function invalidImageSourceResponse() {
|
|
165
|
+
return new Response(INVALID_IMAGE_SOURCE_MESSAGE, { status: 400 });
|
|
166
|
+
}
|
|
167
|
+
const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
|
|
81
168
|
/**
|
|
82
169
|
* Parse and validate image optimization query parameters.
|
|
83
170
|
* Returns null if the request is malformed.
|
|
@@ -113,7 +200,9 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
|
|
|
113
200
|
if (!normalizedUrl.startsWith("/") || normalizedUrl.startsWith("//")) return null;
|
|
114
201
|
try {
|
|
115
202
|
const base = "https://localhost";
|
|
116
|
-
|
|
203
|
+
const resolved = new URL(normalizedUrl, base);
|
|
204
|
+
if (resolved.origin !== base) return null;
|
|
205
|
+
if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
|
|
117
206
|
} catch {
|
|
118
207
|
return null;
|
|
119
208
|
}
|
|
@@ -226,7 +315,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
|
|
|
226
315
|
if (!params) return badRequestResponse();
|
|
227
316
|
const { imageUrl, width, quality } = params;
|
|
228
317
|
const source = await handlers.fetchAsset(imageUrl, request);
|
|
229
|
-
if (!source.ok || !source.body) return
|
|
318
|
+
if (!source.ok || !source.body) return invalidImageSourceResponse();
|
|
230
319
|
const format = negotiateImageFormat(request.headers.get("Accept"));
|
|
231
320
|
const sourceContentType = source.headers.get("Content-Type");
|
|
232
321
|
if (!isSafeImageContentType(sourceContentType, imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
|
|
@@ -252,7 +341,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
|
|
|
252
341
|
} catch (e) {
|
|
253
342
|
console.error("[vinext] Image fallback error, refetching source image:", e);
|
|
254
343
|
const refetchedSource = await handlers.fetchAsset(imageUrl, request);
|
|
255
|
-
if (!refetchedSource.ok || !refetchedSource.body) return
|
|
344
|
+
if (!refetchedSource.ok || !refetchedSource.body) return invalidImageSourceResponse();
|
|
256
345
|
if (!isSafeImageContentType(refetchedSource.headers.get("Content-Type"), imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
|
|
257
346
|
return createPassthroughImageResponse(refetchedSource, imageConfig);
|
|
258
347
|
}
|
|
@@ -307,4 +396,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
|
|
|
307
396
|
}, allowedWidths, imageConfig);
|
|
308
397
|
}
|
|
309
398
|
//#endregion
|
|
310
|
-
export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams, resolveDevImageRedirect, setImageOptimizer };
|
|
399
|
+
export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
|
|
@@ -112,7 +112,17 @@ export declare function pagesIsrCacheKey(pathname: string, buildId?: string, i18
|
|
|
112
112
|
* Cloudflare KV key under its 512-byte limit for long pathnames.
|
|
113
113
|
*/
|
|
114
114
|
export declare function appIsrCacheKey(pathname: string, suffix: string, buildId?: string | undefined): string;
|
|
115
|
+
/**
|
|
116
|
+
* Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
|
|
117
|
+
* `/en/[slug]`. Shells get their own namespace: a request for the literal path
|
|
118
|
+
* `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
|
|
119
|
+
* stored where every unknown `[slug]` reads its shell. The namespace is a
|
|
120
|
+
* prefix of its own rather than a part after the build ID, which can contain
|
|
121
|
+
* `:` and so could spell it.
|
|
122
|
+
*/
|
|
123
|
+
export declare function appIsrFallbackShellKey(pathname: string, suffix: "html" | "rsc", buildId?: string | undefined): string;
|
|
115
124
|
export declare function appIsrHtmlKey(pathname: string): string;
|
|
125
|
+
export declare function appIsrFallbackShellHtmlKey(pathname: string): string;
|
|
116
126
|
/**
|
|
117
127
|
* Build the ISR cache key for an RSC payload.
|
|
118
128
|
*
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -4,12 +4,26 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
|
4
4
|
import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
|
|
5
5
|
import { reportRequestError } from "./instrumentation.js";
|
|
6
6
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
7
|
-
import { fnv1a64 } from "../utils/hash.js";
|
|
8
7
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
9
8
|
import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
|
|
10
9
|
import { getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret } from "./revalidation-request.js";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
11
|
//#region src/server/isr-cache.ts
|
|
12
12
|
/**
|
|
13
|
+
* ISR (Incremental Static Regeneration) cache layer.
|
|
14
|
+
*
|
|
15
|
+
* Wraps the pluggable CacheHandler with stale-while-revalidate semantics:
|
|
16
|
+
* - Fresh hit: serve immediately
|
|
17
|
+
* - Stale hit: serve immediately + trigger background regeneration
|
|
18
|
+
* - Miss: render synchronously, cache, serve
|
|
19
|
+
*
|
|
20
|
+
* Background regeneration is deduped — only one regeneration per cache key
|
|
21
|
+
* runs at a time, preventing thundering herd on popular pages.
|
|
22
|
+
*
|
|
23
|
+
* This layer works with any CacheHandler backend (memory, Redis, KV, etc.)
|
|
24
|
+
* because it only uses the standard get/set interface.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
13
27
|
* Get a cache entry with staleness information.
|
|
14
28
|
*
|
|
15
29
|
* Returns { value, isStale: false } for fresh entries,
|
|
@@ -151,12 +165,19 @@ function buildAppPageCacheValue(html, rscData, status, renderObservation, header
|
|
|
151
165
|
function normalizeCachePathname(pathname) {
|
|
152
166
|
return pathname === "/" ? "/" : pathname.replace(/\/$/, "");
|
|
153
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Digest a request-controlled key part. It must be collision resistant: a
|
|
170
|
+
* crafted value that shared a weaker hash with another would share its entry.
|
|
171
|
+
*/
|
|
172
|
+
function digestKeyPart(value) {
|
|
173
|
+
return createHash("sha256").update(value).digest("hex");
|
|
174
|
+
}
|
|
154
175
|
function buildCacheKey(prefix, pathname, suffix) {
|
|
155
176
|
const normalized = normalizeCachePathname(pathname);
|
|
156
177
|
const suffixPart = suffix ? `:${suffix}` : "";
|
|
157
178
|
const key = `${prefix}:${normalized}${suffixPart}`;
|
|
158
179
|
if (key.length <= 200) return key;
|
|
159
|
-
return `${prefix}:__hash:${
|
|
180
|
+
return `${prefix}:__hash:${digestKeyPart(normalized)}${suffixPart}`;
|
|
160
181
|
}
|
|
161
182
|
/**
|
|
162
183
|
* Compute an ISR cache key for a given router type and pathname.
|
|
@@ -179,9 +200,23 @@ function pagesIsrCacheKey(pathname, buildId, i18nCacheVariant) {
|
|
|
179
200
|
function appIsrCacheKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
|
|
180
201
|
return buildCacheKey(buildId ? `app:v2:${buildId}` : "app:v2", pathname, suffix);
|
|
181
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
|
|
205
|
+
* `/en/[slug]`. Shells get their own namespace: a request for the literal path
|
|
206
|
+
* `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
|
|
207
|
+
* stored where every unknown `[slug]` reads its shell. The namespace is a
|
|
208
|
+
* prefix of its own rather than a part after the build ID, which can contain
|
|
209
|
+
* `:` and so could spell it.
|
|
210
|
+
*/
|
|
211
|
+
function appIsrFallbackShellKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
|
|
212
|
+
return buildCacheKey(buildId ? `app-fallback-shell:v1:${buildId}` : "app-fallback-shell:v1", pathname, suffix);
|
|
213
|
+
}
|
|
182
214
|
function appIsrHtmlKey(pathname) {
|
|
183
215
|
return appIsrCacheKey(pathname, "html");
|
|
184
216
|
}
|
|
217
|
+
function appIsrFallbackShellHtmlKey(pathname) {
|
|
218
|
+
return appIsrFallbackShellKey(pathname, "html");
|
|
219
|
+
}
|
|
185
220
|
function normalizeInterceptionContextForCacheKey(interceptionContext) {
|
|
186
221
|
return isInterceptionMatchedUrlPath(interceptionContext) ? interceptionContext : null;
|
|
187
222
|
}
|
|
@@ -200,9 +235,9 @@ function appIsrRscKey(pathname, mountedSlotsHeader, renderMode = APP_RSC_RENDER_
|
|
|
200
235
|
const normalizedMountedSlotsHeader = normalizeMountedSlotsHeader(mountedSlotsHeader);
|
|
201
236
|
const sourceVariant = interceptionContext === void 0 || interceptionContext === null ? null : normalizeInterceptionContextForCacheKey(interceptionContext);
|
|
202
237
|
const variant = [
|
|
203
|
-
sourceVariant ? `source:${
|
|
204
|
-
interceptionId ? `selector:${
|
|
205
|
-
normalizedMountedSlotsHeader ? `slots:${
|
|
238
|
+
sourceVariant ? `source:${digestKeyPart(sourceVariant)}` : null,
|
|
239
|
+
interceptionId ? `selector:${digestKeyPart(interceptionId)}` : null,
|
|
240
|
+
normalizedMountedSlotsHeader ? `slots:${digestKeyPart(normalizedMountedSlotsHeader)}` : null,
|
|
206
241
|
getRscRenderModeCacheVariant(renderMode)
|
|
207
242
|
].filter((part) => part !== null).join(":");
|
|
208
243
|
return appIsrCacheKey(pathname, variant ? `rsc:${variant}` : "rsc");
|
|
@@ -211,4 +246,4 @@ function appIsrRouteKey(pathname) {
|
|
|
211
246
|
return appIsrCacheKey(pathname, pathname !== "/" && pathname.endsWith("/") ? "route:trailing-slash" : "route");
|
|
212
247
|
}
|
|
213
248
|
//#endregion
|
|
214
|
-
export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
|
|
249
|
+
export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrFallbackShellHtmlKey, appIsrFallbackShellKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
|
|
@@ -3,6 +3,7 @@ import { closeAfterResponse, createRequestContext, runWithRequestContext } from
|
|
|
3
3
|
import { decodeMatchedParams } from "../routing/utils.js";
|
|
4
4
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
5
5
|
import { VINEXT_METADATA_ROUTE_CACHE_HEADER } from "./headers.js";
|
|
6
|
+
import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
|
|
6
7
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
7
8
|
import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
|
|
8
9
|
import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy } from "./cache-control.js";
|
|
@@ -215,7 +216,7 @@ async function writeRenderedMetadataRoute(options, route, routeKey, rendered, pr
|
|
|
215
216
|
async function runMetadataRouteRegeneration(options, route, render, routeKey, previousEntry, executionContext) {
|
|
216
217
|
const requestContext = createRequestContext({ executionContext });
|
|
217
218
|
try {
|
|
218
|
-
await runWithRequestContext(requestContext, async () => {
|
|
219
|
+
await runWithRequestContext(requestContext, () => runInWorkStoreScope(async () => {
|
|
219
220
|
ensureFetchPatch();
|
|
220
221
|
beginMetadataRouteCacheability(route);
|
|
221
222
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
@@ -225,7 +226,7 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
|
|
|
225
226
|
} finally {
|
|
226
227
|
rendered.response.body?.cancel().catch(() => {});
|
|
227
228
|
}
|
|
228
|
-
});
|
|
229
|
+
}));
|
|
229
230
|
} finally {
|
|
230
231
|
await closeAfterResponse(requestContext);
|
|
231
232
|
}
|
|
@@ -512,6 +513,12 @@ async function handleMetadataRouteRequest(options) {
|
|
|
512
513
|
decodeMatchedParams(runtimeParams);
|
|
513
514
|
match.params = runtimeParams;
|
|
514
515
|
}
|
|
516
|
+
const render = async () => {
|
|
517
|
+
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
518
|
+
return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
|
|
519
|
+
};
|
|
520
|
+
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
|
|
521
|
+
if (cached.response) return cached.response;
|
|
515
522
|
if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
|
|
516
523
|
const validationParams = { ...rawParams };
|
|
517
524
|
canonicalizeAppPageParams(validationParams);
|
|
@@ -526,12 +533,6 @@ async function handleMetadataRouteRequest(options) {
|
|
|
526
533
|
});
|
|
527
534
|
if (dynamicParamsResponse) return dynamicParamsResponse;
|
|
528
535
|
}
|
|
529
|
-
const render = async () => {
|
|
530
|
-
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
531
|
-
return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
|
|
532
|
-
};
|
|
533
|
-
const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
|
|
534
|
-
if (cached.response) return cached.response;
|
|
535
536
|
const rendered = await render();
|
|
536
537
|
await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
|
|
537
538
|
return rendered.response;
|
|
@@ -7,6 +7,8 @@ export type MiddlewareLocaleMatchContext = {
|
|
|
7
7
|
defaultLocale: string;
|
|
8
8
|
} | {
|
|
9
9
|
kind: "internal" | "literal";
|
|
10
|
+
} | {
|
|
11
|
+
kind: "next-data";
|
|
10
12
|
};
|
|
11
13
|
export declare function matchesMiddleware(pathname: string, matcher: MatcherConfig | undefined, request?: Request, i18nConfig?: NextI18nConfig | null, localeContext?: MiddlewareLocaleMatchContext): boolean;
|
|
12
14
|
/**
|
|
@@ -49,6 +49,7 @@ function matchesMiddlewarePathname(pathname, matcher, i18nConfig, localeContext)
|
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
|
|
52
|
+
if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, pattern, i18nConfig, true);
|
|
52
53
|
if (!i18nConfig) return matchPattern(pathname, pattern);
|
|
53
54
|
if (localeContext) {
|
|
54
55
|
if (localeContext.kind === "internal") return false;
|
|
@@ -57,9 +58,25 @@ function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
|
|
|
57
58
|
return matchPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
|
|
58
59
|
}
|
|
59
60
|
function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
|
|
61
|
+
if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, matcher.source, i18nConfig, matcher.locale !== false);
|
|
60
62
|
if (matcher.locale !== false) return matchMatcherPattern(pathname, matcher.source, i18nConfig, localeContext);
|
|
61
63
|
return matchPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
|
|
62
64
|
}
|
|
65
|
+
function matchNextDataPattern(pathname, source, i18nConfig, localized) {
|
|
66
|
+
if (compiledPattern(source) === UNSAFE_MATCHER_PATTERN) return true;
|
|
67
|
+
return matchPattern(pathname, nextDataMatcherSource(source, i18nConfig, localized));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The source Next.js compiles for a matcher: an optional `_next/data/<buildId>`
|
|
71
|
+
* prefix, an internal locale segment under i18n, and an optional data suffix.
|
|
72
|
+
* Next.js also accepts App Router transport suffixes, which end in `.rsc` and
|
|
73
|
+
* so can never match a `.json` data URL.
|
|
74
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
|
|
75
|
+
*/
|
|
76
|
+
function nextDataMatcherSource(source, i18nConfig, localized) {
|
|
77
|
+
const isRoot = source === "/";
|
|
78
|
+
return `/:nextData(_next/data/[^/]{1,})?${i18nConfig && localized ? `/:nextInternalLocale((?!_next/)[^/.]{1,})${isRoot ? "" : source}` : source}${isRoot ? `(${i18nConfig ? "|\\.json|" : ""}/?index|/?index\\.json)?` : "{(\\.json)}?"}`;
|
|
79
|
+
}
|
|
63
80
|
function stripLocalePrefix(pathname, i18nConfig) {
|
|
64
81
|
if (pathname === "/") return null;
|
|
65
82
|
const lowerFirstSegment = pathname.split("/")[1]?.toLowerCase();
|
|
@@ -71,13 +88,17 @@ function stripFirstPathSegment(pathname) {
|
|
|
71
88
|
}
|
|
72
89
|
function matchPattern(pathname, pattern) {
|
|
73
90
|
if (pattern === "/" && (pathname === "//" || pathname === "/?" || pathname === "/#")) return false;
|
|
91
|
+
const compiled = compiledPattern(pattern);
|
|
92
|
+
if (compiled === UNSAFE_MATCHER_PATTERN) return true;
|
|
93
|
+
return compiled.test(pathname);
|
|
94
|
+
}
|
|
95
|
+
function compiledPattern(pattern) {
|
|
74
96
|
let cached = _mwPatternCache.get(pattern);
|
|
75
97
|
if (cached === void 0) {
|
|
76
98
|
cached = compileMatcherPattern(pattern);
|
|
77
99
|
_mwPatternCache.set(pattern, cached);
|
|
78
100
|
}
|
|
79
|
-
|
|
80
|
-
return cached.test(pathname);
|
|
101
|
+
return cached;
|
|
81
102
|
}
|
|
82
103
|
function compileMatcherPattern(pattern) {
|
|
83
104
|
const result = compileMiddlewareMatcherPattern(pattern);
|
|
@@ -10,6 +10,8 @@ export type MiddlewareResult = {
|
|
|
10
10
|
redirectUrl?: string;
|
|
11
11
|
redirectStatus?: number;
|
|
12
12
|
rewriteUrl?: string;
|
|
13
|
+
/** False when a same-origin rewrite target was outside the basePath. */
|
|
14
|
+
rewriteHadBasePath?: boolean;
|
|
13
15
|
rewriteStatus?: number;
|
|
14
16
|
status?: number;
|
|
15
17
|
responseHeaders?: Headers;
|
|
@@ -41,6 +43,11 @@ type ExecuteMiddlewareOptions = {
|
|
|
41
43
|
* must derive and forward this from trusted URL normalization.
|
|
42
44
|
*/
|
|
43
45
|
isDataRequest?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The data request names a buildId other than the current build. Adapters
|
|
48
|
+
* pass it raw to middleware only under `skipProxyUrlNormalize`.
|
|
49
|
+
*/
|
|
50
|
+
isStaleDataRequest?: boolean;
|
|
44
51
|
isProxy: boolean;
|
|
45
52
|
module: MiddlewareModule;
|
|
46
53
|
normalizedPathname?: string;
|
|
@@ -63,6 +70,12 @@ type ExecuteMiddlewareOptions = {
|
|
|
63
70
|
* `new URL('/x', req.url)`.
|
|
64
71
|
*/
|
|
65
72
|
trailingSlash?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* next.config `skipTrailingSlashRedirect`. Middleware then sees requests in
|
|
75
|
+
* either slash form, so NextURL keeps each request's own trailing slash and
|
|
76
|
+
* redirect Locations keep theirs, as Next.js's `getNextPathnameInfo` does.
|
|
77
|
+
*/
|
|
78
|
+
skipTrailingSlashRedirect?: boolean;
|
|
66
79
|
};
|
|
67
80
|
type RunGeneratedMiddlewareOptions = ExecuteMiddlewareOptions & {
|
|
68
81
|
ctx?: ExecutionContextLike;
|
|
@@ -8,12 +8,14 @@ import { getRequestExecutionContext, runWithExecutionContext } from "../shims/re
|
|
|
8
8
|
import "./server-globals.js";
|
|
9
9
|
import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
|
|
10
10
|
import { NextFetchEvent, NextRequest } from "../shims/server.js";
|
|
11
|
+
import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
|
|
11
12
|
import { normalizePath } from "./normalize-path.js";
|
|
12
13
|
import { matchesMiddleware, matchesMiddlewarePathname } from "./middleware-matcher.js";
|
|
13
14
|
import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
|
|
14
15
|
import { isOpenRedirectShaped } from "./open-redirect.js";
|
|
15
16
|
import { processMiddlewareHeaders } from "./request-pipeline.js";
|
|
16
17
|
import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
18
|
+
import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
|
|
17
19
|
import { reportRequestError } from "./instrumentation.js";
|
|
18
20
|
import { isValidNavigationSignalError } from "../utils/navigation-signal.js";
|
|
19
21
|
import { frameworkTracer } from "./tracer.js";
|
|
@@ -133,7 +135,7 @@ function resolveMiddlewarePathname(request) {
|
|
|
133
135
|
return badRequestResponse();
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
|
-
function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false) {
|
|
138
|
+
function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false, skipTrailingSlashRedirect = false) {
|
|
137
139
|
const url = new URL(request.url);
|
|
138
140
|
let mwRequest = !requestBodyAlreadyIsolated && request.body && !request.bodyUsed ? request.clone() : request;
|
|
139
141
|
const requestPathname = url.pathname;
|
|
@@ -143,10 +145,11 @@ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBase
|
|
|
143
145
|
mwUrl.pathname = mwPathname;
|
|
144
146
|
mwRequest = new Request(mwUrl, mwRequest);
|
|
145
147
|
}
|
|
146
|
-
const nextConfig = basePath || i18nConfig || trailingSlash ? {
|
|
148
|
+
const nextConfig = basePath || i18nConfig || trailingSlash || skipTrailingSlashRedirect ? {
|
|
147
149
|
basePath: basePath ?? "",
|
|
148
150
|
i18n: i18nConfig ?? void 0,
|
|
149
|
-
trailingSlash: trailingSlash ?? void 0
|
|
151
|
+
trailingSlash: trailingSlash ?? void 0,
|
|
152
|
+
skipTrailingSlashRedirect
|
|
150
153
|
} : void 0;
|
|
151
154
|
const nextRequest = mwRequest instanceof NextRequest && (!mwRequest.body || mwRequest.bodyUsed) ? mwRequest : new NextRequest(mwRequest, nextConfig ? { nextConfig } : void 0);
|
|
152
155
|
if (mwRequest !== request && mwRequest.body && !mwRequest.bodyUsed && !mwRequest.body.locked) mwRequest.body.cancel().catch(() => {});
|
|
@@ -164,7 +167,10 @@ function releaseMiddlewareRequestBody(request, waitUntilPromises, retainedByResp
|
|
|
164
167
|
}
|
|
165
168
|
Promise.allSettled(waitUntilPromises).then(cancel);
|
|
166
169
|
}
|
|
167
|
-
|
|
170
|
+
function executeMiddleware(options) {
|
|
171
|
+
return runInWorkStoreScope(() => executeMiddlewareModule(options));
|
|
172
|
+
}
|
|
173
|
+
async function executeMiddlewareModule(options) {
|
|
168
174
|
const middlewareFn = resolveMiddlewareModuleHandler(options.module, {
|
|
169
175
|
filePath: options.filePath,
|
|
170
176
|
isProxy: options.isProxy
|
|
@@ -187,14 +193,17 @@ async function executeMiddleware(options) {
|
|
|
187
193
|
if (options.hadBasePath === true) return candidate;
|
|
188
194
|
return matcher === void 0 ? candidate : null;
|
|
189
195
|
};
|
|
190
|
-
const
|
|
196
|
+
const isStaleDataRequest = options.isDataRequest === true && options.isStaleDataRequest === true;
|
|
197
|
+
const toPageMatcherPathname = (candidate) => candidate !== null && options.isDataRequest && !isStaleDataRequest ? nextDataMiddlewareMatchPathname(candidate) ?? candidate : candidate;
|
|
198
|
+
const encodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(encodedRequestPathname));
|
|
191
199
|
let decodedMatchPathname = encodedMatchPathname;
|
|
192
200
|
try {
|
|
193
201
|
if (encodedMatchPathname !== null) decodedMatchPathname = decodeURIComponent(encodedMatchPathname);
|
|
194
|
-
else if (!options.i18nConfig) decodedMatchPathname = prepareMatcherPathname(decodeURIComponent(encodedRequestPathname));
|
|
202
|
+
else if (!options.i18nConfig) decodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(decodeURIComponent(encodedRequestPathname)));
|
|
195
203
|
} catch {}
|
|
196
204
|
let localeContext;
|
|
197
|
-
if (
|
|
205
|
+
if (isStaleDataRequest) localeContext = { kind: "next-data" };
|
|
206
|
+
else if (options.i18nConfig && encodedMatchPathname !== null) {
|
|
198
207
|
const hostname = normalizeHost(options.request.headers.get("host"), requestUrl.hostname);
|
|
199
208
|
const firstSegment = encodedMatchPathname.split("/", 3)[1];
|
|
200
209
|
if (firstSegment !== void 0 && options.i18nConfig.locales.some((locale) => locale.toLowerCase() === firstSegment.toLowerCase())) localeContext = { kind: "literal" };
|
|
@@ -210,7 +219,7 @@ async function executeMiddleware(options) {
|
|
|
210
219
|
const decodedMatches = !encodedMatches && decodedMatchPathname !== null && decodedMatchPathname !== encodedMatchPathname && matchesMiddleware(decodedMatchPathname, matcher, options.request, options.i18nConfig, localeContext);
|
|
211
220
|
if (!encodedMatches && !decodedMatches) return { continue: true };
|
|
212
221
|
options.onMatch?.();
|
|
213
|
-
const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated);
|
|
222
|
+
const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
|
|
214
223
|
if (options.isDataRequest) Object.defineProperty(nextRequest, "__isData", {
|
|
215
224
|
enumerable: false,
|
|
216
225
|
value: true
|
|
@@ -248,21 +257,12 @@ async function executeMiddleware(options) {
|
|
|
248
257
|
waitUntilPromises
|
|
249
258
|
};
|
|
250
259
|
}
|
|
251
|
-
if (response.headers.get("x-middleware-next") === "1") {
|
|
252
|
-
releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
|
|
253
|
-
return {
|
|
254
|
-
continue: true,
|
|
255
|
-
responseHeaders: collectMiddlewareHeaders(response),
|
|
256
|
-
status: response.status !== 200 ? response.status : void 0,
|
|
257
|
-
waitUntilPromises
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
260
|
if (response.status >= 300 && response.status < 400) {
|
|
261
261
|
const location = response.headers.get("Location") ?? response.headers.get("location");
|
|
262
262
|
if (location) {
|
|
263
263
|
const relativeLocation = relativizeLocation(location, options.request.url);
|
|
264
264
|
let normalizedLocation = relativeLocation;
|
|
265
|
-
if (options.trailingSlash !== void 0) try {
|
|
265
|
+
if (options.trailingSlash !== void 0 && !options.skipTrailingSlashRedirect) try {
|
|
266
266
|
const loc = new URL(relativeLocation, options.request.url);
|
|
267
267
|
if (loc.origin === new URL(options.request.url).origin) {
|
|
268
268
|
const p = loc.pathname;
|
|
@@ -309,11 +309,14 @@ async function executeMiddleware(options) {
|
|
|
309
309
|
const rewriteUrl = response.headers.get(MIDDLEWARE_REWRITE_HEADER);
|
|
310
310
|
if (rewriteUrl) {
|
|
311
311
|
let rewritePath;
|
|
312
|
+
let rewriteHadBasePath;
|
|
312
313
|
try {
|
|
313
314
|
const rewriteParsed = new URL(rewriteUrl, options.request.url);
|
|
314
315
|
const requestOrigin = new URL(options.request.url).origin;
|
|
315
|
-
if (rewriteParsed.origin === requestOrigin)
|
|
316
|
-
|
|
316
|
+
if (rewriteParsed.origin === requestOrigin) {
|
|
317
|
+
rewriteHadBasePath = !options.basePath || hasBasePath(rewriteParsed.pathname, options.basePath);
|
|
318
|
+
rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
|
|
319
|
+
} else rewritePath = rewriteParsed.href;
|
|
317
320
|
} catch {
|
|
318
321
|
rewritePath = rewriteUrl;
|
|
319
322
|
}
|
|
@@ -321,12 +324,22 @@ async function executeMiddleware(options) {
|
|
|
321
324
|
return {
|
|
322
325
|
continue: true,
|
|
323
326
|
rewriteUrl: rewritePath,
|
|
327
|
+
rewriteHadBasePath,
|
|
324
328
|
rewriteStatus: response.status !== 200 ? response.status : void 0,
|
|
325
329
|
responseHeaders: collectMiddlewareHeaders(response),
|
|
326
330
|
status: response.status !== 200 ? response.status : void 0,
|
|
327
331
|
waitUntilPromises
|
|
328
332
|
};
|
|
329
333
|
}
|
|
334
|
+
if (response.headers.get("x-middleware-next") === "1") {
|
|
335
|
+
releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
|
|
336
|
+
return {
|
|
337
|
+
continue: true,
|
|
338
|
+
responseHeaders: collectMiddlewareHeaders(response),
|
|
339
|
+
status: response.status !== 200 ? response.status : void 0,
|
|
340
|
+
waitUntilPromises
|
|
341
|
+
};
|
|
342
|
+
}
|
|
330
343
|
releaseMiddlewareRequestBody(nextRequest, waitUntilPromises, response.body === nextRequest.body);
|
|
331
344
|
return {
|
|
332
345
|
continue: false,
|
|
@@ -44,6 +44,6 @@ export declare function findMiddlewareFile(root: string, fileMatcher: ValidFileM
|
|
|
44
44
|
* @param request - The incoming Request object
|
|
45
45
|
* @returns Middleware result describing what action to take
|
|
46
46
|
*/
|
|
47
|
-
export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string): Promise<MiddlewareResult>;
|
|
47
|
+
export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string, isStaleDataRequest?: boolean, skipTrailingSlashRedirect?: boolean): Promise<MiddlewareResult>;
|
|
48
48
|
//#endregion
|
|
49
49
|
export { matchPattern, matchesMiddleware };
|
|
@@ -77,7 +77,7 @@ function isMiddlewareModule(value) {
|
|
|
77
77
|
* @param request - The incoming Request object
|
|
78
78
|
* @returns Middleware result describing what action to take
|
|
79
79
|
*/
|
|
80
|
-
async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname) {
|
|
80
|
+
async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname, isStaleDataRequest, skipTrailingSlashRedirect) {
|
|
81
81
|
const mod = await runner.import(middlewarePath);
|
|
82
82
|
if (!isMiddlewareModule(mod)) throw new Error(`Middleware module "${middlewarePath}" did not evaluate to an object.`);
|
|
83
83
|
return runGeneratedMiddleware({
|
|
@@ -87,10 +87,12 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
|
|
|
87
87
|
i18nConfig,
|
|
88
88
|
includeErrorDetails: process.env.NODE_ENV !== "production",
|
|
89
89
|
isDataRequest,
|
|
90
|
+
isStaleDataRequest,
|
|
90
91
|
isProxy: isProxyFile(middlewarePath),
|
|
91
92
|
module: mod,
|
|
92
93
|
normalizedPathname,
|
|
93
94
|
request,
|
|
95
|
+
skipTrailingSlashRedirect,
|
|
94
96
|
trailingSlash
|
|
95
97
|
});
|
|
96
98
|
}
|
|
@@ -29,6 +29,17 @@ export type VinextResponseStageDispatchOptions = {
|
|
|
29
29
|
* partition its lookup or stored entry by it.
|
|
30
30
|
*/
|
|
31
31
|
renderWholeDocument?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Regenerate an entry the adapter already stores for this build. Set only
|
|
34
|
+
* from a platform-invoked regeneration callback or a platform cache's
|
|
35
|
+
* conditional revalidation of a stored entry, never from anything a client
|
|
36
|
+
* can send: core then treats the path as admitted and skips
|
|
37
|
+
* request-time admission work such as a Pages `getStaticPaths()` call, as
|
|
38
|
+
* Next.js regenerates a generated path without consulting it. It is not a
|
|
39
|
+
* cache selector: a transport must not partition its lookup or stored entry
|
|
40
|
+
* by it.
|
|
41
|
+
*/
|
|
42
|
+
regeneratesStoredEntry?: boolean;
|
|
32
43
|
/**
|
|
33
44
|
* Query-free identity of a shared App page GET/HEAD dispatch, supplied only
|
|
34
45
|
* to adapters that declare `responseStageCacheIdentity: "query-free"` and
|
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
//#region src/server/normalize-path.d.ts
|
|
2
|
-
export declare function escapePathDelimiters(segment: string, escapeEncoded?: boolean): string;
|
|
3
|
-
/**
|
|
4
|
-
* Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
|
|
5
|
-
* Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
|
|
6
|
-
* like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
|
|
7
|
-
*
|
|
8
|
-
* This prevents encoded slashes from changing the path structure (e.g.
|
|
9
|
-
* /admin%2Fpanel stays as a single segment, not /admin/panel).
|
|
10
|
-
*
|
|
11
|
-
* Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
|
|
12
|
-
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
|
|
13
|
-
*/
|
|
14
|
-
export declare function decodePathParams(pathname: string): string;
|
|
15
2
|
export declare function isInterceptionMatchedUrlPath(value: string): boolean;
|
|
16
3
|
/**
|
|
17
4
|
* Path normalization utility for request handling.
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
//#region src/server/normalize-path.ts
|
|
2
|
-
/**
|
|
3
|
-
* Re-encode path delimiter characters that were decoded by decodeURIComponent.
|
|
4
|
-
* After decoding a URL segment, characters like / # ? \ need to be re-encoded
|
|
5
|
-
* so they don't change the path structure.
|
|
6
|
-
*
|
|
7
|
-
* Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
8
|
-
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
9
|
-
*/
|
|
10
|
-
const PATH_DELIMITERS_RE = /([/#?])/gi;
|
|
11
|
-
const PATH_DELIMITERS_ENCODED_RE = /([/#?]|%(2f|23|3f|5c))/gi;
|
|
12
|
-
function escapePathDelimiters(segment, escapeEncoded) {
|
|
13
|
-
return segment.replace(escapeEncoded ? PATH_DELIMITERS_ENCODED_RE : PATH_DELIMITERS_RE, (char) => encodeURIComponent(char));
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
|
|
17
|
-
* Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
|
|
18
|
-
* like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
|
|
19
|
-
*
|
|
20
|
-
* This prevents encoded slashes from changing the path structure (e.g.
|
|
21
|
-
* /admin%2Fpanel stays as a single segment, not /admin/panel).
|
|
22
|
-
*
|
|
23
|
-
* Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
|
|
24
|
-
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
|
|
25
|
-
*/
|
|
26
|
-
function decodePathParams(pathname) {
|
|
27
|
-
return pathname.split("/").map((seg) => {
|
|
28
|
-
try {
|
|
29
|
-
return escapePathDelimiters(decodeURIComponent(seg), true);
|
|
30
|
-
} catch {
|
|
31
|
-
return seg;
|
|
32
|
-
}
|
|
33
|
-
}).join("/");
|
|
34
|
-
}
|
|
35
2
|
function isInterceptionMatchedUrlPath(value) {
|
|
36
3
|
return value.startsWith("/") && !value.startsWith("//") && !value.includes("?") && !value.includes("#") && !value.includes("\\") && !value.includes("\0");
|
|
37
4
|
}
|
|
@@ -67,4 +34,4 @@ function normalizePath(pathname) {
|
|
|
67
34
|
return "/" + resolved.join("/");
|
|
68
35
|
}
|
|
69
36
|
//#endregion
|
|
70
|
-
export {
|
|
37
|
+
export { isInterceptionMatchedUrlPath, normalizePath };
|