Parallel Routes로 URL 기반 모달 구현하기
Parallel Routes로 URL 기반 모달 구현하기

Parallel Routes로 URL 기반 모달 구현하기

작성일
2026년 01월 18일
태그
nextjs
react
카테고리
nextjs
Last edited time
Last updated January 18, 2026
날짜
블로그에서 포스트를 클릭하면 모달로 열리고, URL도 바뀌는 UX를 구현했다.

기존 모달의 문제

일반적인 모달 (useState, overlay-kit 등)의 문제:
  • URL이 안 바뀌는다 → 공유 불가
  • 새로고침하면 모달 닫힘 → 상태 유실
  • SEO에 불리 → 모달 내용 크롤링 안 됨
디자이너 사이트는 이미지 클릭 시 모달로 상세가 열리는 게 핵심 UX였다. 하지만 링크 공유도 되어야 했다.

Parallel Routes + Intercepting Routes

Next.js App Router의 이 기능으로 해결했다.

폴더 구조

src/app/ ├── @modal/ # Parallel Route (slot) │ ├── (.)archive/[id]/ # Intercepting Route │ │ └── page.tsx # 모달로 열릴 때 │ └── default.tsx # 모달 없을 때 ├── archive/ │ ├── page.tsx # 목록 │ └── [id]/ │ └── page.tsx # 직접 접근 시 전체 페이지 └── layout.tsx
핵심 컵셈:
  • @modal: Parallel Route slot 이름 (@로 시작)
  • (.): 같은 레벨의 라우트를 인터셉트

동작 방식

1. 목록에서 클릭 시

/archive 에서 포스트 클릭 ↓ URL: /archive/123 으로 변경 ↓ (.)archive/[id]/page.tsx 가 인터셉트 ↓ 모달로 표시 (배경에 목록 유지)

2. URL 직접 접근 또는 새로고침

/archive/123 직접 접속 ↓ 인터셉트 안 됨 ↓ archive/[id]/page.tsx 로 렌더링 ↓ 전체 페이지로 표시

구현 코드

Root Layout

// src/app/layout.tsx export default function RootLayout({ children, modal, }: { children: React.ReactNode; modal: React.ReactNode; // @modal slot }) { return ( <html> <body> {children} {modal} </body> </html> ); }

default.tsx (필수!)

// src/app/@modal/default.tsx export default function Default() { return null; }
왜 필수인가?
Parallel Routes는 현재 URL과 매칭되지 않을 때 default.tsx를 찾는다. 없으면 404가 뜬다.
예: /archive 페이지에서는 @modal/(.)archive/[id]가 매칭 안 되므로 default.tsx가 렌더링됨.

모달 페이지

// src/app/@modal/(.)archive/[id]/page.tsx import { Modal } from '@/components/modal'; import { PostDetail } from '@/widgets/PostDetail'; export default async function PostModal({ params }: { params: { id: string } }) { const post = await getPostById(params.id); return ( <Modal> <PostDetail post={post} /> </Modal> ); }

전체 페이지

// src/app/archive/[id]/page.tsx import { PostDetail } from '@/widgets/PostDetail'; export default async function PostPage({ params }: { params: { id: string } }) { const post = await getPostById(params.id); return ( <main> <PostDetail post={post} /> </main> ); }
포인트: PostDetail 컴포넌트를 공유해서 모달/페이지 둘 다 동일한 UI.

인터셉트 규칙

패턴
의미
(.)
같은 레벨
(..)
한 레벨 위
(..)(..)
두 레벨 위
(...)
루트부터
이 프로젝트에서는 @modalarchive가 같은 레벨이므로 (.)를 사용.

결과

  • 목록에서 클릭 → 모달로 열림 + URL 변경
  • URL 공유/archive/123 링크로 공유 가능
  • 새로고침 → 전체 페이지로 보임
  • SEO → 각 포스트가 별도 URL을 가짐
디자이너 사이트의 모달 UX를 유지하면서 URL 기반 네비게이션까지 얻을 수 있었다.