본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 3장: 동적 라우팅과 병렬 라우트
2026년 1월 24일·웹 개발·

3장: 동적 라우팅과 병렬 라우트

Next.js의 동적 세그먼트, catch-all 라우트, 병렬 라우트(@slot)의 동작 원리와 대시보드, 조건부 렌더링 등 실전 패턴을 다룹니다.

10분515자4개 섹션
nextjsreacttypescriptfrontend
공유
nextjs-app-router3 / 13
12345678910111213
이전2장: App Router 아키텍처 - 레이아웃, 템플릿, 라우트 그룹다음4장: 인터셉팅 라우트와 모달 패턴

2장에서 App Router의 기본 아키텍처를 다루었습니다. 이번 장에서는 동적 라우팅의 다양한 패턴과, App Router의 고급 기능인 병렬 라우트(Parallel Routes)를 심층적으로 살펴봅니다.

동적 라우팅

동적 세그먼트

대괄호로 감싼 폴더명은 동적 세그먼트가 됩니다.

app/
  blog/
    [slug]/
      page.tsx       → /blog/hello-world, /blog/nextjs-guide
  users/
    [id]/
      page.tsx       → /users/1, /users/abc
      posts/
        [postId]/
          page.tsx   → /users/1/posts/42
app/blog/[slug]/page.tsx
typescript
interface PageProps {
  params: Promise<{ slug: string }>;
}
 
export default async function BlogPost({ params }: PageProps) {
  const { slug } = await params;
  const post = await getPost(slug);
 
  if (!post) notFound();
 
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

정적 파라미터 생성

generateStaticParams를 사용하면 빌드 타임에 동적 라우트를 미리 렌더링합니다.

정적 생성
typescript
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(post => ({
    slug: post.slug,
  }));
}

Catch-all 세그먼트

[...slug]는 해당 위치 이후의 모든 경로 세그먼트를 배열로 캡처합니다.

app/
  docs/
    [...slug]/
      page.tsx       → /docs/a, /docs/a/b, /docs/a/b/c
app/docs/[...slug]/page.tsx
typescript
interface PageProps {
  params: Promise<{ slug: string[] }>;
}
 
export default async function DocsPage({ params }: PageProps) {
  const { slug } = await params;
  // /docs/react/hooks/useState → slug = ['react', 'hooks', 'useState']
 
  const doc = await getDoc(slug.join('/'));
  return <div>{doc.content}</div>;
}

Optional Catch-all 세그먼트

[[...slug]]는 catch-all과 동일하지만 파라미터가 없는 경로도 매칭합니다.

app/
  docs/
    [[...slug]]/
      page.tsx       → /docs, /docs/a, /docs/a/b

/docs에서 slug는 undefined가 됩니다.

병렬 라우트 (Parallel Routes)

병렬 라우트는 같은 레이아웃 안에서 여러 페이지를 동시에 렌더링하는 기능입니다.

기본 문법

@ 접두사가 붙은 폴더가 "슬롯(slot)"이 되며, 부모 레이아웃의 props로 전달됩니다.

app/
  layout.tsx
  page.tsx
  @analytics/
    page.tsx
  @team/
    page.tsx
app/layout.tsx
typescript
export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <div>
      {children}
      <div className="grid grid-cols-2 gap-4">
        {analytics}
        {team}
      </div>
    </div>
  );
}

children은 암묵적 슬롯으로, @children/page.tsx와 동일합니다. 즉, page.tsx는 @children/page.tsx의 축약형입니다.

독립적 로딩과 에러 처리

각 슬롯이 독립적으로 스트리밍되고, 독립적인 로딩/에러 상태를 가질 수 있습니다.

app/
  @analytics/
    loading.tsx      ← 분석 데이터 로딩 중 표시
    error.tsx        ← 분석 에러 시 표시
    page.tsx
  @team/
    loading.tsx      ← 팀 데이터 로딩 중 표시
    error.tsx        ← 팀 에러 시 표시
    page.tsx

