From 3e5568f95a8f6eb2946f5fa34a34a3d45bc24456 Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Thu, 16 Jun 2022 11:53:00 +0200 Subject: [PATCH 1/4] Bugfix: use `trailingSlash` correctly when formatting a pathname --- packages/next/server/web/next-url.ts | 3 ++ .../router/utils/format-next-pathname-info.ts | 9 ++++-- test/unit/web-runtime/next-url.test.ts | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/next/server/web/next-url.ts b/packages/next/server/web/next-url.ts index d559db5923392..e1f515bfb2e14 100644 --- a/packages/next/server/web/next-url.ts +++ b/packages/next/server/web/next-url.ts @@ -25,6 +25,7 @@ export class NextURL { domainLocale?: DomainLocale locale?: string options: Options + trailingSlash?: boolean url: URL } @@ -77,6 +78,7 @@ export class NextURL { this[Internal].basePath = pathnameInfo.basePath ?? '' this[Internal].buildId = pathnameInfo.buildId this[Internal].locale = pathnameInfo.locale ?? defaultLocale + this[Internal].trailingSlash = pathnameInfo.trailingSlash } private formatPathname() { @@ -88,6 +90,7 @@ export class NextURL { : undefined, locale: this[Internal].locale, pathname: this[Internal].url.pathname, + trailingSlash: this[Internal].trailingSlash, }) } diff --git a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts index 88cfee77ea692..a5aeed500ad77 100644 --- a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts +++ b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts @@ -1,4 +1,5 @@ import type { NextPathnameInfo } from './get-next-pathname-info' +import { removeTrailingSlash } from './remove-trailing-slash' import { addPathPrefix } from './add-path-prefix' import { addPathSuffix } from './add-path-suffix' import { addLocale } from './add-locale' @@ -22,7 +23,9 @@ export function formatNextPathnameInfo(info: ExtendedInfo) { } pathname = addPathPrefix(pathname, info.basePath) - return info.trailingSlash && !info.buildId && !pathname.endsWith('/') - ? addPathSuffix(pathname, '/') - : pathname + return info.trailingSlash + ? !info.buildId && !pathname.endsWith('/') + ? addPathSuffix(pathname, '/') + : pathname + : removeTrailingSlash(pathname) } diff --git a/test/unit/web-runtime/next-url.test.ts b/test/unit/web-runtime/next-url.test.ts index 969347902bc24..582466a38d072 100644 --- a/test/unit/web-runtime/next-url.test.ts +++ b/test/unit/web-runtime/next-url.test.ts @@ -337,6 +337,37 @@ it('preserves the trailingSlash', async () => { expect(String(url)).toEqual('http://localhost:3000/es/') }) +it('formats correctly the trailingSlash for root pages', async () => { + const url = new NextURL('/', { + base: 'http://127.0.0.1:3000', + nextConfig: { + trailingSlash: true, + i18n: { + defaultLocale: 'en', + locales: ['en', 'es', 'fr'], + }, + }, + }) + + url.locale = 'es' + expect(String(url)).toEqual('http://localhost:3000/es/') +}) + +it('keeps the trailingSlash format for non root pages', async () => { + const url = new NextURL('/es', { + base: 'http://127.0.0.1:3000', + nextConfig: { + trailingSlash: true, + i18n: { + defaultLocale: 'en', + locales: ['en', 'es', 'fr'], + }, + }, + }) + + expect(String(url)).toEqual('http://localhost:3000/es') +}) + it('allows to preserve a json request', async () => { const url = new NextURL( 'http://localhost:3000/_next/static/development/_devMiddlewareManifest.json', From f5f68bd1bfc9ee39440e1b3595c047911367875f Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Thu, 16 Jun 2022 13:09:48 +0200 Subject: [PATCH 2/4] Allow to ignore prefix on `addLocale` --- packages/next/shared/lib/router/utils/add-locale.ts | 8 +++++--- .../shared/lib/router/utils/format-next-pathname-info.ts | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/next/shared/lib/router/utils/add-locale.ts b/packages/next/shared/lib/router/utils/add-locale.ts index 8a9961a71b790..1365d468cff90 100644 --- a/packages/next/shared/lib/router/utils/add-locale.ts +++ b/packages/next/shared/lib/router/utils/add-locale.ts @@ -9,13 +9,15 @@ import { pathHasPrefix } from './path-has-prefix' export function addLocale( path: string, locale?: string | false, - defaultLocale?: string + defaultLocale?: string, + ignorePrefix?: boolean ) { if ( locale && locale !== defaultLocale && - !pathHasPrefix(path.toLowerCase(), `/${locale.toLowerCase()}`) && - !pathHasPrefix(path.toLowerCase(), '/api') + (ignorePrefix || + (!pathHasPrefix(path.toLowerCase(), `/${locale.toLowerCase()}`) && + !pathHasPrefix(path.toLowerCase(), '/api'))) ) { return addPathPrefix(path, `/${locale}`) } diff --git a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts index a5aeed500ad77..784bd8adc8d0e 100644 --- a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts +++ b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts @@ -6,13 +6,15 @@ import { addLocale } from './add-locale' interface ExtendedInfo extends NextPathnameInfo { defaultLocale?: string + ignorePrefix?: boolean } export function formatNextPathnameInfo(info: ExtendedInfo) { let pathname = addLocale( info.pathname, info.locale, - info.buildId ? undefined : info.defaultLocale + info.buildId ? undefined : info.defaultLocale, + info.ignorePrefix ) if (info.buildId) { From 60497f645c491427be959198c053f2e11b6672eb Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Thu, 16 Jun 2022 19:01:44 +0200 Subject: [PATCH 3/4] Polymorphic type for `getCustomRoute` --- packages/next/server/server-route-utils.ts | 27 ++++++++++++++----- .../public/files/texts/file.txt | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 test/integration/i18n-support-base-path/public/files/texts/file.txt diff --git a/packages/next/server/server-route-utils.ts b/packages/next/server/server-route-utils.ts index 881ca8f147052..03f9467a3517a 100644 --- a/packages/next/server/server-route-utils.ts +++ b/packages/next/server/server-route-utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-redeclare */ import type { Header, Redirect, @@ -19,15 +20,27 @@ import { stringify as stringifyQs } from 'querystring' import { format as formatUrl } from 'url' import { normalizeRepeatedSlashes } from '../shared/lib/utils' -export const getCustomRoute = ({ - type, - rule, - restrictedRedirectPaths, -}: { +export function getCustomRoute(params: { + rule: Header + type: RouteType + restrictedRedirectPaths: string[] +}): Route & Header +export function getCustomRoute(params: { + rule: Rewrite + type: RouteType + restrictedRedirectPaths: string[] +}): Route & Rewrite +export function getCustomRoute(params: { + rule: Redirect + type: RouteType + restrictedRedirectPaths: string[] +}): Route & Redirect +export function getCustomRoute(params: { rule: Rewrite | Redirect | Header type: RouteType restrictedRedirectPaths: string[] -}) => { +}): (Route & Rewrite) | (Route & Header) | (Route & Rewrite) { + const { rule, type, restrictedRedirectPaths } = params const match = getPathMatch(rule.source, { strict: true, removeUnnamedParams: true, @@ -46,7 +59,7 @@ export const getCustomRoute = ({ match, name: type, fn: async (_req, _res, _params, _parsedUrl) => ({ finished: false }), - } as Route & Rewrite & Header + } } export const createHeaderRoute = ({ diff --git a/test/integration/i18n-support-base-path/public/files/texts/file.txt b/test/integration/i18n-support-base-path/public/files/texts/file.txt new file mode 100644 index 0000000000000..811b379a89ba4 --- /dev/null +++ b/test/integration/i18n-support-base-path/public/files/texts/file.txt @@ -0,0 +1 @@ +hello from file.txt \ No newline at end of file From d1d4e4867c908b34a835b7f3f3f4e80f2209925d Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Wed, 15 Jun 2022 18:59:10 +0200 Subject: [PATCH 4/4] Refactor Server Router --- packages/next/server/base-server.ts | 7 +- packages/next/server/dev/next-dev-server.ts | 1 - packages/next/server/next-server.ts | 11 +- packages/next/server/router.ts | 177 ++++++++---------- packages/next/server/server-route-utils.ts | 24 ++- .../e2e/middleware-rewrites/app/middleware.js | 2 + test/integration/i18n-support/test/shared.js | 20 +- 7 files changed, 120 insertions(+), 122 deletions(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 43905b1e1802d..5220d13e7f5cd 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -705,7 +705,6 @@ export default abstract class Server { } protected generateRoutes(): { - basePath: string headers: Route[] rewrites: { beforeFiles: Route[] @@ -719,7 +718,7 @@ export default abstract class Server { pageChecker: PageChecker useFileSystemPublicRoutes: boolean dynamicRoutes: DynamicRoutes | undefined - locales: string[] + nextConfig: NextConfig } { const publicRoutes = this.generatePublicRoutes() const imageRoutes = this.generateImageRoutes() @@ -834,6 +833,7 @@ export default abstract class Server { const catchAllRoute: Route = { match: getPathMatch('/:path*'), type: 'route', + matchesLocale: true, name: 'Catchall render', fn: async (req, res, _params, parsedUrl) => { let { pathname, query } = parsedUrl @@ -899,9 +899,8 @@ export default abstract class Server { catchAllMiddleware, useFileSystemPublicRoutes, dynamicRoutes: this.dynamicRoutes, - basePath: this.nextConfig.basePath, pageChecker: this.hasPage.bind(this), - locales: this.nextConfig.i18n?.locales || [], + nextConfig: this.nextConfig, } } diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index c827f599180b8..1bcc41e5a63c5 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -948,7 +948,6 @@ export default class DevServer extends Server { fsRoutes.push({ match: getPathMatch('/:path*'), type: 'route', - requireBasePath: false, name: 'catchall public directory route', fn: async (req, res, params, parsedUrl) => { const { pathname } = parsedUrl diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 5beb225713fd6..351d559aa434a 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -381,6 +381,7 @@ export default class NextNodeServer extends BaseServer { return [ { match: getPathMatch('/:path*'), + matchesBasePath: true, name: 'public folder catchall', fn: async (req, res, params, parsedUrl) => { const pathParts: string[] = params.path || [] @@ -973,7 +974,7 @@ export default class NextNodeServer extends BaseServer { let fallback: Route[] = [] if (!this.minimalMode) { - const buildRewrite = (rewrite: Rewrite, check = true) => { + const buildRewrite = (rewrite: Rewrite, check = true): Route => { const rewriteRoute = getCustomRoute({ type: 'rewrite', rule: rewrite, @@ -985,6 +986,10 @@ export default class NextNodeServer extends BaseServer { type: rewriteRoute.type, name: `Rewrite route ${rewriteRoute.source}`, match: rewriteRoute.match, + matchesBasePath: true, + matchesLocale: true, + matchesLocaleAPIRoutes: true, + matchesTrailingSlash: true, fn: async (req, res, params, parsedUrl) => { const { newUrl, parsedDestination } = prepareDestination({ appendParamsToQuery: true, @@ -1011,7 +1016,7 @@ export default class NextNodeServer extends BaseServer { query: parsedDestination.query, } }, - } as Route + } } if (Array.isArray(this.customRoutes.rewrites)) { @@ -1264,6 +1269,8 @@ export default class NextNodeServer extends BaseServer { return { match: getPathMatch('/:path*'), + matchesBasePath: true, + matchesLocale: true, type: 'route', name: 'middleware catchall', fn: async (req, res, _params, parsed) => { diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts index 16bc54bd56c05..20518b20a2fca 100644 --- a/packages/next/server/router.ts +++ b/packages/next/server/router.ts @@ -1,3 +1,4 @@ +import type { NextConfig } from './config' import type { ParsedUrlQuery } from 'querystring' import type { BaseNextRequest, BaseNextResponse } from './base-http' import type { @@ -13,6 +14,8 @@ import { RouteHas } from '../lib/load-custom-routes' import { matchHas } from '../shared/lib/router/utils/prepare-destination' import { removePathPrefix } from '../shared/lib/router/utils/remove-path-prefix' import { getRequestMeta } from './request-meta' +import { formatNextPathnameInfo } from '../shared/lib/router/utils/format-next-pathname-info' +import { getNextPathnameInfo } from '../shared/lib/router/utils/get-next-pathname-info' type RouteResult = { finished: boolean @@ -27,7 +30,10 @@ export type Route = { check?: boolean statusCode?: number name: string - requireBasePath?: false + matchesBasePath?: true + matchesLocale?: true + matchesLocaleAPIRoutes?: true + matchesTrailingSlash?: true internal?: true fn: ( req: BaseNextRequest, @@ -41,10 +47,7 @@ export type DynamicRoutes = Array<{ page: string; match: RouteMatch }> export type PageChecker = (pathname: string) => Promise -const customRouteTypes = new Set(['rewrite', 'redirect', 'header']) - export default class Router { - basePath: string headers: Route[] fsRoutes: Route[] redirects: Route[] @@ -58,11 +61,10 @@ export default class Router { pageChecker: PageChecker dynamicRoutes: DynamicRoutes useFileSystemPublicRoutes: boolean - locales: string[] seenRequests: Set + nextConfig: NextConfig constructor({ - basePath = '', headers = [], fsRoutes = [], rewrites = { @@ -76,9 +78,8 @@ export default class Router { dynamicRoutes = [], pageChecker, useFileSystemPublicRoutes, - locales = [], + nextConfig, }: { - basePath: string headers: Route[] fsRoutes: Route[] rewrites: { @@ -92,9 +93,9 @@ export default class Router { dynamicRoutes: DynamicRoutes | undefined pageChecker: PageChecker useFileSystemPublicRoutes: boolean - locales: string[] + nextConfig: NextConfig }) { - this.basePath = basePath + this.nextConfig = nextConfig this.headers = headers this.fsRoutes = fsRoutes this.rewrites = rewrites @@ -104,10 +105,17 @@ export default class Router { this.catchAllMiddleware = catchAllMiddleware this.dynamicRoutes = dynamicRoutes this.useFileSystemPublicRoutes = useFileSystemPublicRoutes - this.locales = locales this.seenRequests = new Set() } + get locales() { + return this.nextConfig.i18n?.locales || [] + } + + get basePath() { + return this.nextConfig.basePath || '' + } + setDynamicRoutes(routes: DynamicRoutes = []) { this.dynamicRoutes = routes } @@ -228,7 +236,6 @@ export default class Router { { type: 'route', name: 'page checker', - requireBasePath: false, match: getPathMatch('/:path*'), fn: async ( checkerReq, @@ -262,7 +269,6 @@ export default class Router { { type: 'route', name: 'dynamic route/page check', - requireBasePath: false, match: getPathMatch('/:path*'), fn: async ( _checkerReq, @@ -283,86 +289,62 @@ export default class Router { // disabled ...(this.useFileSystemPublicRoutes ? [this.catchAllRoute] : []), ] - const originallyHadBasePath = - !this.basePath || getRequestMeta(req, '_nextHadBasePath') for (const testRoute of allRoutes) { - // if basePath is being used, the basePath will still be included - // in the pathname here to allow custom-routes to require containing - // it or not, filesystem routes and pages must always include the basePath - // if it is set - let currentPathname = parsedUrlUpdated.pathname as string - const originalPathname = currentPathname - const requireBasePath = testRoute.requireBasePath !== false - const isCustomRoute = customRouteTypes.has(testRoute.type) - const isPublicFolderCatchall = - testRoute.name === 'public folder catchall' - const isMiddlewareCatchall = testRoute.name === 'middleware catchall' - const keepBasePath = - isCustomRoute || isPublicFolderCatchall || isMiddlewareCatchall - const keepLocale = isCustomRoute - - const currentPathnameNoBasePath = removePathPrefix( - currentPathname, - this.basePath - ) - - if (!keepBasePath) { - currentPathname = currentPathnameNoBasePath + const originalPathname = parsedUrlUpdated.pathname as string + const pathnameInfo = getNextPathnameInfo(originalPathname, { + nextConfig: this.nextConfig, + parseData: false, + }) + + if ( + pathnameInfo.locale && + !testRoute.matchesLocaleAPIRoutes && + pathnameInfo.pathname.match(/^\/api(?:\/|$)/) + ) { + continue } - const localePathResult = normalizeLocalePath( - currentPathnameNoBasePath, - this.locales - ) + if (getRequestMeta(req, '_nextHadBasePath')) { + pathnameInfo.basePath = this.basePath + } - const activeBasePath = keepBasePath ? this.basePath : '' + const basePath = pathnameInfo.basePath + if (!testRoute.matchesBasePath) { + pathnameInfo.basePath = '' + } - // don't match API routes when they are locale prefixed - // e.g. /api/hello shouldn't match /en/api/hello as a page - // rewrites/redirects can match though if ( - !isCustomRoute && - localePathResult.detectedLocale && - localePathResult.pathname.match(/^\/api(?:\/|$)/) + testRoute.matchesLocale && + parsedUrl.query.__nextLocale && + !pathnameInfo.locale ) { - continue + pathnameInfo.locale = parsedUrl.query.__nextLocale } - if (keepLocale) { - if ( - !testRoute.internal && - parsedUrl.query.__nextLocale && - !localePathResult.detectedLocale - ) { - currentPathname = `${activeBasePath}/${ - parsedUrl.query.__nextLocale - }${ - currentPathnameNoBasePath === '/' ? '' : currentPathnameNoBasePath - }` - } + if ( + !testRoute.matchesLocale && + pathnameInfo.locale === this.nextConfig.i18n?.defaultLocale && + pathnameInfo.locale + ) { + pathnameInfo.locale = undefined + } - if ( - getRequestMeta(req, '__nextHadTrailingSlash') && - !currentPathname.endsWith('/') - ) { - currentPathname += '/' - } - } else { - currentPathname = `${ - getRequestMeta(req, '_nextHadBasePath') ? activeBasePath : '' - }${ - activeBasePath && currentPathnameNoBasePath === '/' - ? '' - : currentPathnameNoBasePath - }` + if ( + testRoute.matchesTrailingSlash && + getRequestMeta(req, '__nextHadTrailingSlash') + ) { + pathnameInfo.trailingSlash = true } - let newParams = testRoute.match(currentPathname) + const matchPathname = formatNextPathnameInfo({ + ignorePrefix: true, + ...pathnameInfo, + }) + let newParams = testRoute.match(matchPathname) if (testRoute.has && newParams) { const hasParams = matchHas(req, testRoute.has, parsedUrlUpdated.query) - if (hasParams) { Object.assign(newParams, hasParams) } else { @@ -370,27 +352,23 @@ export default class Router { } } - // Check if the match function matched - if (newParams) { - // since we require basePath be present for non-custom-routes we - // 404 here when we matched an fs route - if (!keepBasePath) { - if ( - !originallyHadBasePath && - !getRequestMeta(req, '_nextDidRewrite') - ) { - if (requireBasePath) { - // consider this a non-match so the 404 renders - return false - } - // page checker occurs before rewrites so we need to continue - // to check those since they don't always require basePath - continue - } - - parsedUrlUpdated.pathname = currentPathname - } + /** + * If it is a matcher that doesn't match the basePath (like the public + * directory) but Next.js is configured to use a basePath that was + * never there, we consider this an invalid match and keep routing. + */ + if ( + newParams && + this.basePath && + !testRoute.matchesBasePath && + !getRequestMeta(req, '_nextDidRewrite') && + !basePath + ) { + continue + } + if (newParams) { + parsedUrlUpdated.pathname = matchPathname const result = await testRoute.fn( req, res, @@ -398,16 +376,13 @@ export default class Router { parsedUrlUpdated ) - // The response was handled if (result.finished) { return true } // since the fs route didn't finish routing we need to re-add the // basePath to continue checking with the basePath present - if (!keepBasePath) { - parsedUrlUpdated.pathname = originalPathname - } + parsedUrlUpdated.pathname = originalPathname if (result.pathname) { parsedUrlUpdated.pathname = result.pathname diff --git a/packages/next/server/server-route-utils.ts b/packages/next/server/server-route-utils.ts index 03f9467a3517a..eecfcb14fff73 100644 --- a/packages/next/server/server-route-utils.ts +++ b/packages/next/server/server-route-utils.ts @@ -68,7 +68,7 @@ export const createHeaderRoute = ({ }: { rule: Header restrictedRedirectPaths: string[] -}) => { +}): Route => { const headerRoute = getCustomRoute({ type: 'header', rule, @@ -76,13 +76,16 @@ export const createHeaderRoute = ({ }) return { match: headerRoute.match, + matchesBasePath: true, + matchesLocale: true, + matchesLocaleAPIRoutes: true, + matchesTrailingSlash: true, has: headerRoute.has, type: headerRoute.type, name: `${headerRoute.type} ${headerRoute.source} header route`, fn: async (_req, res, params, _parsedUrl) => { const hasParams = Object.keys(params).length > 0 - - for (const header of (headerRoute as Header).headers) { + for (const header of headerRoute.headers) { let { key, value } = header if (hasParams) { key = compileNonPath(key, params) @@ -92,7 +95,7 @@ export const createHeaderRoute = ({ } return { finished: false } }, - } as Route + } } export const createRedirectRoute = ({ @@ -101,7 +104,7 @@ export const createRedirectRoute = ({ }: { rule: Redirect restrictedRedirectPaths: string[] -}) => { +}): Route => { const redirectRoute = getCustomRoute({ type: 'redirect', rule, @@ -111,6 +114,10 @@ export const createRedirectRoute = ({ internal: redirectRoute.internal, type: redirectRoute.type, match: redirectRoute.match, + matchesBasePath: true, + matchesLocale: redirectRoute.internal ? undefined : true, + matchesLocaleAPIRoutes: true, + matchesTrailingSlash: true, has: redirectRoute.has, statusCode: redirectRoute.statusCode, name: `Redirect route ${redirectRoute.source}`, @@ -134,10 +141,7 @@ export const createRedirectRoute = ({ } res - .redirect( - updatedDestination, - getRedirectStatus(redirectRoute as Redirect) - ) + .redirect(updatedDestination, getRedirectStatus(redirectRoute)) .body(updatedDestination) .send() @@ -145,7 +149,7 @@ export const createRedirectRoute = ({ finished: true, } }, - } as Route + } } // since initial query values are decoded by querystring.parse diff --git a/test/e2e/middleware-rewrites/app/middleware.js b/test/e2e/middleware-rewrites/app/middleware.js index 6042a2c06e6c3..241b52a023077 100644 --- a/test/e2e/middleware-rewrites/app/middleware.js +++ b/test/e2e/middleware-rewrites/app/middleware.js @@ -117,4 +117,6 @@ export async function middleware(request) { url.searchParams.set('locale', url.locale) return NextResponse.rewrite(url) } + + return NextResponse.rewrite(request.nextUrl) } diff --git a/test/integration/i18n-support/test/shared.js b/test/integration/i18n-support/test/shared.js index b95c70805c040..34c48c021fe91 100644 --- a/test/integration/i18n-support/test/shared.js +++ b/test/integration/i18n-support/test/shared.js @@ -71,8 +71,14 @@ export function runTests(ctx) { undefined, { redirect: 'manual' } ) - expect(res.status).toBe(404) - expect(await res.text()).toContain('could not be found') + + if (locale !== 'en-US') { + expect(res.status).toBe(404) + expect(await res.text()).toContain('could not be found') + } else { + // We only 404 for non-default locale + expect(res.status).toBe(200) + } } } }) @@ -85,8 +91,14 @@ export function runTests(ctx) { undefined, { redirect: 'manual' } ) - expect(res.status).toBe(404) - expect(await res.text()).toContain('could not be found') + + if (locale !== 'en-US') { + expect(res.status).toBe(404) + expect(await res.text()).toContain('could not be found') + } else { + // We only 404 for non-default locale + expect(res.status).toBe(200) + } } })