Skip to content

Commit 6b72050

Browse files
devjiwonchoiijjkvercel[bot]
authored
[Breaking] Skip Next.js internal routes /_next in middleware (#84239)
### Why? When the user hasn't set a matcher in the middleware, it always hits the internal route `/_next/`. This is redundant, though quite common, as it serves all the static assets. Therefore, skip the internal route by default when no matcher is set. --------- Co-authored-by: JJ Kasper <[email protected]> Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
1 parent c08e05f commit 6b72050

File tree

31 files changed

+262
-35
lines changed

31 files changed

+262
-35
lines changed

crates/next-api/src/middleware.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ use crate::{
4242
route::{Endpoint, EndpointOutput, EndpointOutputPaths},
4343
};
4444

45+
/// Rust implementation of the TypeScript getDefaultMiddlewareMatcher function
46+
/// Generates default middleware matcher patterns that respect skipMiddlewareNextInternalRoutes
47+
fn get_default_middleware_matcher(
48+
skip_middleware_next_internal_routes: Option<bool>,
49+
) -> MiddlewareMatcher {
50+
let skip_internal = skip_middleware_next_internal_routes.unwrap_or(true);
51+
52+
if skip_internal {
53+
// Skip "/_next/" internal routes, except for "/_next/data/" which is needed for
54+
// client-side navigation. Do not consider basePath as the user cannot create a
55+
// route starts with underscore.
56+
MiddlewareMatcher {
57+
regexp: Some(rcstr!("^(?!.*\\/\\_next\\/(?!data\\/)).*")),
58+
original_source: rcstr!("/((?!_next/(?!data/))[^]*)*"),
59+
..Default::default()
60+
}
61+
} else {
62+
MiddlewareMatcher {
63+
regexp: Some(rcstr!("^/.*$")),
64+
original_source: rcstr!("/:path*"),
65+
..Default::default()
66+
}
67+
}
68+
}
69+
4570
#[turbo_tasks::value]
4671
pub struct MiddlewareEndpoint {
4772
project: ResolvedVc<Project>,
@@ -165,6 +190,8 @@ impl MiddlewareEndpoint {
165190
.map(|i18n| i18n.locales.len() > 1)
166191
.unwrap_or(false);
167192
let base_path = next_config.base_path().await?;
193+
let skip_middleware_next_internal_routes =
194+
next_config.skip_middleware_next_internal_routes().await?;
168195

169196
let matchers = if let Some(matchers) = config.middleware_matcher.as_ref() {
170197
matchers
@@ -216,11 +243,9 @@ impl MiddlewareEndpoint {
216243
})
217244
.collect()
218245
} else {
219-
vec![MiddlewareMatcher {
220-
regexp: Some(rcstr!("^/.*$")),
221-
original_source: rcstr!("/:path*"),
222-
..Default::default()
223-
}]
246+
vec![get_default_middleware_matcher(
247+
*skip_middleware_next_internal_routes,
248+
)]
224249
};
225250

226251
if matches!(runtime, NextRuntime::NodeJs) {

crates/next-core/src/next_config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub struct NextConfig {
103103
asset_prefix: Option<RcStr>,
104104
base_path: Option<RcStr>,
105105
skip_middleware_url_normalize: Option<bool>,
106+
skip_middleware_next_internal_routes: Option<bool>,
106107
skip_trailing_slash_redirect: Option<bool>,
107108
i18n: Option<I18NConfig>,
108109
cross_origin: Option<CrossOriginConfig>,
@@ -1343,6 +1344,11 @@ impl NextConfig {
13431344
Vc::cell(self.base_path.clone())
13441345
}
13451346

1347+
#[turbo_tasks::function]
1348+
pub fn skip_middleware_next_internal_routes(&self) -> Vc<Option<bool>> {
1349+
Vc::cell(self.skip_middleware_next_internal_routes)
1350+
}
1351+
13461352
#[turbo_tasks::function]
13471353
pub fn cache_handler(&self, project_path: FileSystemPath) -> Result<Vc<OptionFileSystemPath>> {
13481354
if let Some(handler) = &self.cache_handler {

packages/next/src/build/entries.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
isInstrumentationHookFilename,
4646
} from './utils'
4747
import { getPageStaticInfo } from './analysis/get-page-static-info'
48+
import { getDefaultMiddlewareMatcher } from '../shared/lib/router/utils/get-default-middleware-matcher'
4849
import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep'
4950
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
5051
import type { ServerRuntime } from '../types'
@@ -898,7 +899,7 @@ export async function createEntrypoints(
898899

899900
if (isMiddlewareFile(page)) {
900901
middlewareMatchers = staticInfo.middleware?.matchers ?? [
901-
{ regexp: '.*', originalSource: '/:path*' },
902+
getDefaultMiddlewareMatcher(params.config),
902903
]
903904
}
904905

packages/next/src/build/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ import { isEdgeRuntime } from '../lib/is-edge-runtime'
150150
import { recursiveCopy } from '../lib/recursive-copy'
151151
import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'
152152
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'
153+
import { getDefaultMiddlewareMatcher } from '../shared/lib/router/utils/get-default-middleware-matcher'
153154
import { getFilesInDir } from '../lib/get-files-in-dir'
154155
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
155156
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
@@ -2583,10 +2584,7 @@ export default async function build(
25832584
functionsConfigManifest.functions['/_middleware'] = {
25842585
runtime: staticInfo.runtime,
25852586
matchers: staticInfo.middleware?.matchers ?? [
2586-
{
2587-
regexp: '^.*$',
2588-
originalSource: '/:path*',
2589-
},
2587+
getDefaultMiddlewareMatcher(config),
25902588
],
25912589
}
25922590

packages/next/src/build/webpack-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,7 @@ export default async function getBaseWebpackConfig(
20812081
dev,
20822082
sriEnabled: !dev && !!config.experimental.sri?.algorithm,
20832083
rewrites,
2084+
nextConfig: config,
20842085
edgeEnvironments: {
20852086
__NEXT_BUILD_ID: buildId,
20862087
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: encryptionKey,

packages/next/src/build/webpack/plugins/middleware-plugin.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
import type { EdgeSSRMeta } from '../loaders/get-module-build-info'
66
import type { MiddlewareMatcher } from '../../analysis/get-page-static-info'
77
import { getNamedMiddlewareRegex } from '../../../shared/lib/router/utils/route-regex'
8+
import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher'
89
import { getModuleBuildInfo } from '../loaders/get-module-build-info'
910
import { getSortedRoutes } from '../../../shared/lib/router/utils'
1011
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
@@ -36,6 +37,7 @@ import type { CustomRoutes } from '../../../lib/load-custom-routes'
3637
import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites'
3738
import { getDynamicCodeEvaluationError } from './wellknown-errors-plugin/parse-dynamic-code-evaluation-error'
3839
import { getModuleReferencesInOrder } from '../utils'
40+
import type { NextConfigComplete } from '../../../server/config-shared'
3941

4042
const KNOWN_SAFE_DYNAMIC_PACKAGES =
4143
require('../../../lib/known-edge-safe-packages.json') as string[]
@@ -195,21 +197,29 @@ function getCreateAssets(params: {
195197
continue
196198
}
197199

198-
const matcherSource = metadata.edgeSSR?.isAppDir
199-
? normalizeAppPath(page)
200-
: page
201-
202-
const catchAll = !metadata.edgeSSR && !metadata.edgeApiFunction
203-
204-
const { namedRegex } = getNamedMiddlewareRegex(matcherSource, {
205-
catchAll,
206-
})
207-
const matchers = metadata?.edgeMiddleware?.matchers ?? [
208-
{
209-
regexp: namedRegex,
210-
originalSource: page === '/' && catchAll ? '/:path*' : matcherSource,
211-
},
212-
]
200+
let matchers: MiddlewareMatcher[]
201+
if (metadata?.edgeMiddleware?.matchers) {
202+
matchers = metadata.edgeMiddleware.matchers
203+
} else {
204+
// For middleware at root with no explicit matchers, use getDefaultMiddlewareMatcher
205+
// which respects skipMiddlewareNextInternalRoutes config
206+
const catchAll = !metadata.edgeSSR && !metadata.edgeApiFunction
207+
if (page === '/' && catchAll) {
208+
matchers = [getDefaultMiddlewareMatcher(opts.nextConfig)]
209+
} else {
210+
const matcherSource = metadata.edgeSSR?.isAppDir
211+
? normalizeAppPath(page)
212+
: page
213+
matchers = [
214+
{
215+
regexp: getNamedMiddlewareRegex(matcherSource, {
216+
catchAll,
217+
}).namedRegex,
218+
originalSource: matcherSource,
219+
},
220+
]
221+
}
222+
}
213223

214224
const isEdgeFunction = !!(metadata.edgeApiFunction || metadata.edgeSSR)
215225
const edgeFunctionDefinition: EdgeFunctionDefinition = {
@@ -818,19 +828,28 @@ interface Options {
818828
sriEnabled: boolean
819829
rewrites: CustomRoutes['rewrites']
820830
edgeEnvironments: EdgeRuntimeEnvironments
831+
nextConfig: NextConfigComplete
821832
}
822833

823834
export default class MiddlewarePlugin {
824835
private readonly dev: Options['dev']
825836
private readonly sriEnabled: Options['sriEnabled']
826837
private readonly rewrites: Options['rewrites']
827838
private readonly edgeEnvironments: EdgeRuntimeEnvironments
828-
829-
constructor({ dev, sriEnabled, rewrites, edgeEnvironments }: Options) {
839+
private readonly nextConfig: Options['nextConfig']
840+
841+
constructor({
842+
dev,
843+
sriEnabled,
844+
rewrites,
845+
edgeEnvironments,
846+
nextConfig,
847+
}: Options) {
830848
this.dev = dev
831849
this.sriEnabled = sriEnabled
832850
this.rewrites = rewrites
833851
this.edgeEnvironments = edgeEnvironments
852+
this.nextConfig = nextConfig
834853
}
835854

836855
public apply(compiler: webpack.Compiler) {
@@ -886,6 +905,7 @@ export default class MiddlewarePlugin {
886905
rewrites: this.rewrites,
887906
edgeEnvironments: this.edgeEnvironments,
888907
dev: this.dev,
908+
nextConfig: this.nextConfig,
889909
},
890910
})
891911
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
675675
.optional(),
676676
serverExternalPackages: z.array(z.string()).optional(),
677677
skipMiddlewareUrlNormalize: z.boolean().optional(),
678+
skipMiddlewareNextInternalRoutes: z.boolean().optional(),
678679
skipTrailingSlashRedirect: z.boolean().optional(),
679680
staticPageGenerationTimeout: z.number().optional(),
680681
expireTime: z.number().optional(),

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

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

12361236
skipMiddlewareUrlNormalize?: boolean
12371237

1238+
/**
1239+
* Skip Next.js internals route `/_next` from middleware.
1240+
* @default true
1241+
*/
1242+
skipMiddlewareNextInternalRoutes?: boolean
1243+
12381244
skipTrailingSlashRedirect?: boolean
12391245

12401246
modularizeImports?: Record<
@@ -1511,6 +1517,7 @@ export const defaultConfig = Object.freeze({
15111517
},
15121518
htmlLimitedBots: undefined,
15131519
bundlePagesRouterDependencies: false,
1520+
skipMiddlewareNextInternalRoutes: true,
15141521
} satisfies NextConfig)
15151522

15161523
export async function normalizeConfig(phase: string, config: any) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { pathHasPrefix } from '../../../shared/lib/router/utils/path-has-prefix'
3131
import { normalizeLocalePath } from '../../../shared/lib/i18n/normalize-locale-path'
3232
import { removePathPrefix } from '../../../shared/lib/router/utils/remove-path-prefix'
3333
import { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher'
34+
import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher'
3435
import {
3536
APP_PATH_ROUTES_MANIFEST,
3637
BUILD_ID_FILE,
@@ -314,7 +315,7 @@ export async function setupFsCheck(opts: {
314315
} else if (functionsConfigManifest?.functions['/_middleware']) {
315316
middlewareMatcher = getMiddlewareRouteMatcher(
316317
functionsConfigManifest.functions['/_middleware'].matchers ?? [
317-
{ regexp: '.*', originalSource: '/:path*' },
318+
getDefaultMiddlewareMatcher(opts.config),
318319
]
319320
)
320321
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
} from '../../../shared/lib/constants'
4949

5050
import { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher'
51+
import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher'
5152

5253
import {
5354
isMiddlewareFile,
@@ -472,7 +473,7 @@ async function startWatcher(
472473
serverFields.actualMiddlewareFile
473474
)
474475
middlewareMatchers = staticInfo.middleware?.matchers || [
475-
{ regexp: '^/.*$', originalSource: '/:path*' },
476+
getDefaultMiddlewareMatcher(opts.nextConfig),
476477
]
477478
continue
478479
}

0 commit comments

Comments
 (0)