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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: documents middleware matcher #40180

Merged
merged 3 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
36 changes: 36 additions & 0 deletions examples/middleware-matcher/.gitignore
@@ -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
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