Skip to content

Commit 38f006b

Browse files
committed
Deprecate nested middleware
1 parent 40b4512 commit 38f006b

File tree

8 files changed

+126
-39
lines changed

8 files changed

+126
-39
lines changed

errors/manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,10 @@
626626
"title": "middleware-relative-urls",
627627
"path": "/errors/middleware-relative-urls.md"
628628
},
629+
{
630+
"title": "nested-middleware",
631+
"path": "/errors/nested-middleware.md"
632+
},
629633
{
630634
"title": "deleting-query-params-in-middlewares",
631635
"path": "/errors/deleting-query-params-in-middlewares.md"

errors/nested-middleware.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Nested Middleware Deprecated
2+
3+
#### Why This Error Occurred
4+
5+
You are defining a middleware file in a location different from `pages/_middleware` which is _deprecated_.
6+
7+
Declaring a middleware file under specific pages brought the illusion that a middleware would be executed _only_ when pages below its declaration were matched but this wasn't the case.
8+
Supporting this API also comes with other consequences like allowing to nest multiple middleware or dragging effects between different middleware executions.
9+
10+
This makes the execution model for middleware **really complex** and hard to reason about so this API is _deprecated_ in favour of a simpler model with a single root middleware.
11+
12+
#### Possible Ways to Fix It
13+
14+
To fix this error you must declare your middleware in the root pages folder and leverage `NextRequest` parsed URL to decide wether or not to execute the middleware code.
15+
For example, a middleware declared under `pages/about/_middleware.js` can be moved to `pages/_middleware` and updated so that it only executes when `about/*` matches:
16+
17+
```typescript
18+
import type { NextRequest } from 'next/server'
19+
20+
export function middleware(request: NextRequest) {
21+
if (request.nextUrl.pathname.startsWith('/about')) {
22+
// Execute pages/about/_middleware.js
23+
}
24+
}
25+
```
26+
27+
This also means that if you have more than one middleware you will need to reunite them in a single file and model their execution depending on the request.

packages/next/build/entries.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ export function createPagesMapping({
102102
{}
103103
)
104104

105-
// In development we always alias these to allow Webpack to fallback to
106-
// the correct source file so that HMR can work properly when a file is
107-
// added or removed.
108-
109105
if (isViews) {
110106
return pages
111107
}
@@ -117,7 +113,11 @@ export function createPagesMapping({
117113
delete pages['/_document']
118114
}
119115

116+
// In development we always alias these to allow Webpack to fallback to
117+
// the correct source file so that HMR can work properly when a file is
118+
// added or removed.
120119
const root = isDev ? PAGES_DIR_ALIAS : 'next/dist/pages'
120+
121121
return {
122122
'/_app': `${root}/_app`,
123123
'/_error': `${root}/_error`,
@@ -396,6 +396,17 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
396396
return require.resolve(absolutePagePath)
397397
})()
398398

399+
/**
400+
* When we find a middleware that is declared in the pages/ root we fail.
401+
* There is no need to check on `dev` as this should only happen when
402+
* building for production.
403+
*/
404+
if (/[\\\\/]_middleware$/.test(page) && page !== '/_middleware') {
405+
throw new Error(
406+
`nested Middleware is deprecated (found pages${page}) - https://nextjs.org/docs/messages/nested-middleware`
407+
)
408+
}
409+
399410
runDependingOnPageType({
400411
page,
401412
pageRuntime: await getPageRuntime(pageFilePath, config, isDev),

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,6 @@ export default class DevServer extends Server {
249249
return
250250
}
251251

252-
const regexMiddleware = new RegExp(
253-
`[\\\\/](_middleware.(?:${this.nextConfig.pageExtensions.join('|')}))$`
254-
)
255-
256252
const regexPageExtension = new RegExp(
257253
`\\.+(?:${this.nextConfig.pageExtensions.join('|')})$`
258254
)
@@ -280,7 +276,7 @@ export default class DevServer extends Server {
280276
wp.watch([], toWatch, 0)
281277

282278
wp.on('aggregated', async () => {
283-
const routedMiddleware = []
279+
const routedMiddleware: string[] = []
284280
const routedPages: string[] = []
285281
const knownFiles = wp.getTimeInfoEntries()
286282
const viewPaths: Record<string, string> = {}
@@ -318,11 +314,18 @@ export default class DevServer extends Server {
318314
pageName = pageName.replace(/\/index$/, '') || '/'
319315
}
320316

321-
if (regexMiddleware.test(fileName)) {
322-
routedMiddleware.push(
323-
`/${relative(this.pagesDir, fileName).replace(/\\+/g, '/')}`
324-
.replace(/^\/+/g, '/')
325-
.replace(regexMiddleware, '/')
317+
if (pageName === '/_middleware') {
318+
routedMiddleware.push(`/`)
319+
continue
320+
}
321+
322+
/**
323+
* If there is a middleware that is not declared in the root we will
324+
* warn without adding it so it doesn't make its way into the system.
325+
*/
326+
if (/[\\\\/]_middleware$/.test(pageName)) {
327+
Log.warn(
328+
`nested Middleware is deprecated (found pages${pageName}) - https://nextjs.org/docs/messages/nested-middleware`
326329
)
327330
continue
328331
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function middleware() {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function About() {
2+
return (
3+
<div>
4+
<h1 className="title">About Page</h1>
5+
</div>
6+
)
7+
}

test/integration/middleware-module-errors/test/index.test.js

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ import {
1313
waitFor,
1414
} from 'next-test-utils'
1515

16-
const context = {}
17-
const WEBPACK_BREAKING_CHANGE = 'BREAKING CHANGE:'
18-
1916
jest.setTimeout(1000 * 60 * 2)
20-
context.appDir = join(__dirname, '../')
21-
context.middleware = new File(join(__dirname, '../pages/_middleware.js'))
22-
context.page = new File(join(__dirname, '../pages/index.js'))
17+
18+
const WEBPACK_BREAKING_CHANGE = 'BREAKING CHANGE:'
19+
const context = {
20+
appDir: join(__dirname, '../'),
21+
buildLogs: { output: '', stdout: '', stderr: '' },
22+
logs: { output: '', stdout: '', stderr: '' },
23+
middleware: new File(join(__dirname, '../pages/_middleware.js')),
24+
page: new File(join(__dirname, '../pages/index.js')),
25+
}
2326

2427
describe('Middleware importing Node.js modules', () => {
2528
function getModuleNotFound(name) {
@@ -39,20 +42,20 @@ describe('Middleware importing Node.js modules', () => {
3942
})
4043

4144
describe('dev mode', () => {
42-
let output = ''
43-
4445
// restart the app for every test since the latest error is not shown sometimes
4546
// See https://github.com/vercel/next.js/issues/36575
4647
beforeEach(async () => {
47-
output = ''
48+
context.logs = { output: '', stdout: '', stderr: '' }
4849
context.appPort = await findPort()
4950
context.app = await launchApp(context.appDir, context.appPort, {
5051
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
5152
onStdout(msg) {
52-
output += msg
53+
context.logs.output += msg
54+
context.logs.stdout += msg
5355
},
5456
onStderr(msg) {
55-
output += msg
57+
context.logs.output += msg
58+
context.logs.stderr += msg
5659
},
5760
})
5861
})
@@ -72,11 +75,13 @@ describe('Middleware importing Node.js modules', () => {
7275
await waitFor(500)
7376
const msg = getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('path')
7477
expect(res.status).toBe(500)
75-
expect(output).toContain(getModuleNotFound('path'))
76-
expect(output).toContain(msg)
78+
expect(context.logs.output).toContain(getModuleNotFound('path'))
79+
expect(context.logs.output).toContain(msg)
7780
expect(text).toContain(escapeLF(msg))
78-
expect(stripAnsi(output)).toContain("import { basename } from 'path'")
79-
expect(output).not.toContain(WEBPACK_BREAKING_CHANGE)
81+
expect(stripAnsi(context.logs.output)).toContain(
82+
"import { basename } from 'path'"
83+
)
84+
expect(context.logs.output).not.toContain(WEBPACK_BREAKING_CHANGE)
8085
})
8186

8287
it('shows the right error when importing `child_process` on middleware', async () => {
@@ -95,13 +100,13 @@ describe('Middleware importing Node.js modules', () => {
95100
const msg =
96101
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process')
97102
expect(res.status).toBe(500)
98-
expect(output).toContain(getModuleNotFound('child_process'))
99-
expect(output).toContain(msg)
103+
expect(context.logs.output).toContain(getModuleNotFound('child_process'))
104+
expect(context.logs.output).toContain(msg)
100105
expect(text).toContain(escapeLF(msg))
101-
expect(stripAnsi(output)).toContain(
106+
expect(stripAnsi(context.logs.output)).toContain(
102107
"import { spawn } from 'child_process'"
103108
)
104-
expect(output).not.toContain(WEBPACK_BREAKING_CHANGE)
109+
expect(context.logs.output).not.toContain(WEBPACK_BREAKING_CHANGE)
105110
})
106111

107112
it('shows the right error when importing a non-node-builtin module on middleware', async () => {
@@ -121,8 +126,8 @@ describe('Middleware importing Node.js modules', () => {
121126
await waitFor(500)
122127
const msg =
123128
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('not-exist')
124-
expect(output).toContain(getModuleNotFound('not-exist'))
125-
expect(output).not.toContain(msg)
129+
expect(context.logs.output).toContain(getModuleNotFound('not-exist'))
130+
expect(context.logs.output).not.toContain(msg)
126131
expect(text).not.toContain(escapeLF(msg))
127132
})
128133

@@ -146,10 +151,25 @@ describe('Middleware importing Node.js modules', () => {
146151
await waitFor(500)
147152
const msg =
148153
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process')
149-
expect(output).toContain(getModuleNotFound('child_process'))
150-
expect(output).not.toContain(msg)
154+
expect(context.logs.output).toContain(getModuleNotFound('child_process'))
155+
expect(context.logs.output).not.toContain(msg)
151156
expect(text).not.toContain(escapeLF(msg))
152157
})
158+
159+
it('warns about nested middleware being deprecated', async () => {
160+
const file = new File(join(__dirname, '../pages/about/_middleware.js'))
161+
file.write(`export function middleware() {}`)
162+
try {
163+
const res = await fetchViaHTTP(context.appPort, '/about')
164+
console.log(context.logs.stderr)
165+
expect(context.logs.stderr).toContain(
166+
'nested Middleware is deprecated (found pages/about/_middleware) - https://nextjs.org/docs/messages/nested-middleware'
167+
)
168+
expect(res.status).toBe(200)
169+
} finally {
170+
file.delete()
171+
}
172+
})
153173
})
154174

155175
describe('production mode', () => {
@@ -194,5 +214,19 @@ describe('Middleware importing Node.js modules', () => {
194214
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process')
195215
)
196216
})
217+
218+
it('fails when there is a deprecated middleware', async () => {
219+
const file = new File(join(__dirname, '../pages/about/_middleware.js'))
220+
file.write(`export function middleware() {}`)
221+
const buildResult = await nextBuild(context.appDir, undefined, {
222+
stderr: true,
223+
stdout: true,
224+
})
225+
console.log(buildResult.stdout)
226+
227+
expect(buildResult.stderr).toContain(
228+
'nested Middleware is deprecated (found pages/about/_middleware) - https://nextjs.org/docs/messages/nested-middleware'
229+
)
230+
})
197231
})
198232
})

test/production/dependencies-can-use-env-vars-in-middlewares/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('dependencies can use env vars in middlewares', () => {
2626
`,
2727

2828
// The actual middleware code
29-
'pages/api/_middleware.js': `
29+
'pages/_middleware.js': `
3030
import customPackage from 'my-custom-package';
3131
export default function middleware(_req) {
3232
return new Response(JSON.stringify({
@@ -59,7 +59,7 @@ describe('dependencies can use env vars in middlewares', () => {
5959
'.next/server/middleware-manifest.json'
6060
)
6161
const manifest = await readJson(manifestPath)
62-
const envVars = manifest?.middleware?.['/api']?.env
62+
const envVars = manifest?.middleware?.['/']?.env
6363

6464
expect(envVars).toHaveLength(2)
6565
expect(envVars).toContain('ENV_VAR_USED_IN_MIDDLEWARE')

0 commit comments

Comments
 (0)