Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(middleware)!: forbid response body #1

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ test/tmp/**
# Editors
**/.idea
**/.#*
.nvmrc

# examples
examples/**/out
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced-features/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ npm install next@latest
import type { NextFetchEvent, NextRequest } from 'next/server'

export function middleware(req: NextRequest, ev: NextFetchEvent) {
return new Response('Hello, world!')
return new Response(null, { headers: { location: '/hello-world' } })
}
```

Expand Down
18 changes: 16 additions & 2 deletions docs/api-reference/next/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ The following static methods are available on the `NextResponse` class directly:
- `redirect()` - Returns a `NextResponse` with a redirect set
- `rewrite()` - Returns a `NextResponse` with a rewrite set
- `next()` - Returns a `NextResponse` that will continue the middleware chain
- `json()` - A convenience method to create a response that encodes the provided JSON data

```ts
import { NextResponse } from 'next/server'
Expand All @@ -120,7 +119,7 @@ export function middleware(req: NextRequest) {
return NextResponse.rewrite('/not-home')
}

return NextResponse.json({ message: 'Hello World!' })
return NextResponse.next()
}
```

Expand Down Expand Up @@ -183,6 +182,21 @@ console.log(NODE_ENV)
console.log(process.env)
```

### The body limitation

When using middlewares, it is not permitted to change the response body: you can only set responses headers.
Returning a body from a middleware function will issue an `500` server error with an explicit response message.

The `NextResponse` API (which eventually is tweaking response headers) allows you to:

- redirect the incoming request to a different url
- rewrite the response by displaying a given url
- set response cookies
- set response headers

These are solid tools to easily implement A/B testing, authentication, feature flags, bot protection...
A middleware with the ability to change the response's body would bypass Next.js routing logic.

## Related

<div class="card">
Expand Down
1 change: 1 addition & 0 deletions docs/basic-features/pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ You can also use **Client-side Rendering** along with Static Generation or Serve
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.vercel.app/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-kontent">Kontent Example</a> (<a href="https://next-blog-kontent.vercel.app/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-builder-io">Builder.io Example</a> (<a href="https://cms-builder-io.vercel.app/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-tina">TinaCMS Example</a> (<a href="https://cms-tina-example.vercel.app/">Demo</a>)</li>
<li><a href="https://static-tweet.vercel.app/">Static Tweet (Demo)</a></li>
</ul>
</details>
Expand Down
4 changes: 4 additions & 0 deletions errors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@
{
"title": "invalid-script",
"path": "/errors/invalid-script.md"
},
{
"title": "returning-response-body-in-_middleware",
"path": "/errors/returning-response-body-in-_middleware.md"
}
]
}
Expand Down
84 changes: 84 additions & 0 deletions errors/returning-response-body-in-_middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Returning response body in \_middleware

#### Why This Error Occurred

Your [`_middleware`](https://nextjs.org/docs/advanced-features/middleware) function returns a response body, which is not supported.

Letting middleware respond to incoming requests would bypass Next.js routing mechanism, creating an unecessary escape hatch.

#### Possible Ways to Fix It

Next.js middleware gives you a great opportunity to run code and adjust to the requesting user.

It is intended for use cases like:

- A/B testing, where you **_rewrite_** to a different page based on external data (User agent, user location, a custom header or cookie...)

```js
export function middleware(req: NextRequest) {
let res = NextResponse.next()
// reuses cookie, or builds a new one.
const cookie = req.cookies.get(COOKIE_NAME) ?? buildABTestingCookie()

// the cookie contains the displayed variant, 0 being default
const [, variantId] = cookie.split('.')
if (variantId !== '0') {
const url = req.nextUrl.clone()
url.pathname = url.pathname.replace('/', `/${variantId}/`)
// rewrites the response to display desired variant
res = NextResponse.rewrite(url)
}

// don't forget to set cookie if not set yet
if (!req.cookies.has(COOKIE_NAME)) {
res.cookies.set(COOKIE_NAME, cookie)
}
return res
}
```

- authentication, where you **_redirect_** to your log-in/sign-in page any un-authenticated request

```js
export function middleware(req: NextRequest) {
const basicAuth = req.headers.get('authorization')

if (basicAuth) {
const auth = basicAuth.split(' ')[1]
const [user, pwd] = atob(auth).split(':')
if (areCredentialsValid(user, pwd)) {
return NextResponse.next()
}
}

return NextResponse.redirec(`/login?from=${req.nextUrl.pathname}`)
}
```

- detecting bots and **_rewrite_** response to display to some sink

```js
export function middleware(req: NextRequest) {
if (isABotRequest(req)) {
// Bot detected! rewrite to the sink
const url = req.nextUrl.clone()
url.pathname = '/bot-detected'
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
```

- programmatically adding **_headers_** to the response, like cookies.

```js
export function middleware(req: NextRequest) {
const res = NextResponse.next(null, {
// sets a custom response header
headers: { 'response-greetings': 'Hej!' },
})
// configures cookies
response.cookies.set('hello', 'world')
return res
}
```
1 change: 1 addition & 0 deletions examples/blog-starter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu
- [Kontent](/examples/cms-kontent)
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Builder.io](/examples/cms-builder-io)
- [TinaCMS](/examples/cms-tina/)

## How to use

Expand Down
Loading