vinext 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -10
- package/dist/build/client-build-config.d.ts +10 -0
- package/dist/build/client-build-config.js +18 -2
- package/dist/build/output-root-externals.d.ts +21 -0
- package/dist/build/output-root-externals.js +40 -0
- 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 +6 -4
- package/dist/build/prerender.js +1 -1
- 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 +12 -12
- package/dist/cli-dev-config.js +3 -3
- package/dist/cli.js +3 -2
- 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 +90 -88
- 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 +246 -136
- 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 +2 -1
- package/dist/init.js +153 -9
- package/dist/plugins/ast-scope.d.ts +7 -0
- package/dist/plugins/ast-scope.js +59 -3
- package/dist/plugins/ast-utils.d.ts +53 -2
- package/dist/plugins/ast-utils.js +90 -2
- package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
- package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
- package/dist/plugins/commonjs-esm-facade.js +2 -2
- package/dist/plugins/css-module-imports.js +13 -20
- package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
- package/dist/plugins/dynamic-preload-metadata.js +11 -11
- package/dist/plugins/extensionless-dynamic-import.js +2 -3
- package/dist/plugins/fonts.d.ts +1 -1
- package/dist/plugins/fonts.js +3 -3
- package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
- package/dist/plugins/ignore-dynamic-requests.js +5 -7
- package/dist/plugins/import-meta-url.js +3 -4
- package/dist/plugins/instrumentation-client.js +3 -2
- package/dist/plugins/middleware-export-validation.js +2 -3
- package/dist/plugins/middleware-server-only.d.ts +5 -4
- package/dist/plugins/middleware-server-only.js +8 -5
- package/dist/plugins/og-harfbuzz.js +2 -3
- package/dist/plugins/optimize-imports.js +3 -4
- package/dist/plugins/pages-node-externals.js +3 -4
- package/dist/plugins/remove-console.js +2 -18
- package/dist/plugins/require-condition-resolution.js +3 -3
- package/dist/plugins/require-context.js +2 -3
- 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 +265 -102
- package/dist/plugins/styled-jsx.js +2 -3
- 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.d.ts +21 -2
- package/dist/plugins/use-cache-callable.js +84 -35
- package/dist/plugins/worker-image-imports.js +30 -33
- 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 +153 -0
- package/dist/server/api-handler.d.ts +1 -0
- package/dist/server/api-handler.js +3 -2
- package/dist/server/app-action-forwarding.js +2 -2
- 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-entry.js +3 -3
- package/dist/server/app-browser-interception-context.js +1 -1
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-prefetch-response.js +2 -2
- package/dist/server/app-browser-server-action-client.js +1 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-supplemental-refresh.js +1 -1
- package/dist/server/app-middleware.d.ts +11 -0
- package/dist/server/app-middleware.js +17 -2
- package/dist/server/app-optimistic-routing.js +1 -1
- 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 +5 -4
- 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-render.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 +3 -4
- package/dist/server/app-prerender-endpoints.js +9 -6
- package/dist/server/app-render-abort-error.d.ts +19 -0
- package/dist/server/app-render-abort-error.js +31 -1
- package/dist/server/app-request-stage-dispatch.js +6 -2
- package/dist/server/app-request-stage-independent-entry.js +29 -9
- package/dist/server/app-response-stage-entry.js +11 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +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 +46 -25
- package/dist/server/app-router-entry.js +29 -10
- package/dist/server/app-rsc-cache-busting.d.ts +16 -0
- package/dist/server/app-rsc-cache-busting.js +30 -13
- 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 +89 -39
- package/dist/server/app-rsc-request-normalization.js +3 -3
- package/dist/server/app-rsc-response-stage.js +5 -3
- 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 +62 -23
- package/dist/server/app-ssr-entry.js +7 -5
- package/dist/server/app-visited-response-cache.js +2 -2
- 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/config-headers.js +1 -1
- package/dist/server/defer-until-stream-consumed.d.ts +2 -1
- package/dist/server/defer-until-stream-consumed.js +3 -1
- package/dist/server/dev-server.js +39 -28
- 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 +94 -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 +11 -10
- package/dist/server/middleware-matcher.d.ts +2 -0
- package/dist/server/middleware-matcher.js +23 -2
- package/dist/server/middleware-path-to-regexp.js +1 -1
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +38 -25
- 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/navigation-planner.js +1 -1
- package/dist/server/normalize-path.d.ts +0 -13
- package/dist/server/normalize-path.js +1 -34
- package/dist/server/open-redirect.d.ts +11 -4
- package/dist/server/open-redirect.js +11 -4
- package/dist/server/pages-api-route.d.ts +3 -2
- package/dist/server/pages-api-route.js +6 -5
- 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 +21 -3
- 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 +51 -121
- package/dist/server/pages-page-handler.d.ts +0 -6
- package/dist/server/pages-page-handler.js +189 -127
- package/dist/server/pages-page-response.d.ts +12 -6
- package/dist/server/pages-page-response.js +42 -35
- package/dist/server/pages-request-pipeline.d.ts +10 -2
- package/dist/server/pages-request-pipeline.js +41 -22
- package/dist/server/pages-request-stage-entry.d.ts +6 -0
- package/dist/server/pages-request-stage-entry.js +75 -16
- package/dist/server/pages-response-stage-entry.js +4 -0
- package/dist/server/pages-revalidate.js +3 -3
- package/dist/server/pages-router-entry.js +5 -4
- 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 +204 -74
- 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.d.ts +1 -1
- package/dist/server/rsc-stream-hints.js +52 -5
- 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 +99 -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 +16 -14
- package/dist/shims/headers.js +2 -2
- package/dist/shims/image.js +2 -1
- package/dist/shims/internal/hybrid-client-route-owner-direct.js +1 -1
- package/dist/shims/internal/hybrid-client-route-owner.js +1 -0
- package/dist/shims/internal/work-store-scope.d.ts +5 -0
- package/dist/shims/internal/work-store-scope.js +24 -0
- package/dist/shims/link-prefetch.js +5 -4
- package/dist/shims/link.js +2 -2
- package/dist/shims/navigation.js +34 -9
- package/dist/shims/request-context.d.ts +2 -0
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/router.js +21 -32
- package/dist/shims/server.d.ts +27 -4
- package/dist/shims/server.js +65 -9
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/url-utils.d.ts +9 -0
- package/dist/shims/url-utils.js +15 -4
- package/dist/utils/client-runtime-metadata.js +1 -1
- package/dist/utils/domain-locale.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 +6 -0
- package/dist/utils/plugin-rsc-version.js +41 -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 +10 -0
- package/dist/utils/react-version.js +39 -16
- package/package.json +11 -7
- package/dist/build/nitro-route-rules.d.ts +0 -47
- package/dist/build/nitro-route-rules.js +0 -79
- package/dist/plugins/cache-flight-codec.d.ts +0 -19
- package/dist/plugins/cache-flight-codec.js +0 -68
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HasCondition, NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "./next-config.js";
|
|
2
2
|
import { RequestContext, normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
|
|
3
|
+
import { RedirectDestinationQueryPart } from "./destination-params.js";
|
|
3
4
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
4
5
|
//#region src/config/config-matchers.d.ts
|
|
5
6
|
/**
|
|
@@ -75,6 +76,15 @@ export declare function applyMiddlewareRequestHeaders(middlewareHeaders: Record<
|
|
|
75
76
|
};
|
|
76
77
|
export declare function checkHasConditions(has: HasCondition[] | undefined, missing: HasCondition[] | undefined, ctx: RequestContext): boolean;
|
|
77
78
|
export declare function matchConfigPattern(pathname: string, pattern: string): Record<string, string> | null;
|
|
79
|
+
/**
|
|
80
|
+
* A matched config redirect. `destinationQuery` holds the destination query's
|
|
81
|
+
* parts, split before params were substituted, for merging the request query.
|
|
82
|
+
*/
|
|
83
|
+
export type RedirectMatch = {
|
|
84
|
+
destination: string;
|
|
85
|
+
permanent: boolean;
|
|
86
|
+
destinationQuery: RedirectDestinationQueryPart[];
|
|
87
|
+
};
|
|
78
88
|
/**
|
|
79
89
|
* Apply redirect rules from next.config.js.
|
|
80
90
|
* Returns the redirect info if a redirect was matched, or null.
|
|
@@ -110,10 +120,7 @@ export declare function matchConfigPattern(pathname: string, pattern: string): R
|
|
|
110
120
|
* an original index < N are checked via matchConfigPattern first — they are
|
|
111
121
|
* few in practice (typically zero) so this is not a hot-path concern.
|
|
112
122
|
*/
|
|
113
|
-
export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void):
|
|
114
|
-
destination: string;
|
|
115
|
-
permanent: boolean;
|
|
116
|
-
} | null;
|
|
123
|
+
export declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx: RequestContext, basePathState?: BasePathMatchState, onRuleSourceMatch?: (rule: NextRedirect) => void): RedirectMatch | null;
|
|
117
124
|
/**
|
|
118
125
|
* Apply rewrite rules from next.config.js.
|
|
119
126
|
* Returns the rewritten URL or null if no rewrite matched.
|
|
@@ -142,7 +149,8 @@ export declare function matchesRewriteSource(pathname: string, rewrite: NextRewr
|
|
|
142
149
|
* users off-site.
|
|
143
150
|
*
|
|
144
151
|
* This function collapses any leading double (or more) slashes to a single
|
|
145
|
-
* slash for non-external (relative) destinations
|
|
152
|
+
* slash for non-external (relative) destinations, including tab/newline and
|
|
153
|
+
* leading C0 control characters that browsers ignore while parsing.
|
|
146
154
|
*/
|
|
147
155
|
export declare function sanitizeDestination(dest: string): string;
|
|
148
156
|
/**
|
|
@@ -162,13 +170,20 @@ export declare function sanitizeDestination(dest: string): string;
|
|
|
162
170
|
* still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
|
|
163
171
|
* (issue #1529).
|
|
164
172
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
173
|
+
* Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
|
|
174
|
+
* `stringifyQuery`: request keys keep their order (integer-like keys first, as
|
|
175
|
+
* in a JS object), a destination key overrides the request value in place,
|
|
176
|
+
* and destination-only keys follow. Request keys and values are re-encoded;
|
|
177
|
+
* destination query text is emitted verbatim, except that, like Next.js,
|
|
178
|
+
* destination strings equal to a request key or value are re-encoded too.
|
|
179
|
+
* Pass the match's
|
|
180
|
+
* `destinationQuery` so substituted params keep their value boundaries;
|
|
181
|
+
* without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
|
|
182
|
+
* origin should not leak the original request's query).
|
|
183
|
+
*
|
|
184
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
|
|
170
185
|
*/
|
|
171
|
-
export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string): string;
|
|
186
|
+
export declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string, destinationQuery?: RedirectDestinationQueryPart[]): string;
|
|
172
187
|
/**
|
|
173
188
|
* Proxy an incoming request to an external URL and return the upstream response.
|
|
174
189
|
*
|
|
@@ -3,6 +3,8 @@ import { buildRequestHeadersFromMiddlewareResponse, getUnconsumedMiddlewareReque
|
|
|
3
3
|
import { analyzeRegexSafety } from "../utils/regex-safety.js";
|
|
4
4
|
import { normalizeHost, parseCookies, requestContextFromRequest } from "./request-context.js";
|
|
5
5
|
import { isExternalUrl } from "../utils/external-url.js";
|
|
6
|
+
import { attachRequestCfMetadata } from "../server/request-pipeline.js";
|
|
7
|
+
import { substituteDestinationParams, substituteRedirectDestinationQuery } from "./destination-params.js";
|
|
6
8
|
//#region src/config/config-matchers.ts
|
|
7
9
|
/**
|
|
8
10
|
* Cache for compiled regex patterns in matchConfigPattern.
|
|
@@ -44,14 +46,6 @@ const _compiledHeaderSourceCache = /* @__PURE__ */ new Map();
|
|
|
44
46
|
*/
|
|
45
47
|
const _compiledConditionCache = /* @__PURE__ */ new Map();
|
|
46
48
|
/**
|
|
47
|
-
* Cache for destination substitution regexes in substituteDestinationParams.
|
|
48
|
-
*
|
|
49
|
-
* The regex depends only on the set of param keys captured from the matched
|
|
50
|
-
* source pattern. Caching by sorted key list avoids recompiling a new RegExp
|
|
51
|
-
* for repeated redirect/rewrite calls that use the same param shape.
|
|
52
|
-
*/
|
|
53
|
-
const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
|
|
54
|
-
/**
|
|
55
49
|
* Generic helper for the regex compilation caches above.
|
|
56
50
|
*
|
|
57
51
|
* Each cache stores the compiled artifact (or `null` when safeRegExp rejected
|
|
@@ -303,13 +297,17 @@ function applyMiddlewareRequestHeaders(middlewareHeaders, request) {
|
|
|
303
297
|
const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders);
|
|
304
298
|
const unconsumedRequestHeaders = getUnconsumedMiddlewareRequestHeaders(middlewareHeaders);
|
|
305
299
|
for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-") && key !== "x-middleware-cache" && !unconsumedRequestHeaders.has(key)) delete middlewareHeaders[key];
|
|
306
|
-
if (nextHeaders)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
300
|
+
if (nextHeaders) {
|
|
301
|
+
const source = request;
|
|
302
|
+
request = new Request(request.url, {
|
|
303
|
+
method: request.method,
|
|
304
|
+
headers: nextHeaders,
|
|
305
|
+
body: request.body,
|
|
306
|
+
signal: request.signal,
|
|
307
|
+
duplex: request.body ? "half" : void 0
|
|
308
|
+
});
|
|
309
|
+
attachRequestCfMetadata(request, source);
|
|
310
|
+
}
|
|
313
311
|
return {
|
|
314
312
|
request,
|
|
315
313
|
postMwReqCtx: requestContextFromRequest(request)
|
|
@@ -561,13 +559,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
561
559
|
onRuleSourceMatch?.(redirect);
|
|
562
560
|
const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
|
|
563
561
|
if (!conditionParams) continue;
|
|
564
|
-
localeMatch = {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}),
|
|
569
|
-
permanent: redirect.permanent
|
|
570
|
-
};
|
|
562
|
+
localeMatch = resolveRedirectMatch(redirect, {
|
|
563
|
+
[entry.paramName]: "",
|
|
564
|
+
...conditionParams
|
|
565
|
+
});
|
|
571
566
|
localeMatchIndex = entry.originalIndex;
|
|
572
567
|
break;
|
|
573
568
|
}
|
|
@@ -584,13 +579,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
584
579
|
onRuleSourceMatch?.(redirect);
|
|
585
580
|
const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
|
|
586
581
|
if (!conditionParams) continue;
|
|
587
|
-
localeMatch = {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}),
|
|
592
|
-
permanent: redirect.permanent
|
|
593
|
-
};
|
|
582
|
+
localeMatch = resolveRedirectMatch(redirect, {
|
|
583
|
+
[entry.paramName]: localePart,
|
|
584
|
+
...conditionParams
|
|
585
|
+
});
|
|
594
586
|
localeMatchIndex = entry.originalIndex;
|
|
595
587
|
break;
|
|
596
588
|
}
|
|
@@ -604,13 +596,10 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
|
|
|
604
596
|
onRuleSourceMatch?.(redirect);
|
|
605
597
|
const conditionParams = redirect.has || redirect.missing ? collectConditionParams(redirect.has, redirect.missing, ctx) : _emptyParams();
|
|
606
598
|
if (!conditionParams) continue;
|
|
607
|
-
return {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}),
|
|
612
|
-
permanent: redirect.permanent
|
|
613
|
-
};
|
|
599
|
+
return resolveRedirectMatch(redirect, {
|
|
600
|
+
...params,
|
|
601
|
+
...conditionParams
|
|
602
|
+
});
|
|
614
603
|
}
|
|
615
604
|
}
|
|
616
605
|
return localeMatch;
|
|
@@ -636,12 +625,24 @@ function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT
|
|
|
636
625
|
...params,
|
|
637
626
|
...conditionParams
|
|
638
627
|
};
|
|
639
|
-
return substituteAndSanitizeRewriteDestination(rewrite.destination, rewriteParams);
|
|
628
|
+
return substituteAndSanitizeRewriteDestination(serializeRewriteDestinationTemplate(rewrite.destination), rewriteParams);
|
|
640
629
|
}
|
|
641
630
|
}
|
|
642
631
|
return null;
|
|
643
632
|
}
|
|
644
633
|
/**
|
|
634
|
+
* Next.js prepareDestination URL-parses the destination template before it
|
|
635
|
+
* substitutes params, so a literal `é` routes as `%C3%A9` while substituted
|
|
636
|
+
* values stay verbatim: a captured `../admin` is never resolved to `/admin`.
|
|
637
|
+
*
|
|
638
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
639
|
+
*/
|
|
640
|
+
function serializeRewriteDestinationTemplate(destination) {
|
|
641
|
+
if (!destination.startsWith("/") || destination.startsWith("//")) return destination;
|
|
642
|
+
const parsed = new URL(destination, "http://n");
|
|
643
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
645
646
|
* Check whether a rewrite source can match a pathname without evaluating its
|
|
646
647
|
* request-dependent `has` / `missing` conditions.
|
|
647
648
|
*
|
|
@@ -654,47 +655,20 @@ function matchesRewriteSource(pathname, rewrite, basePathState = _BASEPATH_DEFAU
|
|
|
654
655
|
return shouldEvaluateRule(rewrite.basePath, basePathState) && matchConfigPattern(pathname, rewrite.source) !== null;
|
|
655
656
|
}
|
|
656
657
|
/**
|
|
657
|
-
* Substitute all matched route params into a redirect/rewrite destination.
|
|
658
|
-
*
|
|
659
|
-
* Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
|
|
660
|
-
* (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
|
|
661
|
-
*/
|
|
662
|
-
function substituteDestinationParams(destination, params) {
|
|
663
|
-
const keys = Object.keys(params);
|
|
664
|
-
if (keys.length === 0) return destination;
|
|
665
|
-
const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
|
|
666
|
-
const cacheKey = sortedKeys.join("\0");
|
|
667
|
-
let paramRe = _compiledDestinationParamCache.get(cacheKey);
|
|
668
|
-
if (!paramRe) {
|
|
669
|
-
const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
670
|
-
paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
|
|
671
|
-
_compiledDestinationParamCache.set(cacheKey, paramRe);
|
|
672
|
-
}
|
|
673
|
-
const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
|
|
674
|
-
const hashIndex = destination.indexOf("#");
|
|
675
|
-
const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
|
|
676
|
-
const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
|
|
677
|
-
const queryIndex = beforeHash.indexOf("?");
|
|
678
|
-
if (queryIndex !== -1) {
|
|
679
|
-
const beforeQuery = beforeHash.slice(0, queryIndex);
|
|
680
|
-
const query = beforeHash.slice(queryIndex + 1);
|
|
681
|
-
return `${replaceParams(beforeQuery, (value) => value)}?${replaceParams(query, encodeDestinationQueryParamValue)}${replaceParams(hash, (value) => value)}`;
|
|
682
|
-
}
|
|
683
|
-
return replaceParams(destination, (value) => value);
|
|
684
|
-
}
|
|
685
|
-
function encodeDestinationQueryParamValue(value) {
|
|
686
|
-
const params = new URLSearchParams();
|
|
687
|
-
params.set("", value);
|
|
688
|
-
return params.toString().slice(1);
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
658
|
* Substitute params into a redirect/rewrite destination and sanitize the
|
|
692
659
|
* result. Used by every redirect/rewrite branch — the substitution can
|
|
693
660
|
* introduce protocol-relative URLs (e.g. `//evil.com` from a decoded `%2F`
|
|
694
661
|
* in a catch-all param), which sanitizeDestination collapses.
|
|
695
662
|
*/
|
|
696
|
-
function substituteAndSanitizeDestination(destination, params) {
|
|
697
|
-
return sanitizeDestination(substituteDestinationParams(destination, params));
|
|
663
|
+
function substituteAndSanitizeDestination(destination, params, kind) {
|
|
664
|
+
return sanitizeDestination(substituteDestinationParams(destination, params, kind));
|
|
665
|
+
}
|
|
666
|
+
function resolveRedirectMatch(redirect, params) {
|
|
667
|
+
return {
|
|
668
|
+
destination: substituteAndSanitizeDestination(redirect.destination, params, "redirect"),
|
|
669
|
+
permanent: redirect.permanent,
|
|
670
|
+
destinationQuery: substituteRedirectDestinationQuery(redirect.destination, params)
|
|
671
|
+
};
|
|
698
672
|
}
|
|
699
673
|
/**
|
|
700
674
|
* Match Next.js's rewrite-specific prepareDestination behavior: source params
|
|
@@ -704,7 +678,7 @@ function substituteAndSanitizeDestination(destination, params) {
|
|
|
704
678
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
705
679
|
*/
|
|
706
680
|
function substituteAndSanitizeRewriteDestination(destination, params) {
|
|
707
|
-
const rewritten = substituteAndSanitizeDestination(destination, params);
|
|
681
|
+
const rewritten = substituteAndSanitizeDestination(destination, params, "rewrite");
|
|
708
682
|
if (!shouldAppendRewriteParamsToQuery(destination, params)) return rewritten;
|
|
709
683
|
const existingQueryKeys = getDestinationQueryKeys(destination);
|
|
710
684
|
const paramsToAppend = [];
|
|
@@ -771,11 +745,12 @@ function appendQueryParams(url, params) {
|
|
|
771
745
|
* users off-site.
|
|
772
746
|
*
|
|
773
747
|
* This function collapses any leading double (or more) slashes to a single
|
|
774
|
-
* slash for non-external (relative) destinations
|
|
748
|
+
* slash for non-external (relative) destinations, including tab/newline and
|
|
749
|
+
* leading C0 control characters that browsers ignore while parsing.
|
|
775
750
|
*/
|
|
776
751
|
function sanitizeDestination(dest) {
|
|
777
752
|
if (dest.startsWith("http://") || dest.startsWith("https://")) return dest;
|
|
778
|
-
dest = dest.replace(/^[\\/]+/, "/");
|
|
753
|
+
dest = dest.replace(/^[\u0000-\u0020]*(?:[\\/][\t\n\r]*)+/, "/");
|
|
779
754
|
return dest;
|
|
780
755
|
}
|
|
781
756
|
/**
|
|
@@ -795,13 +770,20 @@ function sanitizeDestination(dest) {
|
|
|
795
770
|
* still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
|
|
796
771
|
* (issue #1529).
|
|
797
772
|
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
773
|
+
* Ported from Next.js's `{ ...requestQuery, ...destinationQuery }` merge and
|
|
774
|
+
* `stringifyQuery`: request keys keep their order (integer-like keys first, as
|
|
775
|
+
* in a JS object), a destination key overrides the request value in place,
|
|
776
|
+
* and destination-only keys follow. Request keys and values are re-encoded;
|
|
777
|
+
* destination query text is emitted verbatim, except that, like Next.js,
|
|
778
|
+
* destination strings equal to a request key or value are re-encoded too.
|
|
779
|
+
* Pass the match's
|
|
780
|
+
* `destinationQuery` so substituted params keep their value boundaries;
|
|
781
|
+
* without it the destination's own query is split. External destinations are returned untouched (a config redirect to another
|
|
782
|
+
* origin should not leak the original request's query).
|
|
783
|
+
*
|
|
784
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
|
|
803
785
|
*/
|
|
804
|
-
function preserveRedirectDestinationQuery(destination, requestSearch) {
|
|
786
|
+
function preserveRedirectDestinationQuery(destination, requestSearch, destinationQuery) {
|
|
805
787
|
if (requestSearch === "" || requestSearch === "?" || isExternalUrl(destination)) return destination;
|
|
806
788
|
const requestParams = new URLSearchParams(requestSearch);
|
|
807
789
|
if ([...requestParams.keys()].length === 0) return destination;
|
|
@@ -810,13 +792,29 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
|
|
|
810
792
|
const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
|
|
811
793
|
const queryIndex = beforeHash.indexOf("?");
|
|
812
794
|
const pathPart = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
795
|
+
const requestStrings = /* @__PURE__ */ new Set();
|
|
796
|
+
const requestParts = Object.create(null);
|
|
797
|
+
for (const [key, value] of requestParams) {
|
|
798
|
+
requestStrings.add(key);
|
|
799
|
+
requestStrings.add(value);
|
|
800
|
+
(requestParts[key] ??= []).push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
801
|
+
}
|
|
802
|
+
const encode = (text) => requestStrings.has(text) ? encodeURIComponent(text) : text;
|
|
803
|
+
const destinationParts = Object.create(null);
|
|
804
|
+
for (const [key, keyText, valueText] of destinationQuery ?? substituteRedirectDestinationQuery(destination, {})) (destinationParts[key] ??= []).push(valueText === null ? encode(keyText) : `${encode(keyText)}=${encode(valueText)}`);
|
|
805
|
+
const mergedQuery = Object.values({
|
|
806
|
+
...requestParts,
|
|
807
|
+
...destinationParts
|
|
808
|
+
}).flat().join("&");
|
|
818
809
|
return mergedQuery === "" ? `${pathPart}${hash}` : `${pathPart}?${mergedQuery}${hash}`;
|
|
819
810
|
}
|
|
811
|
+
function parseExternalRewriteUrl(externalUrl) {
|
|
812
|
+
try {
|
|
813
|
+
return new URL(externalUrl);
|
|
814
|
+
} catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
820
818
|
/**
|
|
821
819
|
* Proxy an incoming request to an external URL and return the upstream response.
|
|
822
820
|
*
|
|
@@ -828,7 +826,11 @@ function preserveRedirectDestinationQuery(destination, requestSearch) {
|
|
|
828
826
|
*/
|
|
829
827
|
async function proxyExternalRequest(request, externalUrl) {
|
|
830
828
|
const originalUrl = new URL(request.url);
|
|
831
|
-
const targetUrl =
|
|
829
|
+
const targetUrl = parseExternalRewriteUrl(externalUrl);
|
|
830
|
+
if (!targetUrl) {
|
|
831
|
+
console.error("[vinext] Invalid external rewrite destination:", externalUrl);
|
|
832
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
833
|
+
}
|
|
832
834
|
const destinationKeys = new Set(targetUrl.searchParams.keys());
|
|
833
835
|
for (const [key, value] of originalUrl.searchParams) if (!destinationKeys.has(key)) targetUrl.searchParams.append(key, value);
|
|
834
836
|
const headers = new Headers(request.headers);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/config/destination-params.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Destination param substitution for next.config.js redirects and rewrites.
|
|
4
|
+
*
|
|
5
|
+
* Kept free of server-only imports so the Pages Router client can apply simple
|
|
6
|
+
* beforeFiles rewrites with exactly the same substitution as the server.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Substitute all matched route params into a redirect/rewrite destination.
|
|
10
|
+
*
|
|
11
|
+
* Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
|
|
12
|
+
* (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
|
|
13
|
+
*
|
|
14
|
+
* Query substitution follows how Next.js consumes each destination kind:
|
|
15
|
+
*
|
|
16
|
+
* - A rewrite's query is parsed into an object before substitution, so the
|
|
17
|
+
* target sees each param verbatim as one value. vinext encodes the whole
|
|
18
|
+
* value so it round-trips back to that verbatim string.
|
|
19
|
+
* - A redirect's query is emitted as Location text with params inserted
|
|
20
|
+
* verbatim, so an already percent-encoded capture is decoded once by the
|
|
21
|
+
* client. vinext inserts redirect params verbatim too.
|
|
22
|
+
*
|
|
23
|
+
* A rewrite's absolute destination is proxied server-side, so params in its
|
|
24
|
+
* authority (`https://:tenant.example.com/`) are percent-encoded, keeping the
|
|
25
|
+
* `%XX` escapes a raw path capture already carries. A decoded `has` capture
|
|
26
|
+
* carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
|
|
27
|
+
* moving the proxy to another host. Next.js substitutes the hostname verbatim
|
|
28
|
+
* and can be retargeted this way; vinext is stricter.
|
|
29
|
+
*
|
|
30
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
31
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
|
|
32
|
+
*/
|
|
33
|
+
export declare function substituteDestinationParams(destination: string, params: Record<string, string>, kind: "redirect" | "rewrite"): string;
|
|
34
|
+
/**
|
|
35
|
+
* Split a redirect destination's query into parts before substituting params,
|
|
36
|
+
* as Next.js's prepareDestination parses the template query first. Each part
|
|
37
|
+
* is `[key, keyText, valueText]`: the texts are the part's template text with
|
|
38
|
+
* params substituted verbatim (`valueText` is `null` without an `=`), and
|
|
39
|
+
* `key` is the decoded key, used only to match request params. Keeping these
|
|
40
|
+
* boundaries means a param containing `&` stays one value when the request
|
|
41
|
+
* query is merged in.
|
|
42
|
+
*
|
|
43
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
44
|
+
*/
|
|
45
|
+
export declare function substituteRedirectDestinationQuery(destination: string, params: Record<string, string>): RedirectDestinationQueryPart[];
|
|
46
|
+
export type RedirectDestinationQueryPart = [key: string, keyText: string, valueText: string | null];
|
|
47
|
+
//#endregion
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region src/config/destination-params.ts
|
|
2
|
+
/**
|
|
3
|
+
* Destination param substitution for next.config.js redirects and rewrites.
|
|
4
|
+
*
|
|
5
|
+
* Kept free of server-only imports so the Pages Router client can apply simple
|
|
6
|
+
* beforeFiles rewrites with exactly the same substitution as the server.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Cache for destination substitution regexes in substituteDestinationParams.
|
|
10
|
+
*
|
|
11
|
+
* The regex depends only on the set of param keys captured from the matched
|
|
12
|
+
* source pattern. Caching by sorted key list avoids recompiling a new RegExp
|
|
13
|
+
* for repeated redirect/rewrite calls that use the same param shape.
|
|
14
|
+
*/
|
|
15
|
+
const _compiledDestinationParamCache = /* @__PURE__ */ new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Substitute all matched route params into a redirect/rewrite destination.
|
|
18
|
+
*
|
|
19
|
+
* Handles repeated params (e.g. `/api/:id/:id`) and catch-all suffix forms
|
|
20
|
+
* (`:path*`, `:path+`) in a single pass. Unknown params are left intact.
|
|
21
|
+
*
|
|
22
|
+
* Query substitution follows how Next.js consumes each destination kind:
|
|
23
|
+
*
|
|
24
|
+
* - A rewrite's query is parsed into an object before substitution, so the
|
|
25
|
+
* target sees each param verbatim as one value. vinext encodes the whole
|
|
26
|
+
* value so it round-trips back to that verbatim string.
|
|
27
|
+
* - A redirect's query is emitted as Location text with params inserted
|
|
28
|
+
* verbatim, so an already percent-encoded capture is decoded once by the
|
|
29
|
+
* client. vinext inserts redirect params verbatim too.
|
|
30
|
+
*
|
|
31
|
+
* A rewrite's absolute destination is proxied server-side, so params in its
|
|
32
|
+
* authority (`https://:tenant.example.com/`) are percent-encoded, keeping the
|
|
33
|
+
* `%XX` escapes a raw path capture already carries. A decoded `has` capture
|
|
34
|
+
* carrying `#`, `/`, `?`, `@` or `:` then makes the host invalid instead of
|
|
35
|
+
* moving the proxy to another host. Next.js substitutes the hostname verbatim
|
|
36
|
+
* and can be retargeted this way; vinext is stricter.
|
|
37
|
+
*
|
|
38
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
39
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/server-route-utils.ts
|
|
40
|
+
*/
|
|
41
|
+
function substituteDestinationParams(destination, params, kind) {
|
|
42
|
+
const paramRe = getDestinationParamRegex(params);
|
|
43
|
+
if (!paramRe) return destination;
|
|
44
|
+
const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
|
|
45
|
+
if (kind === "redirect") return replaceParams(destination, (value) => value);
|
|
46
|
+
const hashIndex = destination.indexOf("#");
|
|
47
|
+
const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
|
|
48
|
+
const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
|
|
49
|
+
const queryIndex = beforeHash.indexOf("?");
|
|
50
|
+
const beforeQuery = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
|
|
51
|
+
const authorityEnd = getAbsoluteUrlAuthorityEnd(beforeQuery.replace(paramRe, (token) => "x".repeat(token.length)));
|
|
52
|
+
return `${`${replaceParams(beforeQuery.slice(0, authorityEnd), encodeAuthorityParam)}${replaceParams(beforeQuery.slice(authorityEnd), (value) => value)}`}${queryIndex === -1 ? "" : `?${replaceParams(beforeHash.slice(queryIndex + 1), encodeRewriteQueryParamValue)}`}${replaceParams(hash, (value) => value)}`;
|
|
53
|
+
}
|
|
54
|
+
function getAbsoluteUrlAuthorityEnd(value) {
|
|
55
|
+
const scheme = /^[a-z][a-z0-9+.-]*:[/\\\t\n\r]*/i.exec(value);
|
|
56
|
+
if (!scheme && /^[/\\]/.test(value)) return 0;
|
|
57
|
+
const start = scheme ? scheme[0].length : 0;
|
|
58
|
+
const pathStart = value.slice(start).search(/[/\\]/);
|
|
59
|
+
return pathStart === -1 ? value.length : start + pathStart;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Split a redirect destination's query into parts before substituting params,
|
|
63
|
+
* as Next.js's prepareDestination parses the template query first. Each part
|
|
64
|
+
* is `[key, keyText, valueText]`: the texts are the part's template text with
|
|
65
|
+
* params substituted verbatim (`valueText` is `null` without an `=`), and
|
|
66
|
+
* `key` is the decoded key, used only to match request params. Keeping these
|
|
67
|
+
* boundaries means a param containing `&` stays one value when the request
|
|
68
|
+
* query is merged in.
|
|
69
|
+
*
|
|
70
|
+
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
|
|
71
|
+
*/
|
|
72
|
+
function substituteRedirectDestinationQuery(destination, params) {
|
|
73
|
+
const hashIndex = destination.indexOf("#");
|
|
74
|
+
const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
|
|
75
|
+
const queryIndex = beforeHash.indexOf("?");
|
|
76
|
+
if (queryIndex === -1) return [];
|
|
77
|
+
const paramRe = getDestinationParamRegex(params);
|
|
78
|
+
const substitute = (value) => paramRe ? value.replace(paramRe, (_token, key) => params[key]) : value;
|
|
79
|
+
return beforeHash.slice(queryIndex + 1).split("&").filter(Boolean).map((part) => {
|
|
80
|
+
const equalsIndex = part.indexOf("=");
|
|
81
|
+
const keyText = substitute(equalsIndex === -1 ? part : part.slice(0, equalsIndex));
|
|
82
|
+
const valueText = equalsIndex === -1 ? null : substitute(part.slice(equalsIndex + 1));
|
|
83
|
+
return [
|
|
84
|
+
decodeQueryKey(keyText),
|
|
85
|
+
keyText,
|
|
86
|
+
valueText
|
|
87
|
+
];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function decodeQueryKey(keyText) {
|
|
91
|
+
const [entry] = new URLSearchParams(keyText.replace(/[&=]/g, (char) => encodeURIComponent(char)));
|
|
92
|
+
return entry?.[0] ?? "";
|
|
93
|
+
}
|
|
94
|
+
function getDestinationParamRegex(params) {
|
|
95
|
+
const keys = Object.keys(params);
|
|
96
|
+
if (keys.length === 0) return null;
|
|
97
|
+
const sortedKeys = [...keys].sort((a, b) => b.length - a.length);
|
|
98
|
+
const cacheKey = sortedKeys.join("\0");
|
|
99
|
+
let paramRe = _compiledDestinationParamCache.get(cacheKey);
|
|
100
|
+
if (!paramRe) {
|
|
101
|
+
const paramAlternation = sortedKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
102
|
+
paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
|
|
103
|
+
_compiledDestinationParamCache.set(cacheKey, paramRe);
|
|
104
|
+
}
|
|
105
|
+
return paramRe;
|
|
106
|
+
}
|
|
107
|
+
function encodeAuthorityParam(value) {
|
|
108
|
+
return value.replace(/%(?![0-9a-f]{2})|[^A-Za-z0-9._~%-]/giu, encodeURIComponent);
|
|
109
|
+
}
|
|
110
|
+
function encodeRewriteQueryParamValue(value) {
|
|
111
|
+
const params = new URLSearchParams();
|
|
112
|
+
params.set("", value);
|
|
113
|
+
return params.toString().slice(1);
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { substituteDestinationParams, substituteRedirectDestinationQuery };
|
|
@@ -111,6 +111,12 @@ export type NextConfig = {
|
|
|
111
111
|
crossOrigin?: "anonymous" | "use-credentials";
|
|
112
112
|
/** Whether to add trailing slashes */
|
|
113
113
|
trailingSlash?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Skip the automatic trailing-slash redirects and keep `<Link>`/router
|
|
116
|
+
* hrefs exactly as written.
|
|
117
|
+
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/skipTrailingSlashRedirect
|
|
118
|
+
*/
|
|
119
|
+
skipTrailingSlashRedirect?: boolean;
|
|
114
120
|
/**
|
|
115
121
|
* Compress responses from the Node production server. Defaults to true.
|
|
116
122
|
* @see https://nextjs.org/docs/app/api-reference/config/next-config-js/compress
|
|
@@ -317,6 +323,7 @@ export type ResolvedNextConfig = {
|
|
|
317
323
|
*/
|
|
318
324
|
assetPrefix: string;
|
|
319
325
|
trailingSlash: boolean;
|
|
326
|
+
skipTrailingSlashRedirect: boolean;
|
|
320
327
|
/** Whether the Node production server compresses responses (`compress`, default true). */
|
|
321
328
|
compress: boolean;
|
|
322
329
|
skipProxyUrlNormalize: boolean;
|
|
@@ -373,6 +373,7 @@ function warnDeprecatedConfigOptions(config, root) {
|
|
|
373
373
|
["experimental.instrumentationHook", `\`experimental.instrumentationHook\` is no longer needed, because \`instrumentation.js\` is available by default. You can remove it from ${configFileName}.`]
|
|
374
374
|
];
|
|
375
375
|
for (const [propertyPath, warning] of warnings) if (hasConfigProperty(config, propertyPath)) warnConfigOnce(warning);
|
|
376
|
+
if (readOptionalRecord(config.experimental)?.skipTrailingSlashRedirect != null) warnConfigOnce(`\`experimental.skipTrailingSlashRedirect\` has been moved to \`skipTrailingSlashRedirect\`. Please update your ${configFileName} file accordingly.`);
|
|
376
377
|
}
|
|
377
378
|
async function resolveNextConfigInput(config, phase = PHASE_DEVELOPMENT_SERVER) {
|
|
378
379
|
return await resolveConfigValue(config, phase);
|
|
@@ -761,6 +762,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
761
762
|
basePath: "",
|
|
762
763
|
assetPrefix: "",
|
|
763
764
|
trailingSlash: false,
|
|
765
|
+
skipTrailingSlashRedirect: false,
|
|
764
766
|
compress: true,
|
|
765
767
|
skipProxyUrlNormalize: false,
|
|
766
768
|
typescript: {},
|
|
@@ -947,6 +949,7 @@ async function resolveNextConfig(config, root = toSlash(process.cwd()), options
|
|
|
947
949
|
basePath: config.basePath ?? "",
|
|
948
950
|
assetPrefix: normalizeAssetPrefix(config.assetPrefix),
|
|
949
951
|
trailingSlash: config.trailingSlash ?? false,
|
|
952
|
+
skipTrailingSlashRedirect: (experimental?.skipTrailingSlashRedirect ?? config.skipTrailingSlashRedirect) === true,
|
|
950
953
|
compress: config.compress !== false,
|
|
951
954
|
skipProxyUrlNormalize: config.skipProxyUrlNormalize ?? config.skipMiddlewareUrlNormalize ?? false,
|
|
952
955
|
typescript: typeof config.typescript?.tsconfigPath === "string" ? { tsconfigPath: config.typescript.tsconfigPath } : {},
|
|
@@ -93,6 +93,10 @@ type AppRouterConfig = {
|
|
|
93
93
|
draftModeSecret?: string;
|
|
94
94
|
/** Server-only token used to authorize build-time prerender endpoints. */
|
|
95
95
|
prerenderSecret?: string;
|
|
96
|
+
/** next.config `skipTrailingSlashRedirect`. */
|
|
97
|
+
skipTrailingSlashRedirect?: boolean;
|
|
98
|
+
/** Dev-only key authenticating the hybrid Pages handler's forwarded middleware context. */
|
|
99
|
+
devForwardedMiddlewareContextKey?: string;
|
|
96
100
|
};
|
|
97
101
|
/** Generate the module-free App request stage used by multi-stage Worker outputs. */
|
|
98
102
|
export declare function generateAppRequestRscEntry(appDir: string, routes: AppRoute[], middlewarePath?: string | null, metadataRoutes?: MetadataFileRoute[], _globalErrorPath?: string | null, basePath?: string, trailingSlash?: boolean, config?: AppRouterConfig, instrumentationPath?: string | null): string;
|