동적 라우트 생성
대괄호 폴더로 단일·다중 동적 세그먼트를 만들고 params와 generateStaticParams로 상세 페이지를 구성합니다.
이전 장에서는 정적인 URL 경로를 가진 페이지를 만들고 연결하는 방법을 배웠습니다.
하지만 실제 서비스에서는 URL 일부가 데이터에 따라 동적으로 바뀌는 경우가 훨씬 많습니다.
예를 들어 블로그 상세(/blog/nextjs-basics),
상품 상세(/products/luxury-watch-xyz),
사용자 프로필(/users/alice) 같은 경로가 대표적입니다.
Next.js App Router는 이러한 요구사항을 충족시키기 위해 동적 라우트(Dynamic Routes) 기능을 제공합니다.
이 절에서는 동적 라우트를 생성하고, URL에서 동적인 값을 추출하여 페이지 컴포넌트에서 사용하는 방법에 대해 자세히 알아보겠습니다.
이 장은 Next.js 16 App Router의 기본 설정을 기준으로 합니다. cacheComponents를 별도로 켠 경우의 추가 Suspense·캐시 요건은 여기서 다루지 않습니다.
동적 라우트의 개념과 필요성
정적 라우트가 /about이나 /dashboard/settings처럼 고정된 URL을 가진다면, 동적 라우트는 URL의 특정 부분이 변수처럼 작동합니다.
블로그 게시물:/blog/my-first-post,/blog/nextjs-deep-dive상품 상세:/products/12345,/products/67890사용자 프로필:/users/john-doe,/users/jane-smith
이러한 URL들은 /blog/, /products/, /users/는 고정되어 있지만, 그 뒤의 my-first-post, 12345, john-doe와 같은 값은 각 항목에 따라 달라집니다.
Next.js는 이 동적인 부분을 감지하고, 해당 값을 페이지 컴포넌트로 전달하여 우리가 각기 다른 콘텐츠를 렌더링할 수 있도록 돕습니다.
동적 라우트 생성하기: 대괄호 [] 사용
Next.js App Router에서 동적 라우트 세그먼트를 정의하려면, 폴더 이름을 대괄호([])로 감쌉니다.
이 대괄호 안의 이름이 파라미터(Parameter)의 키(key)가 됩니다.
예를 들어, 블로그 게시물 상세 페이지를 만든다고 가정해 봅시다.
src/app/blog 폴더 생성:
먼저 src/app 안에 blog 폴더를 생성합니다. (만약 없다면)
[slug] 폴더 생성:
src/app/blog 안에 [slug]라는 이름의 폴더를 생성합니다.
여기서 slug는 동적인 값이 들어갈 파라미터의 이름이 됩니다.
page.tsx 파일 생성:
src/app/blog/[slug] 폴더 안에 page.tsx 파일을 생성합니다.
src/app/blog/[slug]/page.tsx 파일 내용 작성:
이 page.tsx 컴포넌트는 params라는 prop을 통해 URL에서 추출된 동적인 값을 전달받습니다.
// src/app/blog/[slug]/page.tsx
interface BlogDetailPageProps {
params: Promise<{
slug: string; // [slug] 폴더 이름과 일치해야 합니다.
}>;
}
// 서버 컴포넌트로 작동하며, params를 prop으로 받습니다.
export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
const { slug } = await params; // URL에서 slug 값을 추출
// 실제 애플리케이션에서는 이 slug 값을 사용하여
// 데이터베이스나 API에서 해당 게시물의 내용을 불러올 것입니다.
// 예: const post = await getPostBySlug(slug);
return (
<div>
<h1>블로그 게시물: {slug}</h1>
<p>
현재 보고 계신 게시물의 식별자(slug)는 <strong>{slug}</strong> 입니다.
</p>
<p>여기에 실제 게시물 내용이 표시됩니다.</p>
</div>
);
}실습: 개발 서버가 실행 중인 상태에서 브라우저를 열고 다음 URL로 접속해 보세요.
http://localhost:3000/blog/my-first-posthttp://localhost:3000/blog/nextjs-deep-divehttp://localhost:3000/blog/welcome-to-my-blog
각 URL에 따라 페이지 제목과 내용에 slug 값이 다르게 표시되는 것을 확인할 수 있습니다.
다중 동적 라우트 세그먼트
하나의 라우트에 여러 개의 동적 세그먼트를 포함할 수도 있습니다.
예를 들어, 특정 카테고리 내의 블로그 게시물을 나타내는 /blog/[category]/[slug]와 같은 경로를 만들 수 있습니다.
다음은 앞의 한 세그먼트 예제와 별개의 설계입니다. 같은 blog 바로 아래에 [slug]와 [category]를 함께 두면 동적 키 이름이 충돌하므로 두 구조를 동시에 추가하지 마세요.
동적 폴더 이름과 params 키의 대응
src/app/blog/[category]/[slug]/page.tsx 구조 만들기src/app/blog/[category]/[slug]/page.tsx 파일 내용 작성interface CategoryBlogDetailPageProps {
params: Promise<{
category: string; // [category] 폴더 이름과 일치
slug: string; // [slug] 폴더 이름과 일치
}>;
}
export default async function CategoryBlogDetailPage({ params }: CategoryBlogDetailPageProps) {
const { category, slug } = await params;
return (
<div>
<h1>카테고리: {category}</h1>
<h2>게시물: {slug}</h2>
<p>
이 게시물은 <strong>{category}</strong> 카테고리에 속하며, 식별자는 <strong>{slug}</strong> 입니다.
</p>
</div>
);
}실습: 이제 다음 URL로 접속해 보세요.
http://localhost:3000/blog/web-dev/understanding-reacthttp://localhost:3000/blog/frontend/nextjs-features
URL의 category와 slug 부분이 정확히 추출되어 페이지에 표시되는 것을 볼 수 있습니다.
generateStaticParams 함수
동적 라우트를 가진 페이지를 정적으로 생성(Static Site Generation, SSG)해야 할 때, Next.js는 빌드 시점에 어떤 params 값을 가지고 페이지를 생성할지 알아야 합니다.
이를 위해 generateStaticParams 함수를 사용합니다.
이 함수는 라우트의 page.tsx나 layout.tsx에서 내보낼 수 있습니다. 다음 코드는 처음의 blog/[slug]/page.tsx 예제에 추가합니다. 바로 앞의 다중 세그먼트 구조를 함께 추가하는 코드는 아닙니다.
// ... (BlogDetailPage 컴포넌트 코드) ...
// SSG를 위해 빌드 시 어떤 slug 값을 생성할지 명시
export async function generateStaticParams() {
const posts = [
{ slug: 'my-first-post' },
{ slug: 'nextjs-deep-dive' },
{ slug: 'understanding-server-components' },
];
// 반환 값은 { slug: '...' } 형태의 객체 배열이어야 합니다.
return posts;
}이 예제는 빌드 시 알려진 세 slug를 후보로 제공해 해당 경로를 사전 렌더합니다. 데이터나 런타임 API 사용에 따라 렌더링 방식은 달라질 수 있으므로, 함수가 있다는 사실만으로 모든 경로가 완전한 정적 HTML이 되는 것은 아닙니다.
목록에 없는 slug의 허용 여부는 dynamicParams로 정합니다. 기본값 true는 요청 시 처리를 허용하고, false는 미등록 경로를 허용하지 않습니다. 경로가 매칭되어도 실제 게시물이 없으면 데이터 조회 뒤 notFound() 같은 분기가 필요합니다.
next build는 관련 페이지·레이아웃을 생성하기 전에 이 함수를 호출합니다. ISR 재검증 중에는 generateStaticParams를 다시 실행하지 않습니다. 이 절에서는 API 규약과 코드 구성을 설명했으며 특정 로딩 시간은 측정하지 않았습니다.