문서
App Router 사용하기
rewrites

rewrites

리라이트를 사용하면 들어오는 요청 경로를 다른 엔드포인트 경로로 매핑할 수 있습니다.

리라이트는 URL 프록시 역할을 하며 엔드포인트 경로를 마스킹하여 사용자가 사이트에서 위치를 변경하지 않은 것처럼 보이게 합니다. 반면에 리다이렉트는 새 페이지로 라우팅하고 URL 변경을 보여줍니다.

리라이트를 사용하려면 next.config.js에서 rewrites 키를 사용할 수 있습니다:

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

리라이트는 클라이언트 측 라우팅에 적용됩니다. 위의 예에서 <Link href="/about">는 리라이트가 적용됩니다.

rewritessourcedestination 속성을 가진 객체들의 배열 또는 배열의 객체(아래 참조)를 반환할 것으로 예상되는 비동기 함수입니다:

  • source: String. 들어오는 요청 경로 패턴입니다.
  • destination: String. 라우팅하고자 하는 경로입니다.
  • basePath: false 또는 undefined. false이면 매칭 시 basePath가 포함되지 않습니다. 외부 리라이트에만 사용할 수 있습니다.
  • locale: false 또는 undefined. 매칭 시 로케일을 포함해야 하는지 여부입니다.
  • hastype, key, value 속성을 가진 has 객체의 배열입니다.
  • missingtype, key, value 속성을 가진 missing 객체의 배열입니다.

rewrites 함수가 배열을 반환할 때, 리라이트는 파일 시스템(페이지 및 /public 파일)을 확인한 후 동적 라우트 전에 적용됩니다. rewrites 함수가 특정 형태의 배열 객체를 반환할 때, 이 동작을 변경하고 더 세밀하게 제어할 수 있습니다. Next.js의 v10.1부터 가능합니다:

next.config.js
module.exports = {
  async rewrites() {
    return {
      beforeFiles: [
        // 이 리라이트들은 헤더/리다이렉트 후에 확인되며
        // _next/public 파일을 포함한 모든 파일 전에 확인됩니다
        // 이를 통해 페이지 파일을 재정의할 수 있습니다
        {
          source: "/some-page",
          destination: "/somewhere-else",
          has: [{ type: "query", key: "overrideMe" }],
        },
      ],
      afterFiles: [
        // 이 리라이트들은 페이지/public 파일이 확인된 후에
        // 확인되지만 동적 라우트 전에 확인됩니다
        {
          source: "/non-existent",
          destination: "/somewhere-else",
        },
      ],
      fallback: [
        // 이 리라이트들은 페이지/public 파일과
        // 동적 라우트가 모두 확인된 후에 확인됩니다
        {
          source: "/:path*",
          destination: `https://my-old-site.com/:path*`,
        },
      ],
    };
  },
};

알아두면 좋은 점: beforeFiles의 리라이트는 소스와 일치한 직후에 파일 시스템/동적 라우트를 확인하지 않고, 모든 beforeFiles가 확인될 때까지 계속됩니다.

Next.js 라우트가 확인되는 순서는 다음과 같습니다:

  1. 헤더가 확인/적용됩니다
  2. 리다이렉트가 확인/적용됩니다
  3. beforeFiles 리라이트가 확인/적용됩니다
  4. public 디렉토리의 정적 파일, _next/static 파일, 비동적 페이지가 확인/제공됩니다
  5. afterFiles 리라이트가 확인/적용됩니다. 이 중 하나의 리라이트가 일치하면 각 일치 후에 동적 라우트/정적 파일을 확인합니다
  6. fallback 리라이트가 확인/적용됩니다. 이는 404 페이지를 렌더링하기 전과 동적 라우트/모든 정적 자산이 확인된 후에 적용됩니다. getStaticPaths에서 fallback: true/'blocking'을 사용하는 경우, next.config.js에 정의된 fallback rewrites는 실행되지 않습니다.

리라이트 파라미터

리라이트에서 파라미터를 사용할 때, destination에서 파라미터가 사용되지 않으면 기본적으로 파라미터가 쿼리에 전달됩니다.

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/old-about/:path*",
        destination: "/about", // :path 파라미터가 여기서 사용되지 않으므로 자동으로 쿼리에 전달됩니다
      },
    ];
  },
};

엔드포인트에서 파라미터가 사용되면 파라미터가 자동으로 쿼리에 전달되지 않습니다.

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/docs/:path*",
        destination: "/:path*", // :path 파라미터가 여기서 사용되므로 자동으로 쿼리에 전달되지 않습니다
      },
    ];
  },
};

