Skip to content

Commit d7a5820

Browse files
devjiwonchoibgw
andauthored
Deprecate middleware and recommend proxy (#84119)
We recommend users avoid relying on Middleware unless no other options exist. Our goal is to give them APIs with better ergonomics so they can achieve their goals without Middleware. The term “middleware” often confuses users with Express.js middleware, which can encourage misuse. To clarify our direction, we are renaming the file convention to “proxy.” This highlights that we are moving away from Middleware, breaking down its overloaded features, and making the Proxy clear in its purpose. `proxy.js` should be a drop-in replacement for `middleware.js` for its features, but ONE thing: ```diff - export function middleware + export function proxy ``` Codemod: #84127 --------- Co-authored-by: Benjamin Woodruff <[email protected]>
1 parent d81ef26 commit d7a5820

36 files changed

+759
-23
lines changed

crates/next-core/src/middleware.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::util::load_next_js_template;
99
#[turbo_tasks::function]
1010
pub async fn middleware_files(page_extensions: Vc<Vec<RcStr>>) -> Result<Vc<Vec<RcStr>>> {
1111
let extensions = page_extensions.await?;
12-
let files = ["middleware.", "src/middleware."]
12+
let files = ["middleware.", "src/middleware.", "proxy.", "src/proxy."]
1313
.into_iter()
1414
.flat_map(|f| {
1515
extensions
@@ -29,14 +29,16 @@ pub async fn get_middleware_module(
2929
) -> Result<Vc<Box<dyn Module>>> {
3030
const INNER: &str = "INNER_MIDDLEWARE_MODULE";
3131

32+
// Determine if this is a proxy file by checking the module path
33+
let userland_path = userland_module.ident().path().await?;
34+
let is_proxy = userland_path.file_stem() == Some("proxy");
35+
let page_path = if is_proxy { "/proxy" } else { "/middleware" };
36+
3237
// Load the file from the next.js codebase.
3338
let source = load_next_js_template(
3439
"middleware.js",
3540
project_root,
36-
&[
37-
("VAR_USERLAND", INNER),
38-
("VAR_DEFINITION_PAGE", "/middleware"),
39-
],
41+
&[("VAR_USERLAND", INNER), ("VAR_DEFINITION_PAGE", page_path)],
4042
&[],
4143
&[],
4244
)

packages/next/errors.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,5 +863,7 @@
863863
"862": "Request body exceeded %s",
864864
"863": "\\`<Link legacyBehavior>\\` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link's \\`<a>\\` tag.",
865865
"864": "Missing value for segment key: \"%s\" with dynamic param type: %s",
866-
"865": "`experimental.rdcForNavigations` is enabled, but `experimental.cacheComponents` is not."
866+
"865": "`experimental.rdcForNavigations` is enabled, but `experimental.cacheComponents` is not.",
867+
"866": "Both \"%s\" and \"%s\" files are detected. Please use \"%s\" instead.",
868+
"867": "The %s \"%s\" must export a %s or a \\`default\\` function"
867869
}

packages/next/src/build/entries.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
APP_DIR_ALIAS,
2121
WEBPACK_LAYERS,
2222
INSTRUMENTATION_HOOK_FILENAME,
23+
PROXY_FILENAME,
24+
MIDDLEWARE_FILENAME,
2325
} from '../lib/constants'
2426
import { isAPIRoute } from '../lib/is-api-route'
2527
import { isEdgeRuntime } from '../lib/is-edge-runtime'
@@ -948,7 +950,13 @@ export async function createEntrypoints(
948950
isDev: false,
949951
})
950952
} else if (isMiddlewareFile(page)) {
951-
server[serverBundlePath.replace('src/', '')] = getEdgeServerEntry({
953+
server[
954+
serverBundlePath
955+
// proxy.js still uses middleware.js for bundle path for now.
956+
// TODO: Revisit when we remove middleware.js.
957+
.replace(PROXY_FILENAME, MIDDLEWARE_FILENAME)
958+
.replace('src/', '')
959+
] = getEdgeServerEntry({
952960
...params,
953961
rootDir,
954962
absolutePagePath: absolutePagePath,
@@ -1023,7 +1031,12 @@ export async function createEntrypoints(
10231031
: undefined,
10241032
}).import
10251033
}
1026-
edgeServer[serverBundlePath] = getEdgeServerEntry({
1034+
const edgeServerBundlePath = isMiddlewareFile(page)
1035+
? serverBundlePath
1036+
.replace(PROXY_FILENAME, MIDDLEWARE_FILENAME)
1037+
.replace('src/', '')
1038+
: serverBundlePath
1039+
edgeServer[edgeServerBundlePath] = getEdgeServerEntry({
10271040
...params,
10281041
rootDir,
10291042
absolutePagePath: absolutePagePath,

packages/next/src/build/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR,
2222
PUBLIC_DIR_MIDDLEWARE_CONFLICT,
2323
MIDDLEWARE_FILENAME,
24+
PROXY_FILENAME,
2425
PAGES_DIR_ALIAS,
2526
INSTRUMENTATION_HOOK_FILENAME,
2627
RSC_PREFETCH_SUFFIX,
@@ -1163,6 +1164,10 @@ export default async function build(
11631164
`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
11641165
)
11651166

1167+
const proxyDetectionRegExp = new RegExp(
1168+
`^${PROXY_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
1169+
)
1170+
11661171
const instrumentationHookDetectionRegExp = new RegExp(
11671172
`^${INSTRUMENTATION_HOOK_FILENAME}\\.(?:${config.pageExtensions.join(
11681173
'|'
@@ -1172,6 +1177,7 @@ export default async function build(
11721177
const rootDir = path.join((pagesDir || appDir)!, '..')
11731178
const includes = [
11741179
middlewareDetectionRegExp,
1180+
proxyDetectionRegExp,
11751181
instrumentationHookDetectionRegExp,
11761182
]
11771183

@@ -1186,6 +1192,17 @@ export default async function build(
11861192
const hasMiddlewareFile = rootPaths.some((p) =>
11871193
p.includes(MIDDLEWARE_FILENAME)
11881194
)
1195+
const hasProxyFile = rootPaths.some((p) => p.includes(PROXY_FILENAME))
1196+
if (hasMiddlewareFile) {
1197+
if (hasProxyFile) {
1198+
throw new Error(
1199+
`Both "${MIDDLEWARE_FILENAME}" and "${PROXY_FILENAME}" files are detected. Please use "${PROXY_FILENAME}" instead.`
1200+
)
1201+
}
1202+
Log.warn(
1203+
`The "${MIDDLEWARE_FILENAME}" file convention is deprecated. Please use "${PROXY_FILENAME}" instead.`
1204+
)
1205+
}
11891206

11901207
NextBuildContext.hasInstrumentationHook = hasInstrumentationHook
11911208

@@ -2543,8 +2560,8 @@ export default async function build(
25432560
return serverFilesManifest
25442561
})
25452562

2546-
const middlewareFile = rootPaths.find((p) =>
2547-
p.includes(MIDDLEWARE_FILENAME)
2563+
const middlewareFile = rootPaths.find(
2564+
(p) => p.includes(MIDDLEWARE_FILENAME) || p.includes(PROXY_FILENAME)
25482565
)
25492566
let hasNodeMiddleware = false
25502567

@@ -3918,7 +3935,7 @@ export default async function build(
39183935
rewritesWithHasCount: combinedRewrites.filter((r: any) => !!r.has)
39193936
.length,
39203937
redirectsWithHasCount: redirects.filter((r: any) => !!r.has).length,
3921-
middlewareCount: hasMiddlewareFile ? 1 : 0,
3938+
middlewareCount: hasMiddlewareFile || hasProxyFile ? 1 : 0,
39223939
totalAppPagesCount,
39233940
staticAppPagesCount,
39243941
serverAppPagesCount,

packages/next/src/build/templates/middleware.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import { edgeInstrumentationOnRequestError } from '../../server/web/globals'
1010
import { isNextRouterError } from '../../client/components/is-next-router-error'
1111

1212
const mod = { ..._mod }
13-
const handler = mod.middleware || mod.default
1413

1514
const page = 'VAR_DEFINITION_PAGE'
15+
// @ts-expect-error `page` will be replaced during build
16+
const isProxy = page === '/proxy' || page === '/src/proxy'
17+
const handler = (isProxy ? mod.proxy : mod.middleware) || mod.default
1618

1719
if (typeof handler !== 'function') {
1820
throw new Error(
19-
`The Middleware "${page}" must export a \`middleware\` or a \`default\` function`
21+
`The ${isProxy ? 'Proxy' : 'Middleware'} "${page}" must export a ${isProxy ? '`proxy`' : '`middleware`'} or a \`default\` function`
2022
)
2123
}
2224

packages/next/src/build/utils.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
SERVER_PROPS_SSG_CONFLICT,
2323
SSG_GET_INITIAL_PROPS_CONFLICT,
2424
WEBPACK_LAYERS,
25+
PROXY_FILENAME,
2526
} from '../lib/constants'
2627
import type {
2728
AppPageModule,
@@ -96,7 +97,12 @@ export function difference<T>(
9697
}
9798

9899
export function isMiddlewareFilename(file?: string | null) {
99-
return file === MIDDLEWARE_FILENAME || file === `src/${MIDDLEWARE_FILENAME}`
100+
return (
101+
file === MIDDLEWARE_FILENAME ||
102+
file === `src/${MIDDLEWARE_FILENAME}` ||
103+
file === PROXY_FILENAME ||
104+
file === `src/${PROXY_FILENAME}`
105+
)
100106
}
101107

102108
export function isInstrumentationHookFilename(file?: string | null) {
@@ -489,7 +495,7 @@ export async function printTreeView(
489495
const middlewareInfo = middlewareManifest.middleware?.['/']
490496
if (middlewareInfo?.files.length > 0) {
491497
messages.push([])
492-
messages.push(['ƒ Middleware'])
498+
messages.push([Proxy (Middleware)'])
493499
}
494500

495501
print(
@@ -1375,7 +1381,10 @@ export function isCustomErrorPage(page: string) {
13751381

13761382
export function isMiddlewareFile(file: string) {
13771383
return (
1378-
file === `/${MIDDLEWARE_FILENAME}` || file === `/src/${MIDDLEWARE_FILENAME}`
1384+
file === `/${MIDDLEWARE_FILENAME}` ||
1385+
file === `/src/${MIDDLEWARE_FILENAME}` ||
1386+
file === `/${PROXY_FILENAME}` ||
1387+
file === `/src/${PROXY_FILENAME}`
13791388
)
13801389
}
13811390

@@ -1405,9 +1414,10 @@ export function getPossibleMiddlewareFilenames(
14051414
folder: string,
14061415
extensions: string[]
14071416
) {
1408-
return extensions.map((extension) =>
1409-
path.join(folder, `${MIDDLEWARE_FILENAME}.${extension}`)
1410-
)
1417+
return extensions.flatMap((extension) => [
1418+
path.join(folder, `${MIDDLEWARE_FILENAME}.${extension}`),
1419+
path.join(folder, `${PROXY_FILENAME}.${extension}`),
1420+
])
14111421
}
14121422

14131423
export class NestedMiddlewareError extends Error {

packages/next/src/build/webpack/loaders/next-middleware-loader.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type {
33
MiddlewareMatcher,
44
} from '../../analysis/get-page-static-info'
55
import { getModuleBuildInfo } from './get-module-build-info'
6-
import { MIDDLEWARE_LOCATION_REGEXP } from '../../../lib/constants'
6+
import {
7+
MIDDLEWARE_LOCATION_REGEXP,
8+
PROXY_LOCATION_REGEXP,
9+
} from '../../../lib/constants'
710
import { loadEntrypoint } from '../../load-entrypoint'
811

912
export type MiddlewareLoaderOptions = {
@@ -49,7 +52,12 @@ export default async function middlewareLoader(this: any) {
4952
buildInfo.nextEdgeMiddleware = {
5053
matchers,
5154
page:
52-
page.replace(new RegExp(`/${MIDDLEWARE_LOCATION_REGEXP}$`), '') || '/',
55+
page.replace(
56+
new RegExp(
57+
`/(${MIDDLEWARE_LOCATION_REGEXP}|${PROXY_LOCATION_REGEXP})$`
58+
),
59+
''
60+
) || '/',
5361
}
5462
buildInfo.rootDir = rootDir
5563
buildInfo.route = {

packages/next/src/lib/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export const INFINITE_CACHE = 0xfffffffe
4646
export const MIDDLEWARE_FILENAME = 'middleware'
4747
export const MIDDLEWARE_LOCATION_REGEXP = `(?:src/)?${MIDDLEWARE_FILENAME}`
4848

49+
// Patterns to detect proxy files (replacement for middleware)
50+
export const PROXY_FILENAME = 'proxy'
51+
export const PROXY_LOCATION_REGEXP = `(?:src/)?${PROXY_FILENAME}`
52+
4953
// Pattern to detect instrumentation hooks file
5054
export const INSTRUMENTATION_HOOK_FILENAME = 'instrumentation'
5155

packages/next/src/server/dev/hot-reloader-turbopack.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,8 @@ export async function createHotReloaderTurbopack(
13111311
// TODO: why is this entry missing in turbopack?
13121312
if (page === '/middleware') return
13131313
if (page === '/src/middleware') return
1314+
if (page === '/proxy') return
1315+
if (page === '/src/proxy') return
13141316
if (page === '/instrumentation') return
13151317
if (page === '/src/instrumentation') return
13161318

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ import {
7676
import { getDefineEnv } from '../../../build/define-env'
7777
import { TurbopackInternalError } from '../../../shared/lib/turbopack/internal-error'
7878
import { normalizePath } from '../../../lib/normalize-path'
79-
import { JSON_CONTENT_TYPE_HEADER } from '../../../lib/constants'
79+
import {
80+
JSON_CONTENT_TYPE_HEADER,
81+
MIDDLEWARE_FILENAME,
82+
PROXY_FILENAME,
83+
} from '../../../lib/constants'
8084
import {
8185
createRouteTypesManifest,
8286
writeRouteTypesManifest,
@@ -387,6 +391,29 @@ async function startWatcher(
387391
sortByPageExts(nextConfig.pageExtensions)
388392
)
389393

394+
let hasMiddlewareFile = false
395+
let hasProxyFile = false
396+
for (const fileName of sortedKnownFiles) {
397+
const { name } = path.parse(fileName)
398+
if (name === MIDDLEWARE_FILENAME) {
399+
hasMiddlewareFile = true
400+
}
401+
if (name === PROXY_FILENAME) {
402+
hasProxyFile = true
403+
}
404+
}
405+
406+
if (hasMiddlewareFile) {
407+
if (hasProxyFile) {
408+
throw new Error(
409+
`Both "${MIDDLEWARE_FILENAME}" and "${PROXY_FILENAME}" files are detected. Please use "${PROXY_FILENAME}" instead.`
410+
)
411+
}
412+
Log.warn(
413+
`The "${MIDDLEWARE_FILENAME}" file convention is deprecated. Please use "${PROXY_FILENAME}" instead.`
414+
)
415+
}
416+
390417
for (const fileName of sortedKnownFiles) {
391418
if (
392419
!files.includes(fileName) &&
@@ -467,6 +494,7 @@ async function startWatcher(
467494
continue
468495
}
469496
serverFields.actualMiddlewareFile = rootFile
497+
470498
await propagateServerField(
471499
opts,
472500
'actualMiddlewareFile',

0 commit comments

Comments
 (0)