챕터 20: 동적 라우팅 마스터하기
서론 정적 라우팅으로 기본적인 페이지들을 만들 수 있지만, 실제 웹 애플리케이션에서는 동적인 콘텐츠를 다뤄야 하는 경우가 많습니다. 수백 개의 상품을 판매하는 쇼핑몰에서 각 상품마다 별도의 페이지 파일을 만든다면 어떻게 될까요? 관리가 불가능할 것입니다. 이럴 때 필요한 것이 바로 동적 라우팅입니다.
동적 라우팅을 사용하면 하나의 템플릿으로 무수히 많은 페이지를 생성할 수 있습니다. URL의 일부를 변수처럼 사용하여, 그 값에 따라 다른 콘텐츠를 보여주는 것입니다. 이번 챕터에서는 Next.js의 강력한 동적 라우팅 기능을 완벽하게 마스터해보겠습니다.
본론 동적 라우트의 기본 개념 동적 라우트는 대괄호([])를 사용하여 만듭니다. 대괄호 안의 이름이 매개변수가 됩니다:
jsx // app/products/[id]/page.js export default function ProductPage({ params }) { return (
상품 상세 페이지
상품 ID: {params.id}
{/*
/products/1 → params.id = "1"
/products/laptop → params.id = "laptop"
/products/abc123 → params.id = "abc123"
*/}
</div>
</div>
) } 실제 데이터와 연동하기 동적 라우트를 실제 데이터와 연결해봅시다:
jsx // app/products/[id]/page.js const products = [ { id: '1', name: '노트북', price: 1500000, description: '고성능 노트북' }, { id: '2', name: '마우스', price: 30000, description: '무선 마우스' }, { id: '3', name: '키보드', price: 100000, description: '기계식 키보드' } ]
export default function ProductDetailPage({ params }) { const product = products.find(p => p.id === params.id)
if (!product) { return (
상품을 찾을 수 없습니다
상품 목록으로 돌아가기return (
{product.name}
{product.description}
jsx // app/shop/[category]/[productId]/page.js export default function CategoryProductPage({ params }) { // params = { category: 'electronics', productId: 'laptop-123' }
return (
<h1 className="text-3xl font-bold mb-4">
카테고리: {params.category}
</h1>
<p className="text-xl text-gray-600">
상품 ID: {params.productId}
</p>
</div>
</div>
) } Catch-all 라우트 [...slug] 형식으로 모든 경로를 잡아낼 수 있습니다:
jsx // app/docs/[...slug]/page.js export default function DocsPage({ params }) { // /docs/getting-started → params.slug = ['getting-started'] // /docs/guides/routing → params.slug = ['guides', 'routing'] // /docs/guides/routing/dynamic → params.slug = ['guides', 'routing', 'dynamic']
const path = params.slug ? params.slug.join(' > ') : 'Home'
return (
{/* 메인 콘텐츠 */}
<main className="flex-1 p-8">
<div className="bg-white rounded-lg shadow-md p-8">
<h1 className="text-3xl font-bold mb-4">문서</h1>
<p className="text-gray-600 mb-6">현재 경로: {path}</p>
<div className="prose max-w-none">
<h2 className="text-xl font-semibold mb-3">콘텐츠</h2>
<p className="text-gray-700">
이곳에 {params.slug ? params.slug[params.slug.length - 1] : 'home'} 문서의
실제 내용이 표시됩니다.
</p>
</div>
</div>
</main>
</div>
</div>
) } Optional Catch-all 라우트 [[...slug]] 형식으로 선택적 catch-all 라우트를 만들 수 있습니다:
jsx // app/blog/[[...slug]]/page.js export default function BlogPage({ params }) { // /blog → params.slug = undefined // /blog/2024 → params.slug = ['2024'] // /blog/2024/01 → params.slug = ['2024', '01'] // /blog/2024/01/my-post → params.slug = ['2024', '01', 'my-post']
if (!params.slug) { // 블로그 홈페이지 return (
블로그
블로그 포스트 {i}
포스트 요약 내용...
<a href={/blog/2024/01/post-${i}} className="text-blue-600 hover:underline">
읽기 →
// 특정 포스트 페이지 return (
{params.slug[params.slug.length - 1]}
경로: {params.slug.join(' / ')}
여기에 블로그 포스트 내용이 들어갑니다...
jsx // app/users/[username]/page.js const users = [ { username: 'john_doe', name: '존 도우', bio: '프론트엔드 개발자', posts: 42, followers: 1234, following: 567 }, { username: 'jane_smith', name: '제인 스미스', bio: 'UI/UX 디자이너', posts: 38, followers: 892, following: 234 } ]
export default function UserProfilePage({ params }) { const user = users.find(u => u.username === params.username)
if (!user) { return (
return (
{/* 프로필 정보 */}
<div className="relative px-8 pb-8">
<div className="absolute -top-16 left-8">
<div className="w-32 h-32 bg-white rounded-full border-4 border-white flex items-center justify-center text-4xl">
👤
</div>
</div>
<div className="pt-20">
<h1 className="text-2xl font-bold">{user.name}</h1>
<p className="text-gray-600">@{user.username}</p>
<p className="mt-2">{user.bio}</p>
<div className="flex gap-6 mt-6">
<div className="text-center">
<div className="text-2xl font-bold">{user.posts}</div>
<div className="text-gray-600">포스트</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold">{user.followers.toLocaleString()}</div>
<div className="text-gray-600">팔로워</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold">{user.following}</div>
<div className="text-gray-600">팔로잉</div>
</div>
</div>
<button className="mt-6 bg-blue-600 text-white px-8 py-2 rounded-lg hover:bg-blue-700 transition">
팔로우
</button>
</div>
</div>
</div>
</div>
</div>
) } 결론 동적 라우팅은 Next.js의 가장 강력한 기능 중 하나입니다. 이번 챕터에서 우리는 기본적인 동적 라우트부터 여러 세그먼트, catch-all 라우트, 그리고 optional catch-all 라우트까지 다양한 패턴을 배웠습니다.
동적 라우팅을 사용하면 하나의 템플릿으로 무한한 페이지를 생성할 수 있습니다. 상품 상세 페이지, 사용자 프로필, 블로그 포스트 등 대부분의 동적 콘텐츠는 이 방식으로 구현됩니다. 특히 데이터베이스와 연동하면 진정한 동적 웹 애플리케이션을 만들 수 있습니다.
다음 챕터에서는 페이지 간 이동을 더욱 효율적으로 만들어주는 Link 컴포넌트에 대해 자세히 알아보겠습니다. Link 컴포넌트를 사용하면 페이지 전체를 새로고침하지 않고도 빠르게 이동할 수 있습니다!