-
Notifications
You must be signed in to change notification settings - Fork 26.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: documents middleware matcher (#40180)
### 📖 What's in there? Middleware matchers are powerful, but very few people realized it, because they are not really documented. This PR tries to bring more clarity, and includes a more advanced example. The example shows how to exclude several pages (no `/static`, no `/public`), but also allow specific page in excluded paths (`/public/disclaimer`) ### 🧪 How to test? Run the example: `pnpm next dev examples/middleware-matcher`, then browse to http://localhost:3000 The first 3 links should not match, the last 3 ones should. Don't forget to clear your localhost cookies if you change the middleware code. ### 🆙 Note to reviewers Using session cookies to pass information from middleware to the rendered page is not great, because `document.cookie` is not available during SSR, and because cookies persist when refreshing the page (making it hard to try different matchers) However, I couldn't find a simpler way to convey the information from the middleware to the page, and I meant to have something visual. The other option is to use response headers and curl commands, but...
- Loading branch information
Showing
7 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Middleware | ||
|
||
This example shows how to configure your [Next.js Middleware](https://nextjs.org/docs/advanced-features/middleware) to only match specific pages. | ||
|
||
The index page ([`pages/index.js`](pages/index.js)) has a list of links to dynamic pages, which will tell whether they were matched or not. | ||
|
||
The Middleware file ([`middleware.js`](middleware.js)) has a special `matcher` configuration key, allowing you to fine-grained control [matched pages](https://nextjs.org/docs/advanced-features/middleware#matcher). | ||
|
||
Please keep in mind that: | ||
|
||
1. Middleware always runs first | ||
1. Middleware always matches `_next` routes on server side | ||
1. matcher must always starts with a '/' | ||
|
||
## Deploy your own | ||
|
||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): | ||
|
||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/middleware-matcher&project-name=middleware-matcher&repository-name=middleware-matcher) | ||
|
||
## How to use | ||
|
||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: | ||
|
||
```bash | ||
npx create-next-app --example middleware-matcher middleware-matcher-app | ||
``` | ||
|
||
```bash | ||
yarn create next-app --example middleware-matcher middleware-matcher-app | ||
``` | ||
|
||
```bash | ||
pnpm create next-app --example middleware-matcher middleware-matcher-app | ||
``` | ||
|
||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { NextResponse } from 'next/server' | ||
|
||
export default function middleware(req) { | ||
const { pathname } = new URL(req.url) | ||
const response = NextResponse.next() | ||
response.headers.set( | ||
'set-cookie', | ||
`middleware-slug=${pathname.slice(1)}; Path=${pathname}` | ||
) | ||
return response | ||
} | ||
|
||
export const config = { | ||
matcher: ['/public/disclaimer', '/((?!public|static).*)'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"private": true, | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"next": "latest", | ||
"react": "latest", | ||
"react-dom": "latest" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { useRouter } from 'next/router' | ||
|
||
function hasMiddlewareMatched(slug) { | ||
const values = | ||
(typeof document !== 'undefined' ? document.cookie : '') | ||
.split(';') | ||
.map((pair) => pair.split('=')) | ||
.filter(([key]) => key === 'middleware-slug') | ||
.map(([, value]) => value.trim()) ?? [] | ||
return values.some((value) => value === slug?.join('/')) | ||
} | ||
|
||
export const ContentPage = (props) => { | ||
const { | ||
query: { slug }, // slug is an array of path segments | ||
} = useRouter() | ||
return ( | ||
<> | ||
<h1> | ||
{hasMiddlewareMatched(slug) | ||
? 'Middleware matched!' | ||
: 'Middleware ignored me'} | ||
</h1> | ||
<a href="/">{'<-'} back</a> | ||
</> | ||
) | ||
} | ||
|
||
export default ContentPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const Home = () => { | ||
const matching = ['/about', '/about/topic/cats', '/public/disclaimer'] | ||
const notMatching = ['/public', '/public/disclaimer/nested', '/static'] | ||
return ( | ||
<div> | ||
<h1>Middleware matching</h1> | ||
<p>The current middleware configuration is:</p> | ||
<pre> | ||
export const config = {'{'} | ||
<br /> | ||
{' '}matcher: [ <br /> | ||
{' '}'/public/disclaimer', // match a single, specific page | ||
<br /> | ||
{' '}'/((?!public|static).*) // match all pages not starting with | ||
'public' or 'static' <br /> | ||
{' '}] <br /> | ||
{'}'} | ||
</pre> | ||
<ul> | ||
{[...notMatching, ...matching].map((href) => ( | ||
<li key={href}> | ||
<a href={href}>{href}</a> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
) | ||
} | ||
|
||
export default Home |