Next.js 교재 · 5편 · 동적 라우팅

동적·중첩 라우팅 — [slug]·(group)·[...all] 정리

폴더 이름의 대괄호·소괄호가 URL 을 결정한다. 4가지 패턴이면 끝.

URL 이 동적 세그먼트로 분기하는 라우트 트리 컨셉 아이소메트릭 일러스트

3·4편에서 정적 경로(/about·/blog) 를 만드는 법은 익혔다. 그런데 실전 사이트는 정적만으론 부족하다. 블로그 글마다 다른 URL(/blog/hello-world·/blog/next-tips), 사용자 프로필(/users/123), 문서 사이트의 무한 중첩(/docs/a/b/c) — 다 동적이다.

Next.js 는 이걸 풀기 위해 폴더 이름의 트릭 4가지를 제공한다. [slug]·(group)·[...all]·[[slug]]. 이번 편에서 하나씩 손에 익힌다.

1. [slug] — 동적 세그먼트 한 단어

가장 자주 쓰는 패턴. 폴더 이름을 대괄호로 감싸면 그 자리에 어떤 값이 와도 매칭된다.

app/blog/[slug]/page.tsx

이 한 파일로 /blog/hello·/blog/anything·/blog/2026-04-22 가 모두 같은 페이지를 그린다. 페이지 안에선 params 로 그 값을 받는다.

// app/blog/[slug]/page.tsx export default async function BlogPostPage({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; return ( <article> <h1>블로그 글: {slug}</h1> <p>여기에 {slug} 글 본문을 데이터에서 불러옵니다.</p> </article> ); }

Next 15 부터 paramsPromise 라는 게 큰 변화. 14 까지는 동기였는데 15 부터 비동기로 바뀌어 await 가 필요하다. 마이그레이션 가이드 (23편)에서 자세히.

여러 동적 세그먼트app/users/[userId]/posts/[postId]/page.tsx 처럼 중첩 가능. params 객체에 둘 다 담겨 온다: { userId, postId }.

2. (group) — URL 에 영향 X, 폴더만 묶음

괄호 폴더는 URL 에 안 나타난다. 코드 구조만 정리하는 용도.

app/ ├── (marketing)/ │ ├── layout.tsx ← 마케팅 페이지용 공통 layout │ ├── about/page.tsx → /about │ └── pricing/page.tsx → /pricing └── (app)/ ├── layout.tsx ← 앱 페이지용 다른 layout ├── dashboard/page.tsx → /dashboard └── settings/page.tsx → /settings

URL 만 보면 마케팅 페이지와 앱 페이지가 같은 레벨인데, 폴더 분리로 서로 다른 layout 을 줄 수 있다. 마케팅은 큰 헤더·풋터, 앱은 사이드바·작은 헤더 — 같은 사이트에 두 가지 UI 가 공존할 때 필수 패턴.

주의 — 같은 URL 을 다른 (group) 두 곳에서 정의하면 에러. (marketing)/dashboard/page.tsx(app)/dashboard/page.tsx 가 동시에 있으면 충돌. 각 URL 은 정확히 한 곳에서만 만들어야.

3. [...all] — Catch-all 세그먼트

대괄호 안에 점 3개. "나머지 경로 통째로 받아라" 라는 뜻.

app/docs/[...slug]/page.tsx

이 하나로 다음이 모두 매칭된다:

  • /docs/introslug = ['intro']
  • /docs/guide/installationslug = ['guide', 'installation']
  • /docs/api/v2/usersslug = ['api', 'v2', 'users']

문서 사이트·위키처럼 깊이를 미리 알 수 없는 경로에 쓴다. 페이지 안에선 slug 가 배열로 들어온다.

// app/docs/[...slug]/page.tsx export default async function DocPage({ params, }: { params: Promise<{ slug: string[] }>; }) { const { slug } = await params; const path = slug.join('/'); return <article><h1>Docs: {path}</h1></article>; }

단, /docs (slug 가 없는 경우) 는 이걸로 안 잡힌다. 그 경우엔 옵셔널 catch-all 이 필요.

4. [[...all]] — Optional Catch-all

대괄호를 한 번 더 감싸면 — 없어도 매칭됨.

app/docs/[[...slug]]/page.tsx

이건 /docs · /docs/intro · /docs/guide/installation 모두 같은 페이지로 잡는다. /docs 일 땐 slugundefined, 깊은 경로면 배열.

실용도가 가장 높은 케이스 — 문서 사이트 + 인덱스 페이지를 한 파일로. slug 가 없으면 "전체 목차", 있으면 "해당 문서" 식으로 분기.

5. 네 가지를 한 표로

패턴매칭params 모양대표 사용처
[slug]정확히 한 세그먼트string블로그 글·상품 상세
(group)URL 에 미반영N/Alayout 분리 (마케팅/앱)
[...slug]1개 이상 세그먼트string[]문서 사이트 (인덱스 별도)
[[...slug]]0개 이상 세그먼트string[] | undefined문서 사이트 (인덱스 같이)

실전 비율 — [slug] 70% · (group) 20% · catch-all 둘 합쳐 10%. 처음엔 [slug] 만 익혀도 충분.

요약 — 5편 좌표

여기까지 정리. Next.js App Router 는 폴더 이름으로 라우팅을 결정한다 — 대괄호 동적, 소괄호 그룹, 대괄호 점3개 catch-all, 대괄호 한 번 더 옵셔널. 페이지 안에선 params (Next 15 부터 Promise) 로 동적 값을 받는다. 같은 URL 이 둘 이상 폴더에 매칭되면 충돌이라 주의. 다음 편에선 가장 큰 개념 — Server Component vs Client Component — 의 차이를 본다.

다음 편 예고 — Server Component vs Client Component

두 컴포넌트 모델의 결정적 차이와 사용 기준. 'use client' 의 의미. 6편.

© 2026 주나이테크(주) @JUNAITECH