Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental caseSensitiveRoutes config #50869

Merged
merged 4 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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