Skip to content

重写允许您将传入的请求路径映射到不同的目标路径。

要使用重写,您可以在 next.config.js 中使用 rewrites 键:

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/about',
        destination: '/',
      },
    ]
  },
}

重写应用于客户端路由,在上面的示例中,<Link href="/about"> 将应用重写。

rewrites 是一个异步函数,期望返回一个数组或一个包含数组的对象(见下文),其中包含具有 sourcedestination 属性的对象:

  • source: String - 是传入的请求路径模式。
  • destination: String 是您想要路由到的路径。
  • basePath: falseundefined - 如果为 false,则在匹配时不包括 basePath,只能用于外部重写。
  • locale: falseundefined - 是否在匹配时不包括地区。
  • has 是一个 has 对象 数组,具有 typekeyvalue 属性。
  • missing 是一个 missing 对象 数组,具有 typekeyvalue 属性。

rewrites 函数返回一个数组时,重写在检查文件系统(页面和 /public 文件)之后以及动态路由之前应用。当 rewrites 函数返回一个具有特定形状的数组对象时,这种行为可以改变,并且可以更精细地控制,从 Next.js 的 v10.1 开始:

js
module.exports = {
  async rewrites() {
    return {
      beforeFiles: [
        // 这些重写在检查 headers/redirects 之后进行检查
        // 并在检查所有文件包括 _next/public 文件之前
        // 允许覆盖页面文件
        {
          source: '/some-page',
          destination: '/somewhere-else',
          has: [{ type: 'query', key: 'overrideMe' }],
        },
      ],
      afterFiles: [
        // 这些重写在检查 pages/public 文件之后进行检查
        // 但在动态路由之前
        {
          source: '/non-existent',
          destination: '/somewhere-else',
        },
      ],
      fallback: [
        // 这些重写在检查 pages/public 文件和动态路由之后进行检查
        {
          source: '/:path*',
          destination: `https://my-old-site.com/:path*`,
        },
      ],
    }
  },
}

须知

须知:在 beforeFiles 中的重写不会在匹配到源路径后立即检查文件系统/动态路由,它们会继续执行直到所有 beforeFiles 都被检查完毕。

Next.js 路由的检查顺序如下:

重写参数

在使用重写中的参数时,如果 destination 中没有使用任何参数,参数将默认通过查询传递。

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/old-about/:path*',
        destination: '/about', // 这里的 :path 参数没有使用,所以将自动通过查询传递
      },
    ]
  },
}

如果 destination 中使用了参数,则不会自动通过查询传递任何参数。

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/docs/:path*',
        destination: '/:path*', // 这里的 :path 参数使用了,所以不会自动通过查询传递
      },
    ]
  },
}

如果目的地中已经使用了参数,你仍然可以通过在 destination 中指定查询来手动传递参数。

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/:first/:second',
        destination: '/:first?second=:second',
        // 由于 :first 参数在目的地中使用了,:second 参数
        // 将不会自动添加到查询中,尽管我们可以像上面所示手动添加它
      },
    ]
  },
}

须知:来自 自动静态优化预渲染 的静态页面的重写参数将在客户端水合后解析,并在查询中提供。

路径匹配

路径匹配是允许的,例如 /blog/:slug 将匹配 /blog/hello-world(不匹配嵌套路径):

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog/:slug',
        destination: '/news/:slug', // 匹配的参数可以在目标中使用
      },
    ]
  },
}

通配符路径匹配

要匹配通配符路径,可以在参数后使用 *,例如 /blog/:slug* 将匹配 /blog/a/b/c/d/hello-world

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog/:slug*',
        destination: '/news/:slug*', // 匹配的参数可以在目标中使用
      },
    ]
  },
}

正则表达式路径匹配

要匹配正则表达式路径,可以将正则表达式用括号括起来放在参数后面,例如 /blog/:slug(\\d{1,}) 将匹配 /blog/123 但不会匹配 /blog/abc

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/old-blog/:post(\\d{1,})',
        destination: '/blog/:post', // 匹配的参数可以在目标中使用
      },
    ]
  },
}

以下字符 (, ), {, }, [, ], |, \, ^, ., :, *, +, -, ?, $ 用于正则表达式路径匹配,因此当它们在 source 中作为非特殊值使用时,必须通过在它们前面添加 \\ 进行转义:

js
module.exports = {
  async rewrites() {
    return [
      {
        // 这将匹配被请求的 `/english(default)/something`
        source: '/english\\(default\\)/:slug',
        destination: '/en-us/:slug',
      },
    ]
  },
}

