Catch-all 세그먼트 사용
이전 절에서 동적 라우트 [slug]가
단일 파라미터 처리에 유용하다는 점을 확인했습니다.
하지만 실무에서는 URL 길이가 가변적이거나
중첩된 여러 경로를 한 페이지에서 처리해야 하는 경우가 자주 생깁니다.
예를 들어 문서 사이트의 /docs/getting-started,
/docs/api/v1/auth, /docs/features/dynamic-routes처럼
경로 깊이가 달라지는 시나리오가 대표적입니다.
이러한 시나리오를 위해 Next.js App Router는 Catch-all 세그먼트라는 강력한 기능을 제공합니다. Catch-all 세그먼트는 URL의 나머지 모든 부분을 단일 파라미터로 모두 잡아채는(Catch-all) 역할을 합니다.
Catch-all 세그먼트란?
Catch-all 세그먼트는 폴더 이름을 세 개의 점(...)과 파라미터 이름을 함께 대괄호([])로 감싸서 정의합니다.
-
[...slug](필수 Catch-all 세그먼트): 이 형태는 해당 세그먼트가 최소한 하나 이상의 값을 포함해야 함을 의미합니다. 즉,/docs/[...slug]라면/docs/a는 매칭되지만/docs는 매칭되지 않습니다. URL에서 추출된 값은 문자열 배열로params객체에 전달됩니다. -
[[...slug]](선택적 Catch-all 세그먼트): 이 형태는 해당 세그먼트가 값이 없어도 매칭됨을 의미합니다. 즉,/docs/[[...slug]]라면/docs도 매칭되고/docs/a/b도 매칭됩니다. URL에서 추출된 값이 없을 경우params객체에서 해당 키는undefined가 되며, 값이 있을 경우[...slug]와 동일하게 문자열 배열로 전달됩니다.
이 두 가지 형태 모두 URL의 가변적인 부분을 유연하게 처리할 수 있게 해줍니다.
필수 Catch-all 세그먼트 구현하기
필수 Catch-all 세그먼트 [...slug]는 블로그에서 카테고리와 게시물 제목 외에, 임의의 깊이를 가진 서브 카테고리를 처리하고 싶을 때 유용합니다. 예를 들어 /articles/programming/javascript/nextjs-deep-dive와 같은 경로를 처리할 수 있습니다.
우리는 /docs/path/to/document와 같은 경로를 처리하는 문서를 만들 것입니다.
src/app/docs 폴더 생성:
먼저 src/app 안에 docs 폴더를 생성합니다.
[...slug] 폴더 생성:
src/app/docs 안에 [...slug]라는 이름의 폴더를 생성합니다.
page.tsx 파일 생성:
src/app/docs/[...slug] 폴더 안에 page.tsx 파일을 생성합니다.
src/app/docs/[...slug]/page.tsx 파일 내용 작성:
interface DocDetailPageProps {
params: {
slug: string[]; // Catch-all 세그먼트는 문자열 배열로 전달됩니다.
};
}
// 이 컴포넌트는 서버 컴포넌트로 동작합니다.
export default function DocDetailPage({ params }: DocDetailPageProps) {
const { slug } = params; // URL에서 slug 값을 배열로 추출
// slug 배열을 사용하여 실제 문서 데이터를 불러오는 로직을 여기에 작성합니다.
// 예: const docContent = await fetchDocContent(slug.join('/'));
const docPath = slug.join(' / '); // 경로를 가독성 좋게 표시하기 위함
return (
<div>
<h1>문서 상세 페이지</h1>
<p>
현재 문서의 경로는 <strong>/docs/{slug.join('/')}</strong> 입니다.
</p>
<p>
추출된 파라미터 (`params.slug`): <code>{JSON.stringify(slug)}</code>
</p>
<p>여기에 실제 문서 내용이 표시됩니다.</p>
</div>
);
}
// SSG를 위해 미리 생성할 경로들을 정의합니다. (선택 사항)
export async function generateStaticParams() {
const paths = [
{ slug: ['getting-started'] },
{ slug: ['api', 'v1', 'auth'] },
{ slug: ['features', 'dynamic-routes'] },
];
return paths;
}실습:
개발 서버(npm run dev)가 실행 중인 상태에서 브라우저를 열고 다음 URL로 접속해 보세요.
http://localhost:3000/docs/first-documenthttp://localhost:3000/docs/category/sub-category/article-titlehttp://localhost:3000/docs/a/b/c/d
각 URL에 따라 params.slug가 배열 형태로 추출되고 페이지에 표시되는 것을 확인할 수 있습니다. /docs로만 접속하면 페이지를 찾을 수 없다는 404 에러가 발생할 것입니다. 이는 [...slug]가 최소한 하나의 세그먼트를 요구하기 때문입니다.
선택적 Catch-all 세그먼트 구현하기
이제 /docs 경로 자체도 매칭시키고 싶을 때 사용하는 [[...slug]]를 구현해 보겠습니다.
기존 [...slug] 폴더를 [[...slug]]로 이름 변경:
src/app/docs/[...slug] 폴더의 이름을 src/app/docs/[[...slug]]로 변경합니다. (또는 새롭게 생성합니다.)
src/app/docs/[[...slug]]/page.tsx 파일 내용 수정:
page.tsx 파일 내용은 거의 동일하지만, params.slug가 undefined일 수 있다는 점을 고려하여 코드를 수정합니다.
interface DocsPageProps {
params: {
slug?: string[]; // slug가 선택적(optional)이므로 ? 추가
};
}
export default function DocsPage({ params }: DocsPageProps) {
const { slug } = params; // slug가 없을 경우 undefined
// slug 배열이 존재하면 경로를 합치고, 없으면 "홈"으로 표시
const docPath = slug ? slug.join(' / ') : '홈';
return (
<div>
<h1>문서 페이지 ({docPath})</h1>
{slug ? (
<p>
현재 문서의 경로는 <strong>/docs/{slug.join('/')}</strong> 입니다.
</p>
) : (
<p>문서 홈 페이지입니다. 시작하려면 왼쪽 메뉴를 선택하세요.</p>
)}
<p>추출된 파라미터 (`params.slug`): <code>{JSON.stringify(slug)}</code></p>
<p>여기에 실제 문서 내용이 표시됩니다.</p>
</div>
);
}
// generateStaticParams도 마찬가지로 빈 배열을 포함할 수 있습니다.
export async function generateStaticParams() {
const paths = [
{ slug: ['getting-started'] },
{ slug: ['api', 'v1', 'auth'] },
// 루트 문서 페이지도 미리 생성하려면 빈 배열을 추가합니다.
{ slug: [] },
];
return paths;
}실습: 이제 다음 URL로 접속해 보세요.
http://localhost:3000/docs(매칭 성공!)http://localhost:3000/docs/first-documenthttp://localhost:3000/docs/category/sub-category/article-title
/docs 경로가 매칭되어 문서 홈 페이지가 표시되고, 다른 경로들도 이전처럼 잘 동작하는 것을 확인할 수 있습니다.
Catch-all 세그먼트의 활용 시나리오
- 동적인 URL 구조 처리: 문서, 블로그, 위키 등 계층적이고 유연한 URL 구조가 필요한 경우에 유용합니다.
- 파일 시스템 기반 CMS: 파일 경로 자체가 콘텐츠의 위치를 나타내는 시스템을 구축할 때 활용될 수 있습니다.
- 폴백(Fallback) 라우트: 특정 경로가 매칭되지 않을 경우, Catch-all 라우트가 해당 요청을 처리하도록 하여 404 에러를 방지하거나, 커스텀 에러 페이지를 보여줄 수 있습니다.
Catch-all 세그먼트는 Next.js App Router의 유연성을 극대화하는 강력한 도구입니다. 이를 통해 어떤 URL 패턴도 효율적으로 처리하고, 더욱 견고하고 사용자 친화적인 애플리케이션을 구축할 수 있습니다.
이것으로 동적 라우트와 Catch-all 세그먼트에 대한 설명을 마칩니다. 다음 절에서는 라우트 그룹을 사용하여 라우트 구조를 논리적으로 조직하는 방법에 대해 알아보겠습니다.