2025 . 오은

repo

Parallel, Intercepting routes

자주써도 헷갈리는 parallel routes 와 intercepting routes 로 기본적인 modal 만드는 법을 정리해보려고 합니다.

1. Parallel Routes

Parallel Routes 는 모달을 만들기 위한 기능은 아닙니다.

공식문서에 따르면,

Parallel Routes를 사용하면 동일한 레이아웃 내에서 하나 이상의 페이지를 동시에 또는 조건부로 렌더링할 수 있습니다. 대시보드나 피드와 같이 앱의 매우 동적인 섹션에 유용합니다. 예를 들어 대시보드에서 Parallel Routes를 사용하여 팀 페이지와 분석 페이지를 동시에 렌더링할 수 있습니다:

라고 합니다.

만드는 법은 아래처럼 @ 를 붙여서(슬롯이라고 합니다) 폴더를 생성하고 page.tsx 만들면 됩니다.

app/
  ├─ @modal/
  │   ├─ /example         
  │   │    └─ page.tsx 
  │   └─ default.tsx
  ├─ example/  
  │   └─ page.tsx
  ├─ layout.tsx    
  └─ page.tsx

그리고 layout.tsx 에 아래처럼 설정합니다.

interface LayoutProps {
  children: React.ReactNode;
  modal: React.ReactNode;
};

function Layout({ children, modal }: LayoutProps) {
  return (
    <>
      {children}
      {modal}
    </>
  );
}

export default PublicLayout;

주의점은 슬롯은 경로 세그먼트가 아니며 URL 구조에 영향을 미치지 않습니다. 예를 들어 /@modal/example 의 경우 @modal 이 슬롯이므로 URL은 /example 가 됩니다. (슬롯은 url 에 영향을 주지 않고 무시됩니다)

이 예시에서는, /example 로 접속시 children 부분에는 /example 이, modal 부분에는 /@modal/example 이 매핑됩니다.

만약 / 로 접속한다면 children 부분에는 app/page.tsx 가 맵핑되고 {modal} 부분에는 default.tsx 가 맵핑됩니다.

default.tsx 는 Parallel Routes가 이용되지 않을 때(경로가 unmatched 일 때) 이 페이지를 디폴트로 보여줍니다. (하드네비게이션, 소프트네비게이션 차이가 있는데 공식문서 를 참조하면 좋습니다)

여기까지 아주 기본적인 Parallel Routes 의 개념입니다. 그러나 이 상태로는 /example 로 소프트 네비게이션시 병렬적으로(동시에) /example 과 /@modal/example 이 렌더링 되므로 모달과는 다릅니다.

여기서부터 Intercepting Routes 를 알아보아야 합니다.

2. Intercepting Routes

Intercepting Routes 는 이름처럼 라우트를 '가로챕'니다. 공식문서에서는 다음처럼 설명하고 있습니다.

Intercepting Routes는 현재 레이아웃 내에서 애플리케이션의 다른 부분의 경로를 로드할 수 있습니다. 이 라우팅 패러다임은 사용자가 다른 컨텍스트로 전환하지 않고도 경로의 콘텐츠를 표시하려는 경우에 유용할 수 있습니다.

즉, 현재 페이지 컨텍스트를 유지한 채로 새로운 라우트를 렌더링 해줍니다. 따라서 주소를 바꾸지않고 다른 주소의 페이지들을 렌더링 할 수 있습니다.

Intercepting Routes 는 (..) 와 같은 컨벤션으로 정의됩니다.

  • (.) 동일한 라우팅 레벨 세그먼트에 매칭
  • (..) 부모 라우팅 레벨 세그먼트에 매칭
  • (..)(..) 2단계 윗 레벨
  • (…) app 디렉토리 루트 요소에 매칭

이때 기준은 경로 세그먼트(브라우저 주소...) 이므로 폴더구조와 혼동되면 안됩니다. 따라서 아래처럼 될 수 있습니다.

app/
  ├─ @modal/
  │   ├─ /(.)example         
  │   │    └─ page.tsx 
  │   └─ default.tsx
  ├─ example/  
  │   └─ page.tsx
  ├─ layout.tsx    
  └─ page.tsx

폴더 구조상으로는 app/@modal/(.)example 로 app/example 과 다른 레벨에 있는 것 같지만 경로 세그먼트 기준이므로, @modal은 무시되어 둘은 같은 레벨임에 주의하면 됩니다.

이렇게 모달을 만들시 좋은 점은 공식문서에 따르면 다음과 같습니다.

  • 뒤로가기(router.back())로 모달을 열고 닫을 수 있음
  • URL 공유를 통해 모달 내용 공유 가능
  • 페이지 새로고침 시 모달 안닫힘

즉, 경로로 접근시 모달이 뜨기 때문에 모달 자체를 url 로 관리할 수 있습니다. 많이 쓰는 방법인 createPortal 에 비해 분명한 장점이 있습니다.

그러나 방법이 어떻게 보면 더 난해(?) 할 수도 있기 때문에 필요에 따라 쓰면 좋을 것 같습니다.

추가로, Parallel, Intercepting routes 의 사용법은 모달을 만들기 위한 것은 아닙니다. 그냥 모달로 쓰면 좋고 공식문서에도 예제로 나올 뿐이라고 생각됩니다. 이외에도 사용법이 많으니 공식문서를 한번 정리해봐야겠습니다.

+ 각 파일의 내용

// app/@modal/default.tsx
export default function Default() {
  return null
}
// app/@modal/(.)example/page.tsx
import { Sample, Modal } from "@/components";

function InterceptingPage() {
  return (
    <Modal>
      <Sample />
    </Modal>
  );
}
export default InterceptingLoginPage;
// app/example/page.tsx
import { Sample } from "@/components";

function gPage() {
  return (
   <Sample />
  );
}
export default Page;
// app/layout.tsx
interface LayoutProps {
  children: React.ReactNode;
  modal: React.ReactNode;
};

function Layout({ children, modal }: LayoutProps) {
  return (
    <>
      {children}
      {modal}
    </>
  );
}

export default PublicLayout;