rewrites

Rewrites 允許您將傳入的請求路徑映射到不同的目標路徑。

Rewrites 作為 URL 代理並遮罩目標路徑,使使用者看起來像是沒有改變他們在網站上的位置。相比之下,redirects 會重新路由到新頁面並顯示 URL 變更。

要使用 rewrites,您可以在 next.config.js 中使用 rewrites 鍵:

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

Rewrites 會應用於客戶端路由,在上述範例中,<Link href="/about"> 將會套用 rewrite。

rewrites 是一個非同步函數,預期返回一個陣列或包含陣列的物件(見下文),這些陣列包含具有 sourcedestination 屬性的物件:

  • source: String - 傳入的請求路徑模式。
  • destination: String - 您想要路由到的路徑。
  • basePath: falseundefined - 如果為 false,匹配時將不包含 basePath,僅可用於外部 rewrites。
  • locale: falseundefined - 匹配時是否不包含 locale。
  • has 是一個包含 typekeyvalue 屬性的 has 物件 陣列。
  • missing 是一個包含 typekeyvalue 屬性的 missing 物件 陣列。

rewrites 函數返回一個陣列時,rewrites 會在檢查檔案系統(頁面和 /public 檔案)之後、動態路由之前應用。當 rewrites 函數返回具有特定結構的物件陣列時,可以改變並更精細地控制此行為,自 Next.js v10.1 起:

next.config.js
module.exports = {
  async rewrites() {
    return {
      beforeFiles: [
        // 這些 rewrites 在 headers/redirects 之後檢查
        // 並在所有檔案(包括 _next/public 檔案)之前
        // 允許覆蓋頁面檔案
        {
          source: '/some-page',
          destination: '/somewhere-else',
          has: [{ type: 'query', key: 'overrideMe' }],
        },
      ],
      afterFiles: [
        // 這些 rewrites 在檢查頁面/public 檔案之後
        // 但在動態路由之前檢查
        {
          source: '/non-existent',
          destination: '/somewhere-else',
        },
      ],
      fallback: [
        // 這些 rewrites 在檢查頁面/public 檔案
        // 和動態路由之後檢查
        {
          source: '/:path*',
          destination: `https://my-old-site.com/:path*`,
        },
      ],
    }
  },
}

須知beforeFiles 中的 rewrites 在匹配 source 後不會立即檢查檔案系統/動態路由,它們會繼續檢查直到所有 beforeFiles 都被檢查過。

Next.js 路由的檢查順序是:

  1. 檢查/應用 headers
  2. 檢查/應用 redirects
  3. 檢查/應用 beforeFiles rewrites
  4. 檢查/提供來自 public 目錄_next/static 檔案和非動態頁面的靜態檔案
  5. 檢查/應用 afterFiles rewrites,如果其中一個 rewrites 匹配,我們會在每次匹配後檢查動態路由/靜態檔案
  6. 檢查/應用 fallback rewrites,這些會在渲染 404 頁面之前和檢查所有動態路由/靜態資源之後應用。如果您在 getStaticPaths 中使用 fallback: true/'blocking',則 next.config.js 中定義的 fallback rewrites 不會 執行。

Rewrite 參數

當在 rewrite 中使用參數時,如果 destination 中沒有使用任何參數,則參數將預設傳遞在查詢中。

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/old-about/:path*',
        destination: '/about', // 此處未使用 :path 參數,因此將自動傳遞在查詢中
      },
    ]
  },
}

如果在 destination 中使用了參數,則不會自動將任何參數傳遞在查詢中。

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/docs/:path*',
        destination: '/:path*', // 此處使用了 :path 參數,因此不會自動傳遞在查詢中
      },
    ]
  },
}

如果 destination 中已經使用了參數,您仍然可以通過在 destination 中指定查詢來手動傳遞參數。

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/:first/:second',
        destination: '/:first?second=:second',
        // 由於 :first 參數已在 destination 中使用,:second 參數
        // 不會自動添加到查詢中,但我們可以手動添加
        // 如上所示
      },
    ]
  },
}

須知:來自 Automatic Static Optimizationprerendering 的靜態頁面的 rewrite 參數將在 hydration 後在客戶端解析並提供在查詢中。

路徑匹配

允許路徑匹配,例如 /blog/:slug 將匹配 /blog/hello-world(無嵌套路徑):

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

萬用字元路徑匹配

要匹配萬用字元路徑,您可以在參數後使用 *,例如 /blog/:slug* 將匹配 /blog/a/b/c/d/hello-world

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

正則表達式路徑匹配