@analytics의 데이터를 가져오는 동안 @team은 이미 표시될 수 있고, @analytics에서 에러가 발생해도 @team은 정상적으로 동작합니다.

default.tsx의 역할

병렬 라우트에서 default.tsx는 현재 URL에 해당하는 슬롯의 콘텐츠가 없을 때 렌더링되는 fallback입니다.

app/
  @modal/
    photo/
      [id]/
        page.tsx     → /photo/1 일 때 렌더링
    default.tsx      → / 일 때 렌더링 (모달 없음)
  page.tsx           → / 일 때 children
@modal/default.tsx
typescript
// 모달이 없을 때는 아무것도 렌더링하지 않음
export default function Default() {
  return null;
}
Warning

Next.js 16에서는 모든 병렬 라우트 슬롯에 명시적인 default.tsx 파일이 필수입니다. 없으면 빌드 에러가 발생합니다.

실전: 대시보드 패턴

app/
  dashboard/
    layout.tsx
    page.tsx
    @stats/
      page.tsx
      loading.tsx
      default.tsx
    @activity/
      page.tsx
      loading.tsx
      default.tsx
    @notifications/
      page.tsx
      loading.tsx
      default.tsx
app/dashboard/layout.tsx
typescript
export default function DashboardLayout({
  children,
  stats,
  activity,
  notifications,
}: {
  children: React.ReactNode;
  stats: React.ReactNode;
  activity: React.ReactNode;
  notifications: React.ReactNode;
}) {
  return (
    <div>
      <h1 className="mb-6 text-2xl font-bold">대시보드</h1>
 
      {/* 통계 카드 행 */}
      <div className="mb-6">{stats}</div>
 
      {/* 본문 영역 */}
      <div className="grid grid-cols-3 gap-6">
        <div className="col-span-2">{children}</div>
        <div className="space-y-6">
          {activity}
          {notifications}
        </div>
      </div>
    </div>
  );
}
app/dashboard/@stats/page.tsx
typescript
async function StatsSlot() {
  const stats = await getStats(); // 독립적으로 데이터 페칭
 
  return (
    <div className="grid grid-cols-4 gap-4">
      <StatCard title="사용자" value={stats.users} />
      <StatCard title="매출" value={stats.revenue} />
      <StatCard title="주문" value={stats.orders} />
      <StatCard title="전환율" value={stats.conversion} />
    </div>
  );
}
 
export default StatsSlot;

이 구조에서 통계, 활동 피드, 알림이 병렬로 데이터를 가져오고 독립적으로 스트리밍됩니다. 통계 API가 느려도 알림은 먼저 표시됩니다.

조건부 렌더링 패턴

병렬 라우트를 사용하면 조건에 따라 다른 콘텐츠를 표시할 수 있습니다.

역할 기반 조건부 렌더링
typescript
import { auth } from '@/lib/auth';
 
export default async function Layout({
  admin,
  user,
}: {
  admin: React.ReactNode;
  user: React.ReactNode;
}) {
  const session = await auth();
  const isAdmin = session?.user?.role === 'admin';
 
  return isAdmin ? admin : user;
}
app/
  dashboard/
    layout.tsx
    @admin/
      page.tsx        ← 관리자에게 표시
      default.tsx
    @user/
      page.tsx        ← 일반 사용자에게 표시
      default.tsx

하위 라우트에서의 병렬 라우트

슬롯은 하위 라우트도 가질 수 있습니다.

app/
  dashboard/
    layout.tsx
    page.tsx
    @analytics/
      page.tsx           → /dashboard
      detailed/
        page.tsx         → /dashboard/detailed
      default.tsx

/dashboard에서 @analytics/page.tsx가 렌더링되고, /dashboard/detailed에서 @analytics/detailed/page.tsx가 렌더링됩니다.

소프트 탐색과 하드 탐색

병렬 라우트의 동작은 탐색 유형에 따라 달라집니다.

소프트 탐색 (Client-side Navigation)

