rewrites
리라이트를 사용하면 들어오는 요청 경로를 다른 엔드포인트 경로로 매핑할 수 있습니다.
리라이트는 URL 프록시 역할을 하며 엔드포인트 경로를 마스킹하여 사용자가 사이트에서 위치를 변경하지 않은 것처럼 보이게 합니다. 반면에 리다이렉트는 새 페이지로 라우팅하고 URL 변경을 보여줍니다.
리라이트를 사용하려면 next.config.js
에서 rewrites
키를 사용할 수 있습니다:
module.exports = {
async rewrites() {
return [
{
source: "/about",
destination: "/",
},
];
},
};
리라이트는 클라이언트 측 라우팅에 적용됩니다. 위의 예에서 <Link href="/about">
는 리라이트가 적용됩니다.
rewrites
는 source
와 destination
속성을 가진 객체들의 배열 또는 배열의 객체(아래 참조)를 반환할 것으로 예상되는 비동기 함수입니다:
source
:String
. 들어오는 요청 경로 패턴입니다.destination
:String
. 라우팅하고자 하는 경로입니다.basePath
:false
또는undefined
. false이면 매칭 시 basePath가 포함되지 않습니다. 외부 리라이트에만 사용할 수 있습니다.locale
:false
또는undefined
. 매칭 시 로케일을 포함해야 하는지 여부입니다.has
는type
,key
,value
속성을 가진 has 객체의 배열입니다.missing
은type
,key
,value
속성을 가진 missing 객체의 배열입니다.
rewrites
함수가 배열을 반환할 때, 리라이트는 파일 시스템(페이지 및 /public
파일)을 확인한 후 동적 라우트 전에 적용됩니다. rewrites
함수가 특정 형태의 배열 객체를 반환할 때, 이 동작을 변경하고 더 세밀하게 제어할 수 있습니다. Next.js의 v10.1
부터 가능합니다:
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 라우트가 확인되는 순서는 다음과 같습니다:
- 헤더가 확인/적용됩니다
- 리다이렉트가 확인/적용됩니다
beforeFiles
리라이트가 확인/적용됩니다- public 디렉토리의 정적 파일,
_next/static
파일, 비동적 페이지가 확인/제공됩니다 afterFiles
리라이트가 확인/적용됩니다. 이 중 하나의 리라이트가 일치하면 각 일치 후에 동적 라우트/정적 파일을 확인합니다fallback
리라이트가 확인/적용됩니다. 이는 404 페이지를 렌더링하기 전과 동적 라우트/모든 정적 자산이 확인된 후에 적용됩니다.getStaticPaths
에서 fallback: true/'blocking'을 사용하는 경우,next.config.js
에 정의된 fallbackrewrites
는 실행되지 않습니다.
리라이트 파라미터
리라이트에서 파라미터를 사용할 때, destination
에서 파라미터가 사용되지 않으면 기본적으로 파라미터가 쿼리에 전달됩니다.
module.exports = {
async rewrites() {
return [
{
source: "/old-about/:path*",
destination: "/about", // :path 파라미터가 여기서 사용되지 않으므로 자동으로 쿼리에 전달됩니다
},
];
},
};
엔드포인트에서 파라미터가 사용되면 파라미터가 자동으로 쿼리에 전달되지 않습니다.
module.exports = {
async rewrites() {
return [
{
source: "/docs/:path*",
destination: "/:path*", // :path 파라미터가 여기서 사용되므로 자동으로 쿼리에 전달되지 않습니다
},
];
},
};
엔드포인트에서 이미 하나가 사용되고 있더라도 destination
에서 쿼리를 지정하여 수동으로 파라미터를 전달할 수 있습니다.
module.exports = {
async rewrites() {
return [
{
source: "/:first/:second",
destination: "/:first?second=:second",
// :first 파라미터가 엔드포인트에서 사용되므로 :second 파라미터는
// 자동으로 쿼리에 추가되지 않지만 위와 같이 수동으로 추가할 수 있습니다
},
];
},
};
알아두면 좋은 점: 자동 정적 최적화의 정적 페이지나 사전 렌더링의 리라이트에서 파라미터는 클라이언트에서 하이드레이션 후에 파싱되어 쿼리에서 제공됩니다.
경로 매칭
경로 매칭이 허용됩니다. 예를 들어 /blog/:slug
는 /blog/hello-world
와 일치합니다 (중첩 경로 없음):
module.exports = {
async rewrites() {
return [
{
source: "/blog/:slug",
destination: "/news/:slug", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
},
];
},
};
와일드카드 경로 매칭
와일드카드 경로를 매칭하려면 파라미터 뒤에 *
를 사용할 수 있습니다. 예를 들어 /blog/:slug*
는 /blog/a/b/c/d/hello-world
와 일치합니다:
module.exports = {
async rewrites() {
return [
{
source: "/blog/:slug*",
destination: "/news/:slug*", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
},
];
},
};
정규식 경로 매칭
정규식 경로를 매칭하려면 파라미터 뒤에 괄호 안에 정규식을 넣을 수 있습니다. 예를 들어 /blog/:slug(\\d{1,})
는 /blog/123
과 일치하지만 /blog/abc
와는 일치하지 않습니다:
module.exports = {
async rewrites() {
return [
{
source: "/old-blog/:post(\\d{1,})",
destination: "/blog/:post", // 매치된 파라미터는 엔드포인트에서 사용될 수 있습니다
},
];
},
};
다음 문자 (
, )
, {
, }
, [
, ]
, |
, \
, ^
, .
, :
, *
, +
, -
, ?
, $
는 정규식 경로 매칭에 사용되므로, source
에서 특별하지 않은 값으로 사용될 때는 \\
를 앞에 추가하여 이스케이프해야 합니다:
module.exports = {
async rewrites() {
return [
{
// 이는 요청된 `/english(default)/something`과 일치합니다
source: "/english\\(default\\)/:slug",
destination: "/en-us/:slug",
},
];
},
};
헤더, 쿠키, 쿼리 매칭
헤더, 쿠키 또는 쿼리 값이 has
필드와 일치하거나 missing
필드와 일치하지 않을 때만 리라이트를 매칭하려면 has
필드를 사용할 수 있습니다. 리라이트가 적용되려면 source
와 모든 has
항목이 일치해야 하고 모든 missing
항목이 일치하지 않아야 합니다.
has
와 missing
항목은 다음 필드를 가질 수 있습니다:
type
:String
.header
,cookie
,host
, 또는query
중 하나여야 합니다.key
:String
. 선택한 타입에서 매칭할 키입니다.value
:String
또는undefined
. 확인할 값입니다. undefined인 경우 모든 값이 일치합니다. 정규식 같은 문자열을 사용하여 값의 특정 부분을 캡처할 수 있습니다. 예를 들어first-(?<paramName>.*)
라는 값이first-second
에 사용되면second
는 엔드포인트에서:paramName
으로 사용할 수 있습니다.
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
경로를 외부 사이트로 리다이렉트하는 리라이트 예시입니다.
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
파라미터에도 포함되어야 합니다.
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로 마이그레이션할 때 리라이트 구성을 변경할 필요가 없습니다.
module.exports = {
async rewrites() {
return {
fallback: [
{
source: "/:path*",
destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`,
},
],
};
},
};
basePath 지원이 있는 리라이트
리라이트와 함께 basePath
지원을 활용할 때, basePath: false
를 리라이트에 추가하지 않는 한 각 source
와 destination
은 자동으로 basePath
로 접두사가 붙습니다:
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
를 리라이트에 추가하지 않는 한 각 source
와 destination
은 구성된 locales
를 처리하기 위해 자동으로 접두사가 붙습니다. locale: false
가 사용되면 올바르게 매칭되도록 source
와 destination
에 로케일을 접두사로 붙여야 합니다.
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.0 | missing 추가됨. |
v10.2.0 | has 추가됨. |
v9.5.0 | 헤더 추가됨. |