要匹配正則表達式路徑,您可以將正則表達式包裹在參數後的括號中,例如 /blog/:slug(\\d{1,}) 將匹配 /blog/123 但不匹配 /blog/abc

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

以下字符 (){}[]|\^.:*+-?$ 用於正則表達式路徑匹配,因此當在 source 中作為非特殊值使用時,必須通過在它們前面添加 \\ 來進行轉義:

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        // 這將匹配請求的 `/english(default)/something`
        source: '/english\\(default\\)/:slug',
        destination: '/en-us/:slug',
      },
    ]
  },
}

標頭、Cookie 和查詢匹配

要僅在標頭、cookie 或查詢值也匹配 has 欄位或不匹配 missing 欄位時才匹配 rewrite,可以使用 hasmissingsource 和所有 has 項目必須匹配,且所有 missing 項目必須不匹配,rewrite 才會被應用。

hasmissing 項目可以具有以下欄位:

  • type: String - 必須是 headercookiehostquery 之一。
  • key: String - 要匹配的所選類型的鍵。
  • value: Stringundefined - 要檢查的值,如果為 undefined 則任何值都會匹配。可以使用類似正則表達式的字符串來捕獲值的特定部分,例如,如果值 first-(?<paramName>.*) 用於 first-second,則 second 可以在 destination 中使用 :paramName
next.config.js
module.exports = {
  async rewrites() {
    return [
      // 如果存在標頭 `x-rewrite-me`,
      // 將應用此 rewrite
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // 如果不存在標頭 `x-rewrite-me`,
      // 將應用此 rewrite
      {
        source: '/:path*',
        missing: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // 如果 source、查詢和 cookie 都匹配,
      // 將應用此 rewrite
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            // 由於提供了 value 並且沒有使用命名捕獲組(例如 `(?<page>home)`),
            // page 值將不會在 destination 中可用
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        destination: '/:path*/home',
      },
      // 如果存在標頭 `x-authorized` 並且
      // 包含匹配的值,將應用此 rewrite
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        destination: '/home?authorized=:authorized',
      },
      // 如果 host 是 `example.com`,
      // 將應用此 rewrite
      {
        source: '/:path*',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        destination: '/another-page',
      },
    ]
  },
}

重寫到外部 URL

範例

Rewrites 允許您重寫到外部 URL。這對於逐步採用 Next.js 特別有用。以下是將您的主應用程式的 /blog 路由重定向到外部網站的 rewrite 範例。

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

如果您使用 trailingSlash: true,您還需要在 source 參數中插入尾部斜線。如果目標伺服器也期望尾部斜線,則應在 destination 參數中包含它。

next.config.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 時,您不需要更改 rewrites 配置。

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

支援 basePath 的 Rewrites

當與 rewrites 一起使用 basePath 支援 時,每個 sourcedestination 會自動加上 basePath 前綴,除非您在 rewrite 中添加 basePath: false

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

  async rewrites() {
    return [
      {
        source: '/with-basePath', // 自動變為 /docs/with-basePath
        destination: '/another', // 自動變為 /docs/another
      },
      {
        // 由於設置了 basePath: false,不會將 /docs 添加到 /without-basePath
        // 注意:這不能用於內部 rewrites,例如 `destination: '/another'`
        source: '/without-basePath',
        destination: 'https://example.com',
        basePath: false,
      },
    ]
  },
}

支援 i18n 的 Rewrites

當與 rewrites 一起使用 i18n 支援 時,每個 sourcedestination 會自動加上前綴以處理配置的 locales,除非您在 rewrite 中添加 locale: false。如果使用 locale: false,您必須在 sourcedestination 前加上 locale 才能正確匹配。

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

  async rewrites() {
    return [
      {
        source: '/with-locale', // 自動處理所有 locales
        destination: '/another', // 自動傳遞 locale
      },
      {
        // 由於設置了 locale: false,不會自動處理 locales
        source: '/nl/with-locale-manual',
        destination: '/nl/another',
        locale: false,
      },
      {
        // 這匹配 '/',因為 `en` 是 defaultLocale
        source: '/en',
        destination: '/en/another',
        locale: false,
      },
      {
        // 即使設置了 locale: false,也可以匹配所有 locales
        source: '/:locale/api-alias/:path*',
        destination: '/api/:path*',
        locale: false,
      },
      {
        // 這將轉換為 /(en|fr|de)/(.*),因此不會匹配頂層
        // `/` 或 `/fr` 路由,像 /:path* 那樣
        source: '/(.*)',
        destination: '/another',
      },
    ]
  },
}

版本歷史

版本變更內容
v13.3.0新增 missing
v10.2.0新增 has
v9.5.0新增標頭。

On this page