标题、Cookie和查询匹配

要仅在标题、Cookie或查询值也匹配has字段或不匹配missing字段时匹配重写,可以使用has字段或missing字段。必须匹配source和所有has项,并且所有missing项都不匹配,才能应用重写。

hasmissing项可以具有以下字段:

  • type: String - 必须是headercookiehostquery之一。
  • key: String - 要匹配的所选类型中的键。
  • value: Stringundefined - 要检查的值,如果未定义,则任何值都将匹配。可以使用类似正则表达式的字符串来捕获值的特定部分,例如,如果对于first-second使用值first-(?<paramName>.*),则second将在目的地中可用,使用:paramName
js
module.exports = {
  async rewrites() {
    return [
      // 如果存在标题 `x-rewrite-me`,
      // 将应用此重写
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // 如果不存在标题 `x-rewrite-me`,
      // 将应用此重写
      {
        source: '/:path*',
        missing: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // 如果匹配源、查询和Cookie,
      // 将应用此重写
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            // 由于提供了值并且没有使用命名捕获组
            // 例如 (?<page>home),因此在目的地中页面值将不可用
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        destination: '/:path*/home',
      },
      // 如果存在标题 `x-authorized` 并且
      // 包含匹配的值,将应用此重写
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        destination: '/home?authorized=:authorized',
      },
      // 如果主机是 `example.com`,
      // 将应用此重写
      {
        source: '/:path*',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        destination: '/another-page',
      },
    ]
  },
}

重写到外部URL

示例

重写允许您重写到一个外部URL。这对于逐步采用Next.js特别有用。以下是一个示例重写,用于将主应用的/blog路由重定向到一个外部网站。

js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog',
        destination: 'https://example.com/blog',
      },
      {
        source: '/blog/:slug',
        destination: 'https://example.com/blog/:slug', // 匹配的参数可以在目的地中使用
      },
    ]
  },
}

如果您使用trailingSlash: true,您还需要在source参数中插入一个尾随斜杠。如果目标服务器也期望有一个尾随斜杠,它应该包含在destination参数中。

js
module.exports = {
  trailingSlash: true,
  async rewrites() {
    return [
      {
        source: '/blog/',
        destination: 'https://example.com/blog/',
      },
      {
        source: '/blog/:path*/',
        destination: 'https://example.com/blog/:path*/',
      },
    ]
  },
}

逐步采用Next.js

您也可以在检查完所有Next.js路由后,让Next.js回退到代理到现有网站。

这样,在将更多页面迁移到Next.js时,您不必更改重写配置。

js
module.exports = {
  async rewrites() {
    return {
      fallback: [
        {
          source: '/:path*',
          destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`,
        },
      ],
    }
  },
}

支持basePath的重写

当利用basePath支持与重写时,每个sourcedestination会自动加上basePath前缀,除非您在重写中添加basePath: false

js
module.exports = {
  basePath: '/docs',

  async rewrites() {
    return [
      {
        source: '/with-basePath', // 自动变为 /docs/with-basePath
        destination: '/another', // 自动变为 /docs/another
      },
      {
        // 不添加 /docs 到 /without-basePath,因为设置了 basePath: false
        // 须知:这不能用于内部重写,例如 `destination: '/another'`
        source: '/without-basePath',
        destination: 'https://example.com',
        basePath: false,
      },
    ]
  },
}

Rewrites with i18n support

js
module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'],
    defaultLocale: 'en',
  },

  async rewrites() {
    return [
      {
        source: '/with-locale', // 自动处理所有语言环境
        destination: '/another', // 自动传递语言环境
      },
      {
        // 由于设置了 locale: false,不自动处理语言环境
        source: '/nl/with-locale-manual',
        destination: '/nl/another',
        locale: false,
      },
      {
        // 由于 `en` 是 defaultLocale,这会匹配 '/'
        source: '/en',
        destination: '/en/another',
        locale: false,
      },
      {
        // 即使设置了 locale: false,也可以匹配所有语言环境
        source: '/:locale/api-alias/:path*',
        destination: '/api/:path*',
        locale: false,
      },
      {
        // 这将被转换为 /(en|fr|de)/(.*),因此不会匹配顶级的
        // `/` 或 `/fr` 路由,像 /:path* 会的那样
        source: '/(.*)',
        destination: '/another',
      },
    ]
  },
}

版本历史

版本变更
v13.3.0missing 添加。
v10.2.0has 添加。
v9.5.0添加了头部。