mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
## Summary
The `rewrites`, `headers`, and `redirects` config function examples in
the docs are declared `async` but never `await` anything inside, which
trips up linters (e.g.
ESLint's `require-await` rule) for anyone who copies the example into
`next.config.js`. These config options accept either a sync function or
an async function (see the
type in `packages/next/src/server/config-shared.ts`), so removing the
unused `async` keyword does not change behavior.
- Removed the unnecessary `async` keyword from every example in
`rewrites.mdx`, `headers.mdx`, and `redirects.mdx`
- Updated the accompanying prose ("is an async function" → "is a
function") in each file
Fixes #77598
## Verification
- `grep -rn "async rewrites() {\|async headers() {\|async redirects()
{"` across the three files — no matches
- `npx prettier --check` on the three changed files — passes
- Not run: `pnpm lint` (`node_modules` not installed in this
environment)
<!-- NEXT_JS_LLM_PR -->
---------
Co-authored-by: Joseph <[email protected]>
480 lines
16 KiB
Plaintext
480 lines
16 KiB
Plaintext
---
|
|
title: rewrites
|
|
description: Add rewrites to your Next.js app.
|
|
---
|
|
|
|
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
|
|
|
Rewrites allow you to map an incoming request path to a different destination path.
|
|
|
|
<AppOnly>
|
|
|
|
Rewrites act as a URL proxy and mask the destination path, making it appear the user hasn't changed their location on the site. In contrast, [redirects](/docs/app/api-reference/config/next-config-js/redirects) will reroute to a new page and show the URL changes.
|
|
|
|
</AppOnly>
|
|
|
|
<PagesOnly>
|
|
|
|
Rewrites act as a URL proxy and mask the destination path, making it appear the user hasn't changed their location on the site. In contrast, [redirects](/docs/pages/api-reference/config/next-config-js/redirects) will reroute to a new page and show the URL changes.
|
|
|
|
</PagesOnly>
|
|
|
|
To use rewrites you can use the `rewrites` key in `next.config.js`:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/about',
|
|
destination: '/',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
Rewrites are applied to client-side routing. In the example above, navigating to `<Link href="/about">` will serve content from `/` while keeping the URL as `/about`.
|
|
|
|
`rewrites` can be defined as a synchronous or async function. It should return, or resolve to, either an array or an object of arrays (see below) holding objects with `source` and `destination` properties:
|
|
|
|
- `source`: `String` - is the incoming request path pattern.
|
|
- `destination`: `String` is the path you want to route to.
|
|
- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only.
|
|
- `locale`: `false` or `undefined` - whether the locale should not be included when matching.
|
|
- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties.
|
|
- `missing` is an array of [missing objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties.
|
|
|
|
When the `rewrites` function returns an array, rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes. When the `rewrites` function returns an object of arrays with a specific shape, this behavior can be changed and more finely controlled, as of `v10.1` of Next.js:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return {
|
|
beforeFiles: [
|
|
// These rewrites are checked after headers/redirects
|
|
// and before all files including _next/public files which
|
|
// allows overriding page files
|
|
{
|
|
source: '/some-page',
|
|
destination: '/somewhere-else',
|
|
has: [{ type: 'query', key: 'overrideMe' }],
|
|
},
|
|
],
|
|
afterFiles: [
|
|
// These rewrites are checked after pages/public files
|
|
// are checked but before dynamic routes
|
|
{
|
|
source: '/non-existent',
|
|
destination: '/somewhere-else',
|
|
},
|
|
],
|
|
fallback: [
|
|
// These rewrites are checked after both pages/public files
|
|
// and dynamic routes are checked
|
|
{
|
|
source: '/:path*',
|
|
destination: `https://my-old-site.com/:path*`,
|
|
},
|
|
],
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
> **Good to know**: rewrites in `beforeFiles` do not check the filesystem/dynamic routes immediately after matching a source, they continue until all `beforeFiles` have been checked.
|
|
|
|
The order Next.js routes are checked is:
|
|
|
|
<AppOnly>
|
|
|
|
1. [headers](/docs/app/api-reference/config/next-config-js/headers) are checked/applied
|
|
2. [redirects](/docs/app/api-reference/config/next-config-js/redirects) are checked/applied
|
|
3. [proxy](/docs/app/api-reference/file-conventions/proxy)
|
|
4. `beforeFiles` rewrites: for each entry, if `source`, `has`, and `missing` matches the request, it's rewritten to `destination`.
|
|
5. static files from the [public directory](/docs/app/api-reference/file-conventions/public-folder), `_next/static` files, and non-dynamic pages are checked/served
|
|
6. `afterFiles` rewrites are tried in order. If a `source`, `has`, and `missing` matches the request, it's rewritten to `destination`; the first rewrite that resolves to a static file, page, or dynamic route is served.
|
|
7. dynamic routes (e.g., `app/blog/[slug]/page.tsx`) are matched against the current path
|
|
8. `fallback` rewrites are checked/applied, these are applied before rendering the 404 page and after dynamic routes/all static assets have been checked. If you use [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true) in `getStaticPaths`, those dynamic routes take priority over the fallback `rewrites` defined in your `next.config.js`.
|
|
|
|
</AppOnly>
|
|
|
|
<PagesOnly>
|
|
|
|
1. [headers](/docs/pages/api-reference/config/next-config-js/headers) are checked/applied
|
|
2. [redirects](/docs/pages/api-reference/config/next-config-js/redirects) are checked/applied
|
|
3. `beforeFiles` rewrites: for each entry, if `source` matches the request, it's rewritten to `destination`.
|
|
4. static files from the [public directory](/docs/pages/api-reference/file-conventions/public-folder), `_next/static` files, and non-dynamic pages are checked/served
|
|
5. `afterFiles` rewrites are tried in order. If a `source` matches the request, it's rewritten to `destination`; the first rewrite that resolves to a static file, page, or dynamic route is served.
|
|
6. dynamic routes (e.g., `pages/blog/[slug].tsx`) are matched against the current path
|
|
7. `fallback` rewrites are checked/applied, these are applied before rendering the 404 page and after dynamic routes/all static assets have been checked. If you use [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true) in `getStaticPaths`, the fallback `rewrites` defined in your `next.config.js` will _not_ be run.
|
|
|
|
</PagesOnly>
|
|
|
|
## Rewrite parameters
|
|
|
|
When using parameters in a rewrite the parameters will be passed in the query by default when none of the parameters are used in the `destination`.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/old-about/:path*',
|
|
destination: '/about', // The :path parameter isn't used here so will be automatically passed in the query
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
If a parameter is used in the destination none of the parameters will be automatically passed in the query.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/docs/:path*',
|
|
destination: '/:path*', // The :path parameter is used here so will not be automatically passed in the query
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
You can still pass the parameters manually in the query if one is already used in the destination by specifying the query in the `destination`.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/:first/:second',
|
|
destination: '/:first?second=:second',
|
|
// Since the :first parameter is used in the destination the :second parameter
|
|
// will not automatically be added in the query although we can manually add it
|
|
// as shown above
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
> **Good to know**: Static pages from [Automatic Static Optimization](/docs/pages/building-your-application/rendering/automatic-static-optimization) or [prerendering](/docs/pages/building-your-application/data-fetching/get-static-props) params from rewrites will be parsed on the client after hydration and provided in the query.
|
|
|
|
## Path Matching
|
|
|
|
Path matches are allowed, for example `/blog/:slug` will match `/blog/first-post` (no nested paths):
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/blog/:slug',
|
|
destination: '/news/:slug', // Matched parameters can be used in the destination
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
The pattern `/blog/:slug` matches `/blog/first-post` and `/blog/post-1` but not `/blog/a/b` (no nested paths). Patterns are anchored to the start: `/blog/:slug` will not match `/archive/blog/first-post`.
|
|
|
|
You can use modifiers on parameters: `*` (zero or more), `+` (one or more), `?` (zero or one). For example, `/blog/:slug*` matches `/blog`, `/blog/a`, and `/blog/a/b/c`.
|
|
|
|
Read more details on [path-to-regexp](https://github.com/pillarjs/path-to-regexp) documentation.
|
|
|
|
### Wildcard Path Matching
|
|
|
|
To match a wildcard path you can use `*` after a parameter, for example `/blog/:slug*` will match `/blog/a/b/c/d/hello-world`:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/blog/:slug*',
|
|
destination: '/news/:slug*', // Matched parameters can be used in the destination
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
### Regex Path Matching
|
|
|
|
To match a regex path you can wrap the regex in parenthesis after a parameter, for example `/blog/:slug(\\d{1,})` will match `/blog/123` but not `/blog/abc`:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/old-blog/:post(\\d{1,})',
|
|
destination: '/blog/:post', // Matched parameters can be used in the destination
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
The following characters `(`, `)`, `{`, `}`, `[`, `]`, `|`, `\`, `^`, `.`, `:`, `*`, `+`, `-`, `?`, `$` are used for regex path matching, so when used in the `source` as non-special values they must be escaped by adding `\\` before them:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
// this will match `/english(default)/something` being requested
|
|
source: '/english\\(default\\)/:slug',
|
|
destination: '/en-us/:slug',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
## Header, Cookie, and Query Matching
|
|
|
|
To only match a rewrite when header, cookie, or query values also match the `has` field or don't match the `missing` field can be used. Both the `source` and all `has` items must match and all `missing` items must not match for the rewrite to be applied.
|
|
|
|
`has` and `missing` items can have the following fields:
|
|
|
|
- `type`: `String` - must be either `header`, `cookie`, `host`, or `query`.
|
|
- `key`: `String` - the key from the selected type to match against.
|
|
- `value`: `String` or `undefined` - the value to check for, if undefined any value will match. A regex like string can be used to capture a specific part of the value, e.g. if the value `first-(?<paramName>.*)` is used for `first-second` then `second` will be usable in the destination with `:paramName`.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
// if the header `x-rewrite-me` is present,
|
|
// this rewrite will be applied
|
|
{
|
|
source: '/:path*',
|
|
has: [
|
|
{
|
|
type: 'header',
|
|
key: 'x-rewrite-me',
|
|
},
|
|
],
|
|
destination: '/another-page',
|
|
},
|
|
// if the header `x-rewrite-me` is not present,
|
|
// this rewrite will be applied
|
|
{
|
|
source: '/:path*',
|
|
missing: [
|
|
{
|
|
type: 'header',
|
|
key: 'x-rewrite-me',
|
|
},
|
|
],
|
|
destination: '/another-page',
|
|
},
|
|
// if the source, query, and cookie are matched,
|
|
// this rewrite will be applied
|
|
{
|
|
source: '/specific/:path*',
|
|
has: [
|
|
{
|
|
type: 'query',
|
|
key: 'page',
|
|
// the page value will not be available in the
|
|
// destination since value is provided and doesn't
|
|
// use a named capture group e.g. (?<page>home)
|
|
value: 'home',
|
|
},
|
|
{
|
|
type: 'cookie',
|
|
key: 'authorized',
|
|
value: 'true',
|
|
},
|
|
],
|
|
destination: '/:path*/home',
|
|
},
|
|
// if the header `x-authorized` is present and
|
|
// contains a matching value, this rewrite will be applied
|
|
{
|
|
source: '/:path*',
|
|
has: [
|
|
{
|
|
type: 'header',
|
|
key: 'x-authorized',
|
|
value: '(?<authorized>yes|true)',
|
|
},
|
|
],
|
|
destination: '/home?authorized=:authorized',
|
|
},
|
|
// if the host is `example.com`,
|
|
// this rewrite will be applied
|
|
{
|
|
source: '/:path*',
|
|
has: [
|
|
{
|
|
type: 'host',
|
|
value: 'example.com',
|
|
},
|
|
],
|
|
destination: '/another-page',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
## Rewriting to an external URL
|
|
|
|
<details>
|
|
<summary>Examples</summary>
|
|
|
|
- [Using Multiple Zones](https://github.com/vercel/next.js/tree/canary/examples/with-zones)
|
|
|
|
</details>
|
|
|
|
Rewrites allow you to rewrite to an external URL. This is especially useful for incrementally adopting Next.js. The following is an example rewrite for redirecting the `/blog` route of your main app to an external site.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/blog',
|
|
destination: 'https://example.com/blog',
|
|
},
|
|
{
|
|
source: '/blog/:slug',
|
|
destination: 'https://example.com/blog/:slug', // Matched parameters can be used in the destination
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
If you're using `trailingSlash: true`, you also need to insert a trailing slash in the `source` parameter. If the destination server is also expecting a trailing slash it should be included in the `destination` parameter as well.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
trailingSlash: true,
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/blog/',
|
|
destination: 'https://example.com/blog/',
|
|
},
|
|
{
|
|
source: '/blog/:path*/',
|
|
destination: 'https://example.com/blog/:path*/',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
### Incremental adoption of Next.js
|
|
|
|
You can also have Next.js fall back to proxying to an existing website after checking all Next.js routes.
|
|
|
|
This way you don't have to change the rewrites configuration when migrating more pages to Next.js
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
rewrites() {
|
|
return {
|
|
fallback: [
|
|
{
|
|
source: '/:path*',
|
|
destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`,
|
|
},
|
|
],
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
### Rewrites with basePath support
|
|
|
|
When leveraging [`basePath` support](/docs/app/api-reference/config/next-config-js/basePath) with rewrites each `source` and `destination` is automatically prefixed with the `basePath` unless you add `basePath: false` to the rewrite:
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
basePath: '/docs',
|
|
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/with-basePath', // automatically becomes /docs/with-basePath
|
|
destination: '/another', // automatically becomes /docs/another
|
|
},
|
|
{
|
|
// does not add /docs to /without-basePath since basePath: false is set
|
|
// Note: this cannot be used for internal rewrites e.g. `destination: '/another'`
|
|
source: '/without-basePath',
|
|
destination: 'https://example.com',
|
|
basePath: false,
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
<PagesOnly>
|
|
|
|
### Rewrites with i18n support
|
|
|
|
When leveraging [`i18n` support](/docs/pages/guides/internationalization) with rewrites each `source` and `destination` is automatically prefixed to handle the configured `locales` unless you add `locale: false` to the rewrite. If `locale: false` is used you must prefix the `source` and `destination` with a locale for it to be matched correctly.
|
|
|
|
```js filename="next.config.js"
|
|
module.exports = {
|
|
i18n: {
|
|
locales: ['en', 'fr', 'de'],
|
|
defaultLocale: 'en',
|
|
},
|
|
|
|
rewrites() {
|
|
return [
|
|
{
|
|
source: '/with-locale', // automatically handles all locales
|
|
destination: '/another', // automatically passes the locale on
|
|
},
|
|
{
|
|
// does not handle locales automatically since locale: false is set
|
|
source: '/nl/with-locale-manual',
|
|
destination: '/nl/another',
|
|
locale: false,
|
|
},
|
|
{
|
|
// this matches '/' since `en` is the defaultLocale
|
|
source: '/en',
|
|
destination: '/en/another',
|
|
locale: false,
|
|
},
|
|
{
|
|
// it's possible to match all locales even when locale: false is set
|
|
source: '/:locale/api-alias/:path*',
|
|
destination: '/api/:path*',
|
|
locale: false,
|
|
},
|
|
{
|
|
// this gets converted to /(en|fr|de)/(.*) so will not match the top-level
|
|
// `/` or `/fr` routes like /:path* would
|
|
source: '/(.*)',
|
|
destination: '/another',
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
|
|
</PagesOnly>
|
|
|
|
## Version History
|
|
|
|
| Version | Changes |
|
|
| --------- | ---------------- |
|
|
| `v13.3.0` | `missing` added. |
|
|
| `v10.2.0` | `has` added. |
|
|
| `v9.5.0` | Headers added. |
|