diff --git a/crates/next-api/src/middleware.rs b/crates/next-api/src/middleware.rs index 63304b4b9d3f4..ace2d5c0b9adc 100644 --- a/crates/next-api/src/middleware.rs +++ b/crates/next-api/src/middleware.rs @@ -42,6 +42,31 @@ use crate::{ route::{Endpoint, EndpointOutput, EndpointOutputPaths}, }; +/// Rust implementation of the TypeScript getDefaultMiddlewareMatcher function +/// Generates default middleware matcher patterns that respect skipMiddlewareNextInternalRoutes +fn get_default_middleware_matcher( + skip_middleware_next_internal_routes: Option, +) -> MiddlewareMatcher { + let skip_internal = skip_middleware_next_internal_routes.unwrap_or(true); + + if skip_internal { + // Skip "/_next/" internal routes, except for "/_next/data/" which is needed for + // client-side navigation. Do not consider basePath as the user cannot create a + // route starts with underscore. + MiddlewareMatcher { + regexp: Some(rcstr!("^(?!.*\\/\\_next\\/(?!data\\/)).*")), + original_source: rcstr!("/((?!_next/(?!data/))[^]*)*"), + ..Default::default() + } + } else { + MiddlewareMatcher { + regexp: Some(rcstr!("^/.*$")), + original_source: rcstr!("/:path*"), + ..Default::default() + } + } +} + #[turbo_tasks::value] pub struct MiddlewareEndpoint { project: ResolvedVc, @@ -165,6 +190,8 @@ impl MiddlewareEndpoint { .map(|i18n| i18n.locales.len() > 1) .unwrap_or(false); let base_path = next_config.base_path().await?; + let skip_middleware_next_internal_routes = + next_config.skip_middleware_next_internal_routes().await?; let matchers = if let Some(matchers) = config.middleware_matcher.as_ref() { matchers @@ -216,11 +243,9 @@ impl MiddlewareEndpoint { }) .collect() } else { - vec![MiddlewareMatcher { - regexp: Some(rcstr!("^/.*$")), - original_source: rcstr!("/:path*"), - ..Default::default() - }] + vec![get_default_middleware_matcher( + *skip_middleware_next_internal_routes, + )] }; if matches!(runtime, NextRuntime::NodeJs) { diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 29d625b90c097..4d0b20600c8c6 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -103,6 +103,7 @@ pub struct NextConfig { asset_prefix: Option, base_path: Option, skip_middleware_url_normalize: Option, + skip_middleware_next_internal_routes: Option, skip_trailing_slash_redirect: Option, i18n: Option, cross_origin: Option, @@ -1343,6 +1344,11 @@ impl NextConfig { Vc::cell(self.base_path.clone()) } + #[turbo_tasks::function] + pub fn skip_middleware_next_internal_routes(&self) -> Vc> { + Vc::cell(self.skip_middleware_next_internal_routes) + } + #[turbo_tasks::function] pub fn cache_handler(&self, project_path: FileSystemPath) -> Result> { if let Some(handler) = &self.cache_handler { diff --git a/packages/next/src/build/entries.ts b/packages/next/src/build/entries.ts index ccd03f221f8cf..2927325f0d88c 100644 --- a/packages/next/src/build/entries.ts +++ b/packages/next/src/build/entries.ts @@ -45,6 +45,7 @@ import { isInstrumentationHookFilename, } from './utils' import { getPageStaticInfo } from './analysis/get-page-static-info' +import { getDefaultMiddlewareMatcher } from '../shared/lib/router/utils/get-default-middleware-matcher' import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' import type { ServerRuntime } from '../types' @@ -898,7 +899,7 @@ export async function createEntrypoints( if (isMiddlewareFile(page)) { middlewareMatchers = staticInfo.middleware?.matchers ?? [ - { regexp: '.*', originalSource: '/:path*' }, + getDefaultMiddlewareMatcher(params.config), ] } diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index fd059406d324a..735b75db15fc5 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -150,6 +150,7 @@ import { isEdgeRuntime } from '../lib/is-edge-runtime' import { recursiveCopy } from '../lib/recursive-copy' import { lockfilePatchPromise, teardownTraceSubscriber } from './swc' import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex' +import { getDefaultMiddlewareMatcher } from '../shared/lib/router/utils/get-default-middleware-matcher' import { getFilesInDir } from '../lib/get-files-in-dir' import { eventSwcPlugins } from '../telemetry/events/swc-plugins' import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' @@ -2590,10 +2591,7 @@ export default async function build( functionsConfigManifest.functions['/_middleware'] = { runtime: staticInfo.runtime, matchers: staticInfo.middleware?.matchers ?? [ - { - regexp: '^.*$', - originalSource: '/:path*', - }, + getDefaultMiddlewareMatcher(config), ], } diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 04b9789405093..09e65bdc933bd 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -2081,6 +2081,7 @@ export default async function getBaseWebpackConfig( dev, sriEnabled: !dev && !!config.experimental.sri?.algorithm, rewrites, + nextConfig: config, edgeEnvironments: { __NEXT_BUILD_ID: buildId, NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: encryptionKey, diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index 3f36ac8b09026..1d78740f0e0b9 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -5,6 +5,7 @@ import type { import type { EdgeSSRMeta } from '../loaders/get-module-build-info' import type { MiddlewareMatcher } from '../../analysis/get-page-static-info' import { getNamedMiddlewareRegex } from '../../../shared/lib/router/utils/route-regex' +import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher' import { getModuleBuildInfo } from '../loaders/get-module-build-info' import { getSortedRoutes } from '../../../shared/lib/router/utils' import { webpack, sources } from 'next/dist/compiled/webpack/webpack' @@ -36,6 +37,7 @@ import type { CustomRoutes } from '../../../lib/load-custom-routes' import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites' import { getDynamicCodeEvaluationError } from './wellknown-errors-plugin/parse-dynamic-code-evaluation-error' import { getModuleReferencesInOrder } from '../utils' +import type { NextConfigComplete } from '../../../server/config-shared' const KNOWN_SAFE_DYNAMIC_PACKAGES = require('../../../lib/known-edge-safe-packages.json') as string[] @@ -195,21 +197,29 @@ function getCreateAssets(params: { continue } - const matcherSource = metadata.edgeSSR?.isAppDir - ? normalizeAppPath(page) - : page - - const catchAll = !metadata.edgeSSR && !metadata.edgeApiFunction - - const { namedRegex } = getNamedMiddlewareRegex(matcherSource, { - catchAll, - }) - const matchers = metadata?.edgeMiddleware?.matchers ?? [ - { - regexp: namedRegex, - originalSource: page === '/' && catchAll ? '/:path*' : matcherSource, - }, - ] + let matchers: MiddlewareMatcher[] + if (metadata?.edgeMiddleware?.matchers) { + matchers = metadata.edgeMiddleware.matchers + } else { + // For middleware at root with no explicit matchers, use getDefaultMiddlewareMatcher + // which respects skipMiddlewareNextInternalRoutes config + const catchAll = !metadata.edgeSSR && !metadata.edgeApiFunction + if (page === '/' && catchAll) { + matchers = [getDefaultMiddlewareMatcher(opts.nextConfig)] + } else { + const matcherSource = metadata.edgeSSR?.isAppDir + ? normalizeAppPath(page) + : page + matchers = [ + { + regexp: getNamedMiddlewareRegex(matcherSource, { + catchAll, + }).namedRegex, + originalSource: matcherSource, + }, + ] + } + } const isEdgeFunction = !!(metadata.edgeApiFunction || metadata.edgeSSR) const edgeFunctionDefinition: EdgeFunctionDefinition = { @@ -818,6 +828,7 @@ interface Options { sriEnabled: boolean rewrites: CustomRoutes['rewrites'] edgeEnvironments: EdgeRuntimeEnvironments + nextConfig: NextConfigComplete } export default class MiddlewarePlugin { @@ -825,12 +836,20 @@ export default class MiddlewarePlugin { private readonly sriEnabled: Options['sriEnabled'] private readonly rewrites: Options['rewrites'] private readonly edgeEnvironments: EdgeRuntimeEnvironments - - constructor({ dev, sriEnabled, rewrites, edgeEnvironments }: Options) { + private readonly nextConfig: Options['nextConfig'] + + constructor({ + dev, + sriEnabled, + rewrites, + edgeEnvironments, + nextConfig, + }: Options) { this.dev = dev this.sriEnabled = sriEnabled this.rewrites = rewrites this.edgeEnvironments = edgeEnvironments + this.nextConfig = nextConfig } public apply(compiler: webpack.Compiler) { @@ -886,6 +905,7 @@ export default class MiddlewarePlugin { rewrites: this.rewrites, edgeEnvironments: this.edgeEnvironments, dev: this.dev, + nextConfig: this.nextConfig, }, }) ) diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 31ddf66d65a7c..072a0c4abcd8c 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -677,6 +677,7 @@ export const configSchema: zod.ZodType = z.lazy(() => serverExternalPackages: z.array(z.string()).optional(), serverRuntimeConfig: z.record(z.string(), z.any()).optional(), skipMiddlewareUrlNormalize: z.boolean().optional(), + skipMiddlewareNextInternalRoutes: z.boolean().optional(), skipTrailingSlashRedirect: z.boolean().optional(), staticPageGenerationTimeout: z.number().optional(), expireTime: z.number().optional(), diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 5ec7cf193f2ca..644c7c93cd3e2 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -1249,6 +1249,12 @@ export interface NextConfig { skipMiddlewareUrlNormalize?: boolean + /** + * Skip Next.js internals route `/_next` from middleware. + * @default true + */ + skipMiddlewareNextInternalRoutes?: boolean + skipTrailingSlashRedirect?: boolean modularizeImports?: Record< @@ -1529,6 +1535,7 @@ export const defaultConfig = Object.freeze({ }, htmlLimitedBots: undefined, bundlePagesRouterDependencies: false, + skipMiddlewareNextInternalRoutes: true, } satisfies NextConfig) export async function normalizeConfig(phase: string, config: any) { diff --git a/packages/next/src/server/lib/router-utils/filesystem.ts b/packages/next/src/server/lib/router-utils/filesystem.ts index cd276cd69eb58..be19679c7878f 100644 --- a/packages/next/src/server/lib/router-utils/filesystem.ts +++ b/packages/next/src/server/lib/router-utils/filesystem.ts @@ -31,6 +31,7 @@ import { pathHasPrefix } from '../../../shared/lib/router/utils/path-has-prefix' import { normalizeLocalePath } from '../../../shared/lib/i18n/normalize-locale-path' import { removePathPrefix } from '../../../shared/lib/router/utils/remove-path-prefix' import { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher' +import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher' import { APP_PATH_ROUTES_MANIFEST, BUILD_ID_FILE, @@ -314,7 +315,7 @@ export async function setupFsCheck(opts: { } else if (functionsConfigManifest?.functions['/_middleware']) { middlewareMatcher = getMiddlewareRouteMatcher( functionsConfigManifest.functions['/_middleware'].matchers ?? [ - { regexp: '.*', originalSource: '/:path*' }, + getDefaultMiddlewareMatcher(opts.config), ] ) } diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index 0df31c045d5a2..c66090d75a19d 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -48,6 +48,7 @@ import { } from '../../../shared/lib/constants' import { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher' +import { getDefaultMiddlewareMatcher } from '../../../shared/lib/router/utils/get-default-middleware-matcher' import { isMiddlewareFile, @@ -472,7 +473,7 @@ async function startWatcher( serverFields.actualMiddlewareFile ) middlewareMatchers = staticInfo.middleware?.matchers || [ - { regexp: '^/.*$', originalSource: '/:path*' }, + getDefaultMiddlewareMatcher(opts.nextConfig), ] continue } diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 7e5f627ffb62c..cdc8731129d2c 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -71,6 +71,7 @@ import type { LoadComponentsReturnType } from './load-components' import isError, { getProperError } from '../lib/is-error' import { splitCookiesString, toNodeOutgoingHttpHeaders } from './web/utils' import { getMiddlewareRouteMatcher } from '../shared/lib/router/utils/middleware-route-matcher' +import { getDefaultMiddlewareMatcher } from '../shared/lib/router/utils/get-default-middleware-matcher' import { loadEnvConfig } from '@next/env' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' @@ -1455,13 +1456,13 @@ export default class NextNodeServer extends BaseServer< const middlewareModule = await this.loadNodeMiddleware() if (middlewareModule) { + const matchers = middlewareModule.config?.matchers || [ + getDefaultMiddlewareMatcher(this.nextConfig), + ] return { - match: getMiddlewareRouteMatcher( - middlewareModule.config?.matchers || [ - { regexp: '.*', originalSource: '/:path*' }, - ] - ), + match: getMiddlewareRouteMatcher(matchers), page: '/', + matchers, } } @@ -1471,6 +1472,7 @@ export default class NextNodeServer extends BaseServer< return { match: getMiddlewareMatcher(middleware), page: '/', + matchers: middleware.matchers, } } diff --git a/packages/next/src/shared/lib/router/utils/get-default-middleware-matcher.ts b/packages/next/src/shared/lib/router/utils/get-default-middleware-matcher.ts new file mode 100644 index 0000000000000..3d5c8830b0e27 --- /dev/null +++ b/packages/next/src/shared/lib/router/utils/get-default-middleware-matcher.ts @@ -0,0 +1,21 @@ +import type { NextConfig } from '../../../../types' +import type { MiddlewareMatcher } from '../../../../build/analysis/get-page-static-info' + +export function getDefaultMiddlewareMatcher({ + skipMiddlewareNextInternalRoutes, +}: NextConfig): MiddlewareMatcher { + if (skipMiddlewareNextInternalRoutes !== false) { + // Skip "/_next/" internal routes, except for "/_next/data/" which is needed for + // client-side navigation. Do not consider basePath as the user cannot create a + // route starts with underscore. + return { + regexp: '^(?!.*\\/\\_next\\/(?!data\\/)).*', + originalSource: '/((?!_next/(?!data/))[^]*)*', + } + } + + return { + regexp: '^/.*$', + originalSource: '/:path*', + } +} diff --git a/test/e2e/middleware-general/test/index.test.ts b/test/e2e/middleware-general/test/index.test.ts index 0d616d10b5132..3285390b43ca2 100644 --- a/test/e2e/middleware-general/test/index.test.ts +++ b/test/e2e/middleware-general/test/index.test.ts @@ -39,6 +39,8 @@ describe('Middleware Runtime', () => { ), }, nextConfig: { + // This test needs to intercept internal routes /_next/ (skipped by default) + skipMiddlewareNextInternalRoutes: false, experimental: { webpackBuildWorker: true, }, @@ -127,7 +129,7 @@ describe('Middleware Runtime', () => { runtime: 'nodejs', matchers: [ { - regexp: '^.*$', + regexp: '^/.*$', originalSource: '/:path*', }, ], diff --git a/test/e2e/middleware-skip-next-internal-routes-base-path/app/layout.tsx b/test/e2e/middleware-skip-next-internal-routes-base-path/app/layout.tsx new file mode 100644 index 0000000000000..08eaa94fdc889 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-base-path/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/test/e2e/middleware-skip-next-internal-routes-base-path/app/page.tsx b/test/e2e/middleware-skip-next-internal-routes-base-path/app/page.tsx new file mode 100644 index 0000000000000..ff7159d9149fe --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-base-path/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/middleware-skip-next-internal-routes-base-path/middleware-skip-next-internal-routes-base-path.test.ts b/test/e2e/middleware-skip-next-internal-routes-base-path/middleware-skip-next-internal-routes-base-path.test.ts new file mode 100644 index 0000000000000..2fb46031a98bf --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-base-path/middleware-skip-next-internal-routes-base-path.test.ts @@ -0,0 +1,18 @@ +import { nextTestSetup } from 'e2e-utils' + +describe('middleware skip Next.js internal routes with base path', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should execute middleware on regular routes', async () => { + const res = await next.fetch('/base') + expect(res.status).toBe(200) + expect(res.headers.get('x-middleware-executed')).toBe('true') + }) + + it('should NOT execute middleware on _next routes', async () => { + const res = await next.fetch('/base/_next/static/chunks/webpack.js') + expect(res.headers.get('x-middleware-executed')).toBeFalsy() + }) +}) diff --git a/test/e2e/middleware-skip-next-internal-routes-base-path/middleware.ts b/test/e2e/middleware-skip-next-internal-routes-base-path/middleware.ts new file mode 100644 index 0000000000000..97426e940d1f7 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-base-path/middleware.ts @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server' + +export function middleware() { + const response = NextResponse.next() + response.headers.set('x-middleware-executed', 'true') + return response +} diff --git a/test/e2e/middleware-skip-next-internal-routes-base-path/next.config.js b/test/e2e/middleware-skip-next-internal-routes-base-path/next.config.js new file mode 100644 index 0000000000000..fa1d25f445b24 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-base-path/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + basePath: '/base', +} + +module.exports = nextConfig diff --git a/test/e2e/middleware-skip-next-internal-routes-opt-out/app/layout.tsx b/test/e2e/middleware-skip-next-internal-routes-opt-out/app/layout.tsx new file mode 100644 index 0000000000000..08eaa94fdc889 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-opt-out/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/test/e2e/middleware-skip-next-internal-routes-opt-out/app/page.tsx b/test/e2e/middleware-skip-next-internal-routes-opt-out/app/page.tsx new file mode 100644 index 0000000000000..ff7159d9149fe --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-opt-out/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware-skip-next-internal-routes-opt-out.test.ts b/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware-skip-next-internal-routes-opt-out.test.ts new file mode 100644 index 0000000000000..9e4f81c511742 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware-skip-next-internal-routes-opt-out.test.ts @@ -0,0 +1,18 @@ +import { nextTestSetup } from 'e2e-utils' + +describe('middleware skip Next.js internal routes (opt-out)', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should execute middleware on regular routes', async () => { + const res = await next.fetch('/') + expect(res.status).toBe(200) + expect(res.headers.get('x-middleware-executed')).toBe('true') + }) + + it('should ALSO execute middleware on _next routes when opted out', async () => { + const res = await next.fetch('/_next/static/chunks/webpack.js') + expect(res.headers.get('x-middleware-executed')).toBe('true') + }) +}) diff --git a/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware.ts b/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware.ts new file mode 100644 index 0000000000000..97426e940d1f7 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-opt-out/middleware.ts @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server' + +export function middleware() { + const response = NextResponse.next() + response.headers.set('x-middleware-executed', 'true') + return response +} diff --git a/test/e2e/middleware-skip-next-internal-routes-opt-out/next.config.js b/test/e2e/middleware-skip-next-internal-routes-opt-out/next.config.js new file mode 100644 index 0000000000000..a590bd7b5ce14 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes-opt-out/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + skipMiddlewareNextInternalRoutes: false, +} + +module.exports = nextConfig diff --git a/test/e2e/middleware-skip-next-internal-routes/app/layout.tsx b/test/e2e/middleware-skip-next-internal-routes/app/layout.tsx new file mode 100644 index 0000000000000..08eaa94fdc889 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/test/e2e/middleware-skip-next-internal-routes/app/page.tsx b/test/e2e/middleware-skip-next-internal-routes/app/page.tsx new file mode 100644 index 0000000000000..ff7159d9149fe --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/middleware-skip-next-internal-routes/middleware-skip-next-internal-routes.test.ts b/test/e2e/middleware-skip-next-internal-routes/middleware-skip-next-internal-routes.test.ts new file mode 100644 index 0000000000000..9fa90820d5759 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes/middleware-skip-next-internal-routes.test.ts @@ -0,0 +1,18 @@ +import { nextTestSetup } from 'e2e-utils' + +describe('middleware skip Next.js internal routes', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should execute middleware on regular routes', async () => { + const res = await next.fetch('/') + expect(res.status).toBe(200) + expect(res.headers.get('x-middleware-executed')).toBe('true') + }) + + it('should NOT execute middleware on _next routes', async () => { + const res = await next.fetch('/_next/static/chunks/webpack.js') + expect(res.headers.get('x-middleware-executed')).toBeFalsy() + }) +}) diff --git a/test/e2e/middleware-skip-next-internal-routes/middleware.ts b/test/e2e/middleware-skip-next-internal-routes/middleware.ts new file mode 100644 index 0000000000000..97426e940d1f7 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes/middleware.ts @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server' + +export function middleware() { + const response = NextResponse.next() + response.headers.set('x-middleware-executed', 'true') + return response +} diff --git a/test/e2e/middleware-skip-next-internal-routes/next.config.js b/test/e2e/middleware-skip-next-internal-routes/next.config.js new file mode 100644 index 0000000000000..767719fc4fba5 --- /dev/null +++ b/test/e2e/middleware-skip-next-internal-routes/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/test/e2e/middleware-trailing-slash/app/next.config.js b/test/e2e/middleware-trailing-slash/app/next.config.js index 0c482151c3a26..7bbcc25aa2ac6 100644 --- a/test/e2e/middleware-trailing-slash/app/next.config.js +++ b/test/e2e/middleware-trailing-slash/app/next.config.js @@ -1,4 +1,6 @@ module.exports = { + // This test needs to intercept internal routes /_next/ (skipped by default) + skipMiddlewareNextInternalRoutes: false, trailingSlash: true, redirects() { return [ diff --git a/test/e2e/skip-trailing-slash-redirect/app/next.config.js b/test/e2e/skip-trailing-slash-redirect/app/next.config.js index 5b0be24a8af11..ec8f1f73a1db3 100644 --- a/test/e2e/skip-trailing-slash-redirect/app/next.config.js +++ b/test/e2e/skip-trailing-slash-redirect/app/next.config.js @@ -1,5 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + // This test needs to intercept internal routes /_next/ (skipped by default) + skipMiddlewareNextInternalRoutes: false, skipMiddlewareUrlNormalize: true, skipTrailingSlashRedirect: true, experimental: { diff --git a/test/integration/next-image-new/middleware/next.config.js b/test/integration/next-image-new/middleware/next.config.js new file mode 100644 index 0000000000000..613d90fa882e7 --- /dev/null +++ b/test/integration/next-image-new/middleware/next.config.js @@ -0,0 +1,4 @@ +module.exports = { + // This test needs to intercept internal routes /_next/ (skipped by default) + skipMiddlewareNextInternalRoutes: false, +}