rewrites()
Proxies an incoming path to another path or origin while the browser's URL stays unchanged.
rewrites?(): Promise<Rewrite[] | RewriteGroups> | Rewrite[] | RewriteGroups
type Rewrite = {
source: string
destination: string
basePath?: false
locale?: false
has?: RouteHas[]
missing?: RouteHas[]
}
type RewriteGroups = {
beforeFiles?: Rewrite[]
afterFiles?: Rewrite[]
fallback?: Rewrite[]
}- Added in Next.js v9.5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$source | string | Incoming request path pattern, path-to-regexp syntax, anchored to the start of the path. |
$destination | string | Internal path or absolute external URL to serve. Named parameters from source may be reused; if none are used in the destination, all matched parameters are appended to the query instead. |
$basePathoptional | false | undefined= undefined | false stops basePath being prefixed. Usable only for external rewrites, not for an internal destination. |
$localeoptional | false | undefined= undefined | false stops the locale being included when matching. |
$hasoptional | RouteHas[] | Header, cookie, host or query conditions that must all match for the rewrite to apply. |
$missingoptional | RouteHas[] | Conditions that must all NOT match for the rewrite to apply. |
$beforeFilesoptional | Rewrite[] | Checked after headers, redirects and proxy but before the filesystem, so these can override real page and public files. Every entry is evaluated; matching one does not stop the pass. |
$afterFilesoptional | Rewrite[] | Checked after pages and public files but before dynamic routes. The first entry that resolves to something servable wins. |
$fallbackoptional | Rewrite[] | Checked last, after dynamic routes and before the 404 page. The usual home for a catch-all proxy to a legacy origin. |
Return value
Promise<Rewrite[] | RewriteGroups> | Rewrite[] | RewriteGroupsAn array of rewrites, or an object grouping them into beforeFiles, afterFiles and fallback phases.
Errors
Not supported with output: 'export'; using rewrites in a static export errors at build time.
Changelog
- PHP v13.3.0missing added.
- PHP v10.2.0has added.
- PHP v10.1.0beforeFiles, afterFiles and fallback grouping added.
- PHP v9.5.0rewrites added.
A rewrite is a server-side proxy. The user asked for /about, sees /about, and gets whatever destination produces — including a response from a completely different host. Rewrites also apply to client-side navigation, so <Link href="/about"> follows the same mapping.
When to reach for it
- Strangling a legacy site: send everything Next.js cannot route to the old origin via
fallback. - Putting an API on your own origin to sidestep CORS.
- Stitching several Next.js deployments into one apparent domain.
What trips people up
The plain array form is afterFiles, so a real page always beats it — if your rewrite seems ignored, a file of that name exists and you wanted beforeFiles. Parameter forwarding is all-or-nothing: use a parameter in destination and the rest stop being appended to the query, so pass them manually. With trailingSlash: true the trailing slash must appear in source too. And beforeFiles does not short-circuit — every entry in that group is evaluated even after one matches.
Example
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://api.example.com/:path*',
},
{
source: '/blog/:slug',
destination: 'https://legacy.example.com/blog/:slug',
},
]
},
}
export default nextConfigWhen to use it
- Proxy /api/:path* to an internal service so the browser sees a same-origin request
- Fall back to an old WordPress site for any path the new app does not handle
- Serve a docs sub-site deployed separately under /docs on the main domain
- A/B a page by rewriting to a variant when an experiment cookie is present
Frequently asked questions
Why is my rewrite being ignored?
Because an array return is treated as afterFiles, which is checked after real pages and public files. If a route or file already answers that path, it wins. Move the entry into the beforeFiles group to override a real file.
Can rewrites proxy to an external domain?
Yes. Set destination to an absolute URL and Next.js proxies the request server-side while the browser URL stays put. This is the standard way to avoid CORS for an API and to migrate incrementally off a legacy origin using the fallback group.
Do rewrites work with output: 'export'?
No. Rewrites need a server, so they are on the unsupported list for static exports, along with redirects, headers and proxy. Handle the mapping in your static host, for example an Nginx rewrite block or the CDN's own rules.
More examples
Controlling the phase with beforeFiles, afterFiles and fallback
The three groups sit at different points in the routing pass: beforeFiles runs after headers, redirects and proxy but before any file is served; afterFiles runs after pages and public files but before dynamic routes; fallback runs last, just before the 404. The fallback entry here is the incremental-migration pattern — anything the new app cannot serve is proxied to the old site, and routes stop being proxied as you port them.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return {
beforeFiles: [
// wins over a real /some-page route
{
source: '/some-page',
destination: '/somewhere-else',
has: [{ type: 'query', key: 'overrideMe' }],
},
],
afterFiles: [
{
source: '/non-existent',
destination: '/somewhere-else',
},
],
fallback: [
{
source: '/:path*',
destination: 'https://my-old-site.com/:path*',
},
],
}
},
}
export default nextConfigPassing parameters through to the query
Matched parameters are appended to the destination query automatically only when none of them appear in the destination path. As soon as one is used, that automatic forwarding stops for all of them — the second entry shows the manual form. This is the usual reason a searchParams value silently disappears after a rewrite is added.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
// :path is unused in the destination, so it is added to the query
source: '/old-about/:path*',
destination: '/about',
},
{
// :first is used, so :second must be passed manually
source: '/:first/:second',
destination: '/:first?second=:second',
},
]
},
}
export default nextConfig
Discussion