Skip to content

Commit 1e22020

Browse files
committed
Skip internal routes in middleware
1 parent fbb50f4 commit 1e22020

File tree

8 files changed

+66
-11
lines changed

8 files changed

+66
-11
lines changed

packages/next/src/experimental/testing/server/middleware-testing-utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export function unstable_doesMiddlewareMatch({
3333
return true
3434
}
3535
const matchers = getMiddlewareMatchers(config.matcher, nextConfig ?? {})
36-
const routeMatchFn = getMiddlewareRouteMatcher(matchers)
36+
const routeMatchFn = getMiddlewareRouteMatcher(
37+
matchers,
38+
nextConfig?.skipMiddlewareNextInternalRoutes !== false
39+
)
3740
const { pathname, searchParams = new URLSearchParams() } = parseUrl(url) || {}
3841
const request = constructRequest({ url, headers, cookies })
3942
return routeMatchFn(pathname, request, Object.fromEntries(searchParams))

packages/next/src/server/config-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
690690
serverRuntimeConfig: z.record(z.string(), z.any()).optional(),
691691
skipMiddlewareUrlNormalize: z.boolean().optional(),
692692
skipTrailingSlashRedirect: z.boolean().optional(),
693+
skipMiddlewareNextInternalRoutes: z.boolean().optional(),
693694
staticPageGenerationTimeout: z.number().optional(),
694695
expireTime: z.number().optional(),
695696
target: z.string().optional(),

packages/next/src/server/config-shared.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,12 @@ export interface NextConfig {
12411241

12421242
skipMiddlewareUrlNormalize?: boolean
12431243

1244+
/**
1245+
* Skip Next.js internals route `/_next` from middleware.
1246+
* @default true
1247+
*/
1248+
skipMiddlewareNextInternalRoutes?: boolean
1249+
12441250
skipTrailingSlashRedirect?: boolean
12451251

12461252
modularizeImports?: Record<

packages/next/src/server/dev/next-dev-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,8 @@ export default class DevServer extends Server {
672672
// field as it isn't serializable
673673
if (this.middleware?.match === null) {
674674
this.middleware.match = getMiddlewareRouteMatcher(
675-
this.middleware.matchers || []
675+
this.middleware.matchers || [],
676+
this.nextConfig.skipMiddlewareNextInternalRoutes !== false
676677
)
677678
}
678679
return this.middleware

packages/next/src/server/lib/router-utils/filesystem.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,15 @@ export async function setupFsCheck(opts: {
309309

310310
if (middlewareManifest.middleware?.['/']?.matchers) {
311311
middlewareMatcher = getMiddlewareRouteMatcher(
312-
middlewareManifest.middleware?.['/']?.matchers
312+
middlewareManifest.middleware?.['/']?.matchers,
313+
opts.config.skipMiddlewareNextInternalRoutes !== false
313314
)
314315
} else if (functionsConfigManifest?.functions['/_middleware']) {
315316
middlewareMatcher = getMiddlewareRouteMatcher(
316317
functionsConfigManifest.functions['/_middleware'].matchers ?? [
317318
{ regexp: '.*', originalSource: '/:path*' },
318-
]
319+
],
320+
opts.config.skipMiddlewareNextInternalRoutes !== false
319321
)
320322
}
321323

packages/next/src/server/lib/router-utils/setup-dev-bundler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,10 @@ async function startWatcher(
908908
serverFields.hasAppNotFound = hasRootAppNotFound
909909

910910
opts.fsChecker.middlewareMatcher = serverFields.middleware?.matchers
911-
? getMiddlewareRouteMatcher(serverFields.middleware?.matchers)
911+
? getMiddlewareRouteMatcher(
912+
serverFields.middleware?.matchers,
913+
opts.nextConfig.skipMiddlewareNextInternalRoutes !== false
914+
)
912915
: undefined
913916

914917
const interceptionRoutes = generateInterceptionRoutesRewrites(

packages/next/src/server/next-server.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ const MiddlewareMatcherCache = new WeakMap<
145145
>()
146146

147147
function getMiddlewareMatcher(
148-
info: MiddlewareManifest['middleware'][string]
148+
info: MiddlewareManifest['middleware'][string],
149+
skipMiddlewareNextInternalRoutes: boolean
149150
): MiddlewareRouteMatch {
150151
const stored = MiddlewareMatcherCache.get(info)
151152
if (stored) {
@@ -158,7 +159,10 @@ function getMiddlewareMatcher(
158159
)
159160
}
160161

161-
const matcher = getMiddlewareRouteMatcher(info.matchers)
162+
const matcher = getMiddlewareRouteMatcher(
163+
info.matchers,
164+
skipMiddlewareNextInternalRoutes
165+
)
162166
MiddlewareMatcherCache.set(info, matcher)
163167
return matcher
164168
}
@@ -1467,7 +1471,8 @@ export default class NextNodeServer extends BaseServer<
14671471
match: getMiddlewareRouteMatcher(
14681472
middlewareModule.config?.matchers || [
14691473
{ regexp: '.*', originalSource: '/:path*' },
1470-
]
1474+
],
1475+
this.nextConfig.skipMiddlewareNextInternalRoutes !== false
14711476
),
14721477
page: '/',
14731478
}
@@ -1477,7 +1482,10 @@ export default class NextNodeServer extends BaseServer<
14771482
}
14781483

14791484
return {
1480-
match: getMiddlewareMatcher(middleware),
1485+
match: getMiddlewareMatcher(
1486+
middleware,
1487+
this.nextConfig.skipMiddlewareNextInternalRoutes !== false
1488+
),
14811489
page: '/',
14821490
}
14831491
}

packages/next/src/shared/lib/router/utils/middleware-route-matcher.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@ export interface MiddlewareRouteMatch {
1212
}
1313

1414
export function getMiddlewareRouteMatcher(
15-
matchers: MiddlewareMatcher[]
15+
matchers: MiddlewareMatcher[],
16+
skipMiddlewareNextInternalRoutes: boolean
1617
): MiddlewareRouteMatch {
18+
// Apply exclusion logic to matchers if needed
19+
const processedMatchers = skipMiddlewareNextInternalRoutes
20+
? excludeNextInternalRoutesFromMatchers(matchers)
21+
: matchers
22+
1723
return (
1824
pathname: string | null | undefined,
1925
req: BaseNextRequest,
2026
query: Params
2127
) => {
22-
for (const matcher of matchers) {
28+
for (const matcher of processedMatchers) {
2329
const routeMatch = new RegExp(matcher.regexp).exec(pathname!)
2430
if (!routeMatch) {
2531
continue
@@ -38,3 +44,28 @@ export function getMiddlewareRouteMatcher(
3844
return false
3945
}
4046
}
47+
48+
function excludeNextInternalRoutesFromMatchers(matchers: MiddlewareMatcher[]) {
49+
return matchers.map((matcher) => {
50+
// If the matcher explicitly targets _next, don't modify it
51+
if (matcher.originalSource?.includes('/_next/')) {
52+
return matcher
53+
}
54+
55+
// Modify the regex to exclude _next routes
56+
let modifiedRegex = matcher.regexp
57+
58+
// If the regex starts with ^, insert negative lookahead after it
59+
if (modifiedRegex.startsWith('^')) {
60+
modifiedRegex = `^(?!.*/_next/)${modifiedRegex.slice(1)}`
61+
} else {
62+
// Otherwise, add negative lookahead at the beginning
63+
modifiedRegex = `(?!.*/_next/)${modifiedRegex}`
64+
}
65+
66+
return {
67+
...matcher,
68+
regexp: modifiedRegex,
69+
}
70+
})
71+
}

0 commit comments

Comments
 (0)