diff --git a/packages/next/build/analysis/get-page-static-info.ts b/packages/next/build/analysis/get-page-static-info.ts index d27ef9057b1b..6b57ee25d94c 100644 --- a/packages/next/build/analysis/get-page-static-info.ts +++ b/packages/next/build/analysis/get-page-static-info.ts @@ -178,11 +178,12 @@ function getMiddlewareConfig( function getMiddlewareRegExpStrings( matcherOrMatchers: unknown, - nextConfig: NextConfig + nextConfig: NextConfig, + { doNotRecurse }: { doNotRecurse?: boolean } = {} ): string[] { if (Array.isArray(matcherOrMatchers)) { return matcherOrMatchers.flatMap((matcher) => - getMiddlewareRegExpStrings(matcher, nextConfig) + getMiddlewareRegExpStrings(matcher, nextConfig, { doNotRecurse }) ) } @@ -198,6 +199,13 @@ function getMiddlewareRegExpStrings( throw new Error('`matcher`: path matcher must start with /') } + if (matcher === '/' && !doNotRecurse) { + const rootMatcher = '/' + return getMiddlewareRegExpStrings([rootMatcher, '/index'], nextConfig, { + doNotRecurse: true, + }) + } + if (nextConfig.i18n?.locales) { matcher = `/:nextInternalLocale(${nextConfig.i18n.locales .map((locale) => escapeStringRegexp(locale)) diff --git a/test/e2e/middleware-matcher/index.test.ts b/test/e2e/middleware-matcher/index.test.ts index d76a1b5d8ed0..75defe3d5bc0 100644 --- a/test/e2e/middleware-matcher/index.test.ts +++ b/test/e2e/middleware-matcher/index.test.ts @@ -217,6 +217,49 @@ describe('using a single matcher', () => { }) }) +describe('using root matcher', () => { + let next: NextInstance + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + export default function Home({ message }) { + return
Hi there!
+ } + `, + 'middleware.js': ` + import { NextResponse } from 'next/server' + export default (req) => { + const res = NextResponse.next(); + res.headers.set('X-From-Middleware', 'true'); + return res; + } + + export const config = { matcher: '/' }; + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('adds the header to the /', async () => { + const response = await fetchViaHTTP(next.url, '/') + expect(response.status).toBe(200) + expect(Object.fromEntries(response.headers)).toMatchObject({ + 'x-from-middleware': 'true', + }) + }) + + it('adds the header to the /index', async () => { + const response = await fetchViaHTTP(next.url, '/index') + expect(response.status).toBe(404) + expect(Object.fromEntries(response.headers)).toMatchObject({ + 'x-from-middleware': 'true', + }) + }) +}) + describe('using a single matcher with i18n', () => { let next: NextInstance beforeAll(async () => { @@ -276,6 +319,15 @@ describe('using a single matcher with i18n', () => { expect(res2.headers.get('X-From-Middleware')).toBe('true') }) + it('adds the header for a mathed root path with /index', async () => { + const res1 = await fetchViaHTTP(next.url, `/index`) + expect(await res1.text()).toContain(`(en) Hello from /`) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP(next.url, `/es/index`) + expect(await res2.text()).toContain(`(es) Hello from /`) + expect(res2.headers.get('X-From-Middleware')).toBe('true') + }) + it(`adds the headers for a matched data path`, async () => { const res1 = await fetchViaHTTP( next.url, @@ -367,6 +419,15 @@ describe('using a single matcher with i18n and basePath', () => { expect(res2.headers.get('X-From-Middleware')).toBe('true') }) + it('adds the header for a mathed root path with /index', async () => { + const res1 = await fetchViaHTTP(next.url, `/root/index`) + expect(await res1.text()).toContain(`(en) Hello from /`) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP(next.url, `/root/es/index`) + expect(await res2.text()).toContain(`(es) Hello from /`) + expect(res2.headers.get('X-From-Middleware')).toBe('true') + }) + it(`adds the headers for a matched data path`, async () => { const res1 = await fetchViaHTTP( next.url,