Skip to content

Commit

Permalink
docs: documents middleware matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
feugy committed Sep 2, 2022
1 parent abcf991 commit d4b3643
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 0 deletions.
13 changes: 13 additions & 0 deletions docs/advanced-features/middleware.md
Expand Up @@ -90,6 +90,19 @@ export const config = {

> **Note:** The `matcher` values need to be constants so they can be statically analyzed at build-time. Dynamic values such as variables will be ignored.
Configured matchers:

1. MUST start with `/`
1. can include named parameters: `/about/:path` matches `/about/a` and `/about/b` but not `/about/a/c`
1. can have modifiers on named parameters (starting with `:`): `/about/:path*` matches `/about/a/b/c` because `*` is _zero or more_. `?` is _zero or one_ and `+` _one or more_
1. can use regular expression enclosed in parenthesis: `/about/(.*)` is the same as `/about/:path*`

Read more details on [path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp-1) documentation.

> **Note:** For backward compatibility, Next.js always considers `/public` as `/public/index`. Therefore, a matcher of `/public/:path` will match.
> **Note:** It is not possible to exclude middleware from matching static path starting with `_next/`. This allow enforcing security with middleware.
### Conditional Statements

```typescript
Expand Down
37 changes: 37 additions & 0 deletions examples/middleware-matcher/README.md
@@ -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)).
15 changes: 15 additions & 0 deletions examples/middleware-matcher/middleware.js
@@ -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).*)'],
}
13 changes: 13 additions & 0 deletions examples/middleware-matcher/package.json
@@ -0,0 +1,13 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest"
}
}
29 changes: 29 additions & 0 deletions examples/middleware-matcher/pages/[...slug].jsx
@@ -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
30 changes: 30 additions & 0 deletions examples/middleware-matcher/pages/index.jsx
@@ -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

0 comments on commit d4b3643

Please sign in to comment.