rewrites
Rewrites allow you to map an incoming request path to a different destination path.
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 will reroute to a new page and show the URL changes.
To use rewrites you can use therewrites key innext.config.js:
module.exports= {asyncrewrites() {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 is an async function that expects to return either an array or an object of arrays (see below) holding objects withsource anddestination properties:
source:String- is the incoming request path pattern.destination:Stringis the path you want to route to.basePath:falseorundefined- if false the basePath won't be included when matching, can be used for external rewrites only.locale:falseorundefined- whether the locale should not be included when matching.hasis an array ofhas objects with thetype,keyandvalueproperties.missingis an array ofmissing objects with thetype,keyandvalueproperties.
When therewrites function returns an array, rewrites are applied after checking the filesystem (pages and/public files) and before dynamic routes. When therewrites function returns an object of arrays with a specific shape, this behavior can be changed and more finely controlled, as ofv10.1 of Next.js:
module.exports= {asyncrewrites() {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
beforeFilesdo not check the filesystem/dynamic routes immediately after matching a source, they continue until allbeforeFileshave been checked.
The order Next.js routes are checked is:
- headers are checked/applied
- redirects are checked/applied
beforeFilesrewrites are checked/applied- static files from thepublic directory,
_next/staticfiles, and non-dynamic pages are checked/served afterFilesrewrites are checked/applied, if one of these rewrites is matched we check dynamic routes/static files after each matchfallbackrewrites are checked/applied, these are applied before rendering the 404 page and after dynamic routes/all static assets have been checked. If you usefallback: true/'blocking' ingetStaticPaths, the fallbackrewritesdefined in yournext.config.jswillnot be run.
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 thedestination.
module.exports= {asyncrewrites() {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.
module.exports= {asyncrewrites() {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 thedestination.
module.exports= {asyncrewrites() {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 fromAutomatic Static Optimization orprerendering 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):
module.exports= {asyncrewrites() {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 onpath-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:
module.exports= {asyncrewrites() {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:
module.exports= {asyncrewrites() {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 thesource as non-special values they must be escaped by adding\\ before them:
module.exports= {asyncrewrites() {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 thehas field or don't match themissing field can be used. Both thesource and allhas items must match and allmissing items must not match for the rewrite to be applied.
has andmissing items can have the following fields:
type:String- must be eitherheader,cookie,host, orquery.key:String- the key from the selected type to match against.value:Stringorundefined- 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 valuefirst-(?<paramName>.*)is used forfirst-secondthensecondwill be usable in the destination with:paramName.
module.exports= {asyncrewrites() {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
Examples
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.
module.exports= {asyncrewrites() {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 usingtrailingSlash: true, you also need to insert a trailing slash in thesource parameter. If the destination server is also expecting a trailing slash it should be included in thedestination parameter as well.
module.exports= { trailingSlash:true,asyncrewrites() {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
module.exports= {asyncrewrites() {return { fallback: [ { source:'/:path*', destination:`https://custom-routes-proxying-endpoint.vercel.app/:path*`, }, ], } },}Rewrites with basePath support
When leveragingbasePath support with rewrites eachsource anddestination is automatically prefixed with thebasePath unless you addbasePath: false to the rewrite:
module.exports= { basePath:'/docs',asyncrewrites() {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 can not be used for internal rewrites e.g. `destination: '/another'` source:'/without-basePath', destination:'https://example.com', basePath:false, }, ] },}Rewrites with i18n support
When leveragingi18n support with rewrites eachsource anddestination is automatically prefixed to handle the configuredlocales unless you addlocale: false to the rewrite. Iflocale: false is used you must prefix thesource anddestination with a locale for it to be matched correctly.
module.exports= { i18n: { locales: ['en','fr','de'], defaultLocale:'en', },asyncrewrites() {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', }, ] },}Version History
| Version | Changes |
|---|---|
v13.3.0 | missing added. |
v10.2.0 | has added. |
v9.5.0 | Headers added. |
Was this helpful?