엔드포인트에서 이미 하나가 사용되고 있더라도 destination에서 쿼리를 지정하여 수동으로 파라미터를 전달할 수 있습니다.

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/:first/:second",
        destination: "/:first?second=:second",
        // :first 파라미터가 엔드포인트에서 사용되므로 :second 파라미터는
        // 자동으로 쿼리에 추가되지 않지만 위와 같이 수동으로 추가할 수 있습니다
      },
    ];
  },
};

알아두면 좋은 점: 자동 정적 최적화의 정적 페이지나 사전 렌더링의 리라이트에서 파라미터는 클라이언트에서 하이드레이션 후에 파싱되어 쿼리에서 제공됩니다.

경로 매칭

경로 매칭이 허용됩니다. 예를 들어 /blog/:slug/blog/hello-world와 일치합니다 (중첩 경로 없음):

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/blog/:slug",
        destination: "/news/:slug", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
      },
    ];
  },
};

와일드카드 경로 매칭

와일드카드 경로를 매칭하려면 파라미터 뒤에 *를 사용할 수 있습니다. 예를 들어 /blog/:slug*/blog/a/b/c/d/hello-world와 일치합니다:

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/blog/:slug*",
        destination: "/news/:slug*", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
      },
    ];
  },
};

정규식 경로 매칭

정규식 경로를 매칭하려면 파라미터 뒤에 괄호 안에 정규식을 넣을 수 있습니다. 예를 들어 /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", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
      },
    ];
  },
};

다음 문자 (, ), {, }, [, ], |, \, ^, ., :, *, +, -, ?, $는 정규식 경로 매칭에 사용되므로, source에서 특별하지 않은 값으로 사용될 때는 \\를 앞에 추가하여 이스케이프해야 합니다:

next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        // 이는 요청된 `/english(default)/something`과 일치합니다
        source: "/english\\(default\\)/:slug",
        destination: "/en-us/:slug",
      },
    ];
  },
};

헤더, 쿠키, 쿼리 매칭

헤더, 쿠키 또는 쿼리 값이 has 필드와 일치하거나 missing 필드와 일치하지 않을 때만 리라이트를 매칭하려면 has 필드를 사용할 수 있습니다. 리라이트가 적용되려면 source와 모든 has 항목이 일치해야 하고 모든 missing 항목이 일치하지 않아야 합니다.

hasmissing 항목은 다음 필드를 가질 수 있습니다:

  • type: String. header, cookie, host, 또는 query 중 하나여야 합니다.
  • key: String. 선택한 타입에서 매칭할 키입니다.
  • value: String 또는 undefined. 확인할 값입니다. undefined인 경우 모든 값이 일치합니다. 정규식 같은 문자열을 사용하여 값의 특정 부분을 캡처할 수 있습니다. 예를 들어 first-(?<paramName>.*)라는 값이 first-second에 사용되면 second는 엔드포인트에서 :paramName으로 사용할 수 있습니다.
next.config.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",
      },
      // source, query, cookie가 일치하면,
      // 이 리라이트가 적용됩니다
      {
        source: "/specific/:path*",
        has: [
          {
            type: "query",
            key: "page",
            // 값이 제공되고 명명된 캡처 그룹을 사용하지 않으므로
            // (예: (?<page>home)) page 값은 엔드포인트에서 사용할 수 없습니다
            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 경로를 외부 사이트로 리다이렉트하는 리라이트 예시입니다.

next.config.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 파라미터에도 포함되어야 합니다.

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로 마이그레이션할 때 리라이트 구성을 변경할 필요가 없습니다.

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

basePath 지원이 있는 리라이트

리라이트와 함께 basePath 지원을 활용할 때, basePath: false를 리라이트에 추가하지 않는 한 각 sourcedestination은 자동으로 basePath로 접두사가 붙습니다:

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에 추가하지 않습니다
        // 참고: 이는 내부 리라이트에는 사용할 수 없습니다. 예: `destination: '/another'`
        source: "/without-basePath",
        destination: "https://example.com",
        basePath: false,
      },
    ];
  },
};

i18n 지원이 있는 리라이트

리라이트와 함께 i18n 지원을 활용할 때, locale: false를 리라이트에 추가하지 않는 한 각 sourcedestination은 구성된 locales를 처리하기 위해 자동으로 접두사가 붙습니다. locale: false가 사용되면 올바르게 매칭되도록 sourcedestination에 로케일을 접두사로 붙여야 합니다.

next.config.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)/(.*) 로 변환되므로 /:path*가 할 수 있는 것처럼
        // 최상위 `/` 또는 `/fr` 경로와 일치하지 않습니다
        source: "/(.*)",
        destination: "/another",
      },
    ];
  },
};

버전 히스토리

버전변경 사항
v13.3.0missing 추가됨.
v10.2.0has 추가됨.
v9.5.0헤더 추가됨.