<Link>나 router.push()를 통한 탐색에서는 Next.js가 부분 렌더링(Partial Rendering)을 수행합니다. 변경된 슬롯만 업데이트하고, 나머지 슬롯은 현재 상태를 유지합니다.

하드 탐색 (Full Page Load)

브라우저 새로고침이나 직접 URL 입력 시, Next.js는 현재 URL에 매칭되지 않는 슬롯에 대해 default.tsx를 렌더링합니다. default.tsx가 없으면 404가 발생합니다.

소프트 vs 하드 탐색
typescript
// /dashboard 에서 /dashboard/settings로 소프트 탐색 시:
// - @stats: 이전 상태 유지 (/dashboard의 @stats/page.tsx)
// - children: settings/page.tsx로 교체
 
// /dashboard/settings 에서 브라우저 새로고침 시:
// - @stats: @stats/default.tsx 렌더링 (하드 탐색이므로)
// - children: settings/page.tsx 렌더링

이것이 default.tsx가 필수인 이유입니다. 하드 탐색 시 fallback이 없으면 슬롯을 렌더링할 수 없기 때문입니다.

핵심 요약

  • 동적 세그먼트([slug]), catch-all([...slug]), optional catch-all([[...slug]])로 유연한 URL 매칭이 가능합니다.
  • 병렬 라우트(@slot)는 같은 레이아웃에서 여러 페이지를 동시에 렌더링하며, 각 슬롯은 독립적인 로딩/에러 상태를 가집니다.
  • default.tsx는 하드 탐색 시 URL에 매칭되지 않는 슬롯의 fallback이며, Next.js 16에서는 필수입니다.
  • 대시보드, 조건부 렌더링, 역할 기반 UI 등의 복잡한 레이아웃을 병렬 라우트로 우아하게 구현할 수 있습니다.
  • 소프트 탐색은 변경된 슬롯만 업데이트하고, 하드 탐색은 모든 슬롯에 대해 매칭 또는 default.tsx를 렌더링합니다.

다음 장에서는 인터셉팅 라우트와 모달 패턴을 다룹니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#nextjs#react#typescript#frontend

관련 글

웹 개발

4장: 인터셉팅 라우트와 모달 패턴

Next.js의 인터셉팅 라우트로 모달, 사진 갤러리, 미리보기 패턴을 구현합니다. 병렬 라우트와의 조합, 딥 링킹, 공유 가능한 URL을 다룹니다.

2026년 1월 26일·12분
웹 개발

2장: App Router 아키텍처 - 레이아웃, 템플릿, 라우트 그룹

Next.js App Router의 파일 기반 라우팅, 레이아웃 계층, 템플릿, 라우트 그룹, 에러/로딩 경계의 동작 원리와 실전 패턴을 다룹니다.

2026년 1월 22일·15분
웹 개발

5장: 데이터 페칭과 캐싱 전략의 대전환

Next.js 14에서 16까지 캐싱 전략이 어떻게 변화했는지 살펴봅니다. 네 가지 캐싱 레이어, fetch() 기본값 변경, 재검증 전략을 다룹니다.

2026년 1월 28일·20분
이전 글2장: App Router 아키텍처 - 레이아웃, 템플릿, 라우트 그룹
다음 글4장: 인터셉팅 라우트와 모달 패턴

댓글

목차

약 10분 남음
  • 동적 라우팅
    • 동적 세그먼트
    • 정적 파라미터 생성
    • Catch-all 세그먼트
    • Optional Catch-all 세그먼트
  • 병렬 라우트 (Parallel Routes)
    • 기본 문법
    • 독립적 로딩과 에러 처리
    • default.tsx의 역할
    • 실전: 대시보드 패턴
    • 조건부 렌더링 패턴
    • 하위 라우트에서의 병렬 라우트
  • 소프트 탐색과 하드 탐색
    • 소프트 탐색 (Client-side Navigation)
    • 하드 탐색 (Full Page Load)
  • 핵심 요약