Skip to content

Commit

Permalink
Add experimental caseSensitiveRoutes config (#50869)
Browse files Browse the repository at this point in the history
This adds an experimental `caseSensitiveRoutes` config that currently applies for `rewrites`, `redirects`, and `headers` to change the default of case-insensitive. 

x-ref: [slack thread](https://vercel.slack.com/archives/C02K2HCH5V4/p1686080359514479?thread_ts=1686077053.623389&cid=C02K2HCH5V4)
x-ref: [slack thread](https://vercel.slack.com/archives/C057RG6Q9MX/p1686078875948069?thread_ts=1686077882.133609&cid=C057RG6Q9MX)
x-ref: #21498
  • Loading branch information
ijjk committed Jun 7, 2023
1 parent 6ef7619 commit 06abd63
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ export default async function build(
varyHeader: typeof RSC_VARY_HEADER
}
skipMiddlewareUrlNormalize?: boolean
caseSensitive?: boolean
} = nextBuildSpan.traceChild('generate-routes-manifest').traceFn(() => {
const sortedRoutes = getSortedRoutes([
...pageKeys.pages,
Expand All @@ -731,6 +732,7 @@ export default async function build(
return {
version: 3,
pages404: true,
caseSensitive: !!config.experimental.caseSensitiveRoutes,
basePath: config.basePath,
redirects: redirects.map((r: any) => buildCustomRoute(r, 'redirect')),
headers: headers.map((r: any) => buildCustomRoute(r, 'header')),
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
i18n: this.nextConfig.i18n,
basePath: this.nextConfig.basePath,
rewrites: this.customRoutes.rewrites,
caseSensitive: !!this.nextConfig.experimental.caseSensitiveRoutes,
})

// Ensure parsedUrl.pathname includes locale before processing
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ const configSchema = {
craCompat: {
type: 'boolean',
},
caseSensitiveRoutes: {
type: 'boolean',
},
useDeploymentId: {
type: 'boolean',
},
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export interface NextJsWebpackConfig {
}

export interface ExperimentalConfig {
caseSensitiveRoutes?: boolean
useDeploymentId?: boolean
useDeploymentIdServerActions?: boolean
deploymentId?: string
Expand Down Expand Up @@ -663,6 +664,7 @@ export const defaultConfig: NextConfig = {
output: !!process.env.NEXT_PRIVATE_STANDALONE ? 'standalone' : undefined,
modularizeImports: undefined,
experimental: {
caseSensitiveRoutes: false,
useDeploymentId: false,
deploymentId: undefined,
useDeploymentIdServerActions: false,
Expand Down
15 changes: 13 additions & 2 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,8 @@ export default class NextNodeServer extends BaseServer {
...publicRoutes,
...staticFilesRoutes,
]
const caseSensitiveRoutes =
!!this.nextConfig.experimental.caseSensitiveRoutes

const restrictedRedirectPaths = this.nextConfig.basePath
? [`${this.nextConfig.basePath}/_next`]
Expand All @@ -1345,14 +1347,22 @@ export default class NextNodeServer extends BaseServer {
this.minimalMode || this.isRenderWorker
? []
: this.customRoutes.headers.map((rule) =>
createHeaderRoute({ rule, restrictedRedirectPaths })
createHeaderRoute({
rule,
restrictedRedirectPaths,
caseSensitive: caseSensitiveRoutes,
})
)

const redirects =
this.minimalMode || this.isRenderWorker
? []
: this.customRoutes.redirects.map((rule) =>
createRedirectRoute({ rule, restrictedRedirectPaths })
createRedirectRoute({
rule,
restrictedRedirectPaths,
caseSensitive: caseSensitiveRoutes,
})
)

const rewrites = this.generateRewrites({ restrictedRedirectPaths })
Expand Down Expand Up @@ -2044,6 +2054,7 @@ export default class NextNodeServer extends BaseServer {
type: 'rewrite',
rule: rewrite,
restrictedRedirectPaths,
caseSensitive: !!this.nextConfig.experimental.caseSensitiveRoutes,
})
return {
...rewriteRoute,
Expand Down
11 changes: 11 additions & 0 deletions packages/next/src/server/server-route-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,25 @@ export function getCustomRoute(params: {
rule: Header
type: RouteType
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): Route & Header
export function getCustomRoute(params: {
rule: Rewrite
type: RouteType
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): Route & Rewrite
export function getCustomRoute(params: {
rule: Redirect
type: RouteType
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): Route & Redirect
export function getCustomRoute(params: {
rule: Rewrite | Redirect | Header
type: RouteType
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): (Route & Rewrite) | (Route & Header) | (Route & Rewrite) {
const { rule, type, restrictedRedirectPaths } = params
const match = getPathMatch(rule.source, {
Expand All @@ -51,6 +55,7 @@ export function getCustomRoute(params: {
type === 'redirect' ? restrictedRedirectPaths : undefined
)
: undefined,
sensitive: params.caseSensitive,
})

return {
Expand All @@ -65,14 +70,17 @@ export function getCustomRoute(params: {
export const createHeaderRoute = ({
rule,
restrictedRedirectPaths,
caseSensitive,
}: {
rule: Header
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): Route => {
const headerRoute = getCustomRoute({
type: 'header',
rule,
restrictedRedirectPaths,
caseSensitive,
})
return {
match: headerRoute.match,
Expand Down Expand Up @@ -134,14 +142,17 @@ export const stringifyQuery = (req: BaseNextRequest, query: ParsedUrlQuery) => {
export const createRedirectRoute = ({
rule,
restrictedRedirectPaths,
caseSensitive,
}: {
rule: Redirect
restrictedRedirectPaths: string[]
caseSensitive: boolean
}): Route => {
const redirectRoute = getCustomRoute({
type: 'redirect',
rule,
restrictedRedirectPaths,
caseSensitive,
})
return {
internal: redirectRoute.internal,
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/server/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export function getUtils({
rewrites,
pageIsDynamic,
trailingSlash,
caseSensitive,
}: {
page: string
i18n?: NextConfig['i18n']
Expand All @@ -108,6 +109,7 @@ export function getUtils({
}
pageIsDynamic: boolean
trailingSlash?: boolean
caseSensitive: boolean
}) {
let defaultRouteRegex: ReturnType<typeof getNamedRouteRegex> | undefined
let dynamicRouteMatcher: RouteMatchFn | undefined
Expand Down Expand Up @@ -140,6 +142,7 @@ export function getUtils({
{
removeUnnamedParams: true,
strict: true,
sensitive: !!caseSensitive,
}
)
let params = matcher(parsedUrl.pathname)
Expand Down
8 changes: 7 additions & 1 deletion packages/next/src/shared/lib/router/utils/path-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ interface Options {
* to match.
*/
strict?: boolean

/**
* When true the matcher will be case-sensitive, defaults to false
*/
sensitive?: boolean
}

/**
Expand All @@ -29,7 +34,8 @@ export function getPathMatch(path: string, options?: Options) {
const keys: Key[] = []
const regexp = pathToRegexp(path, keys, {
delimiter: '/',
sensitive: false,
sensitive:
typeof options?.sensitive === 'boolean' ? options.sensitive : false,
strict: options?.strict,
})

Expand Down
3 changes: 3 additions & 0 deletions test/integration/custom-routes/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
experimental: {
caseSensitiveRoutes: true,
},
async rewrites() {
// no-rewrites comment
return {
Expand Down
39 changes: 39 additions & 0 deletions test/integration/custom-routes/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,44 @@ let appPort
let app

const runTests = (isDev = false, isTurbo = false) => {
it.each([
{
path: '/to-ANOTHER',
content: /could not be found/,
status: 404,
},
{
path: '/HELLO-world',
content: /could not be found/,
status: 404,
},
{
path: '/docs/GITHUB',
content: /could not be found/,
status: 404,
},
{
path: '/add-HEADER',
content: /could not be found/,
status: 404,
},
])(
'should honor caseSensitiveRoutes config for $path',
async ({ path, status, content }) => {
const res = await fetchViaHTTP(appPort, path, undefined, {
redirect: 'manual',
})

if (status) {
expect(res.status).toBe(status)
}

if (content) {
expect(await res.text()).toMatch(content)
}
}
)

it('should successfully rewrite a WebSocket request', async () => {
// TODO: remove once test failure has been fixed
if (isTurbo) return
Expand Down Expand Up @@ -1534,6 +1572,7 @@ const runTests = (isDev = false, isTurbo = false) => {
expect(manifest).toEqual({
version: 3,
pages404: true,
caseSensitive: true,
basePath: '',
dataRoutes: [
{
Expand Down
1 change: 1 addition & 0 deletions test/integration/dynamic-routing/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,7 @@ function runTests({ dev }) {
expect(manifest).toEqual({
version: 3,
pages404: true,
caseSensitive: false,
basePath: '',
headers: [],
rewrites: [],
Expand Down
2 changes: 1 addition & 1 deletion test/lib/next-test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export function renderViaHTTP(appPort, pathname, query, opts) {
* @param {string} pathname
* @param {Record<string, any> | string | null | undefined} [query]
* @param {import('node-fetch').RequestInit} [opts]
* @returns {Promise<Response & {buffer: any} & {headers: any}>}
* @returns {Promise<Response & {buffer: any} & {headers: Headers}>}
*/
export function fetchViaHTTP(appPort, pathname, query, opts) {
const url = query ? withQuery(pathname, query) : pathname
Expand Down

0 comments on commit 06abd63

Please sign in to comment.