rewrites()

Proxies an incoming path to another path or origin while the browser's URL stays unchanged.

Signature
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

ParameterTypeDescription
$sourcestringIncoming request path pattern, path-to-regexp syntax, anchored to the start of the path.
$destinationstringInternal 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.
$basePathoptionalfalse | undefined= undefinedfalse stops basePath being prefixed. Usable only for external rewrites, not for an internal destination.
$localeoptionalfalse | undefined= undefinedfalse stops the locale being included when matching.
$hasoptionalRouteHas[]Header, cookie, host or query conditions that must all match for the rewrite to apply.
$missingoptionalRouteHas[]Conditions that must all NOT match for the rewrite to apply.
$beforeFilesoptionalRewrite[]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.
$afterFilesoptionalRewrite[]Checked after pages and public files but before dynamic routes. The first entry that resolves to something servable wins.
$fallbackoptionalRewrite[]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

Example · typescript
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 nextConfig

When 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.

Example · typescript
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 nextConfig

Passing 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.

Example · typescript
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

  • Be the first to comment on this lesson.