Skip to content

Commit

Permalink
Deprecate nested Middleware in favor of root middleware (#36772)
Browse files Browse the repository at this point in the history
This PR deprecates declaring a middleware under `pages` in favour of the project root naming it after `middleware` instead of `_middleware`. This is in the context of having a simpler execution model for middleware and also ships some refactor work. There is a ton of a code to be simplified after this deprecation but I think it is best to do it progressively.

With this PR, when in development, we will **fail** whenever we find a nested middleware but we do **not** include it in the compiler so if the project is using it, it will no longer work. For production we will **fail** too so it will not be possible to build and deploy a deprecated middleware. The error points to a page that should also be reviewed as part of **documentation**.

Aside from the deprecation, this migrates all middleware tests to work with a single middleware. It also splits tests into multiple folders to make them easier to isolate and work with. Finally it ships some small code refactor and simplifications.
  • Loading branch information
javivelasco authored May 19, 2022
1 parent cc8ab99 commit f354f46
Show file tree
Hide file tree
Showing 145 changed files with 2,380 additions and 2,498 deletions.
2 changes: 1 addition & 1 deletion docs/advanced-features/i18n-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ module.exports = {
Next, we can use [Middleware](/docs/middleware.md) to add custom routing rules:

```js
// pages/_middleware.ts
// middleware.ts

import { NextRequest, NextResponse } from 'next/server'

Expand Down
33 changes: 4 additions & 29 deletions docs/advanced-features/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ Middleware enables you to use code over configuration. This gives you full flexi
npm install next@latest
```

2. Then, create a `_middleware.ts` file under your `/pages` directory.
2. Then, create a `middleware.ts` file under your project root directory.

3. Finally, export a middleware function from the `_middleware.ts` file.
3. Finally, export a middleware function from the `middleware.ts` file.

```jsx
// pages/_middleware.ts
// middleware.ts

import type { NextFetchEvent, NextRequest } from 'next/server'

Expand All @@ -42,7 +42,7 @@ In this example, we use the standard Web API Response ([MDN](https://developer.m

## API

Middleware is created by using a `middleware` function that lives inside a `_middleware` file. Its API is based upon the native [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) objects.
Middleware is created by using a `middleware` function that lives inside a `middleware` file. Its API is based upon the native [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) objects.

These native Web API objects are extended to give you more control over how you manipulate and configure a response, based on the incoming requests.

Expand Down Expand Up @@ -75,31 +75,6 @@ Middleware can be used for anything that shares logic for a set of pages, includ
## Execution Order
If your Middleware is created in `/pages/_middleware.ts`, it will run on all routes within the `/pages` directory. The below example assumes you have `about.tsx` and `teams.tsx` routes.
```bash
- package.json
- /pages
_middleware.ts # Will run on all routes under /pages
index.tsx
about.tsx
teams.tsx
```
If you _do_ have sub-directories with nested routes, Middleware will run from the top down. For example, if you have `/pages/about/_middleware.ts` and `/pages/about/team/_middleware.ts`, `/about` will run first and then `/about/team`. The below example shows how this works with a nested routing structure.
```bash
- package.json
- /pages
index.tsx
- /about
_middleware.ts # Will run first
about.tsx
- /teams
_middleware.ts # Will run second
teams.tsx
```
Middleware runs directly after `redirects` and `headers`, before the first filesystem lookup. This excludes `/_next` files.
## Deployment
Expand Down
4 changes: 2 additions & 2 deletions docs/api-reference/next.config.js/custom-page-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {

> **Note**: The default value of `pageExtensions` is [`['tsx', 'ts', 'jsx', 'js']`](https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161).
> **Note**: configuring `pageExtensions` also affects `_document.js`, `_app.js`, `_middleware.js` as well as files under `pages/api/`. For example, setting `pageExtensions: ['page.tsx', 'page.ts']` means the following files: `_document.tsx`, `_app.tsx`, `_middleware.ts`, `pages/users.tsx` and `pages/api/users.ts` will have to be renamed to `_document.page.tsx`, `_app.page.tsx`, `_middleware.page.ts`, `pages/users.page.tsx` and `pages/api/users.page.ts` respectively.
> **Note**: configuring `pageExtensions` also affects `_document.js`, `_app.js`, `middleware.js` as well as files under `pages/api/`. For example, setting `pageExtensions: ['page.tsx', 'page.ts']` means the following files: `_document.tsx`, `_app.tsx`, `middleware.ts`, `pages/users.tsx` and `pages/api/users.ts` will have to be renamed to `_document.page.tsx`, `_app.page.tsx`, `middleware.page.ts`, `pages/users.page.tsx` and `pages/api/users.page.ts` respectively.
## Including non-page files in the `pages` directory

Expand All @@ -32,7 +32,7 @@ module.exports = {

Then rename your pages to have a file extension that includes `.page` (ex. rename `MyPage.tsx` to `MyPage.page.tsx`).

> **Note**: Make sure you also rename `_document.js`, `_app.js`, `_middleware.js`, as well as files under `pages/api/`.
> **Note**: Make sure you also rename `_document.js`, `_app.js`, `middleware.js`, as well as files under `pages/api/`.
Without this config, Next.js assumes every tsx/ts/jsx/js file in the `pages` directory is a page or API route, and may expose unintended routes vulnerable to denial of service attacks, or throw an error like the following when building the production bundle:

Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/next/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The `next/server` module provides several exports for server-only helpers, such

## NextMiddleware

Middleware is created by using a `middleware` function that lives inside a `_middleware` file. The Middleware API is based upon the native [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects.
Middleware is created by using a `middleware` function that lives inside a `middleware` file. The Middleware API is based upon the native [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects.

These native Web API objects are extended to give you more control over how you manipulate and configure a response, based on the incoming requests.

Expand Down
4 changes: 4 additions & 0 deletions errors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,10 @@
"title": "middleware-relative-urls",
"path": "/errors/middleware-relative-urls.md"
},
{
"title": "nested-middleware",
"path": "/errors/nested-middleware.md"
},
{
"title": "deleting-query-params-in-middlewares",
"path": "/errors/deleting-query-params-in-middlewares.md"
Expand Down
4 changes: 2 additions & 2 deletions errors/middleware-new-signature.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Your application is using a Middleware function that is using parameters from the deprecated API.

```typescript
// _middleware.js
// middleware.js
import { NextResponse } from 'next/server'

export function middleware(event) {
Expand All @@ -24,7 +24,7 @@ export function middleware(event) {
Update to use the new API for Middleware:

```typescript
// _middleware.js
// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
Expand Down
26 changes: 26 additions & 0 deletions errors/nested-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Nested Middleware

#### Why This Error Occurred

You are defining a middleware file in a location different from `<root>/middleware` which is not allowed.

While in beta, a middleware file under specific pages implied that it would _only_ be executed when pages below its declaration were matched.
This execution model allowed the nesting of multiple middleware, which is hard to reason about and led to consequences such as dragging effects between different middleware executions.

The API has been removed in favor of a simpler model with a single root middleware.

#### Possible Ways to Fix It

To fix this error, declare your middleware in the root folder and use `NextRequest` parsed URL to define which path the middleware code should be executed for. For example, a middleware declared under `pages/about/_middleware.js` can be moved to `middleware`. A conditional can be used to ensure the middleware executes only when it matches the `about/*` path:

```typescript
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
// Execute pages/about/_middleware.js
}
}
```

If you have more than one middleware, you will need to combine them into a single file and model their execution depending on the request.
6 changes: 3 additions & 3 deletions errors/no-server-import-in-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

### Why This Error Occurred

`next/server` was imported outside of `pages/**/_middleware.{js,ts}`.
`next/server` was imported outside of `middleware.{js,ts}`.

### Possible Ways to Fix It

Only import and use `next/server` in a file located within the pages directory: `pages/**/_middleware.{js,ts}`.
Only import and use `next/server` in a file located within the project root directory: `middleware.{js,ts}`.

```ts
// pages/_middleware.ts
// middleware.ts

import type { NextFetchEvent, NextRequest } from 'next/server'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ const path = require('path')
module.exports = {
meta: {
docs: {
description:
'Disallow importing next/server outside of pages/_middleware.js',
description: 'Disallow importing next/server outside of middleware.js',
recommended: true,
url: 'https://nextjs.org/docs/messages/no-server-import-in-page',
},
Expand All @@ -16,20 +15,18 @@ module.exports = {
return
}

const paths = context.getFilename().split('pages')
const page = paths[paths.length - 1]

const filename = context.getFilename()
if (
!page ||
page.includes(`${path.sep}_middleware`) ||
page.includes(`${path.posix.sep}_middleware`)
filename.startsWith('middleware.') ||
filename.startsWith(`${path.sep}middleware.`) ||
filename.startsWith(`${path.posix.sep}middleware.`)
) {
return
}

context.report({
node,
message: `next/server should not be imported outside of pages/_middleware.js. See: https://nextjs.org/docs/messages/no-server-import-in-page`,
message: `next/server should not be imported outside of middleware.js. See: https://nextjs.org/docs/messages/no-server-import-in-page`,
})
},
}
Expand Down
Loading

0 comments on commit f354f46

Please sign in to comment.