2026.10.07 · 4분 읽기 · 조회 0

인터셉팅 라우트란, 사용자가 새로운 페이지를 요청할 때 해당 요청을 가로채서 다른 페이지를 대신 렌더링되도록 설정하는 패턴을 말한다. 특정 조건을 만족하면 인터셉팅 라우트가 가능하다.
특정 조건은 next앱에서 지정해두었는데, 사용자가 초기접속이 아닌 클라이언트 사이드 렌더링 방식으로 (link태그, router.push 등) 페이지를 이동했을 때만 인터셉팅 라우트가 동작한다.
대표적 예시 중 하나가 인스타그램이다.
인스타그램에서 피드를 누르면 상세 페이지가 모달처럼 뜬다. 이때, 새로고침을 해서 초기 접속으로 바꿔주면
아예 상세 페이지로 이동이 된다. 초기접속이기 때문에 인터셉팅 라우트가 동작하지 않은 것이다.
app
ㄴ book/[id]
ㄴ page.tsxbook/[id] 내의 page.tsx 페이지를 인터셉팅할 수 있는 페이지를 만들어보자.
app
ㄴ book/[id]
| ㄴ page.tsx
|
ㄴ (.)book/[id]다음과 같이 가로챌 페이지 폴더의 명을 그대로 가져와 (.)페이지명 이라는 폴더를 만든다.
소괄호 뒤에 나오는 경로를 지금부터 인터셉트하라는 의미이다.
소괄호 안에 .이 하나만 있다면 상대 경로 상 동일한 경로를 인터셉팅 하겠다는 의미다.
인터셉팅 할 폴더가 상위 경로에 있다면 ..처럼 상대 경로를 바꾸어줘야 한다.
폴더가 두 단계 상위에 있다면 (..)(..)로 적어주면 되며,
(...) 으로 되어있다면 app 폴더 바로 밑의 폴더를 인터셉팅 하겠다는 의미다.
app
ㄴ book/[id]
| ㄴ page.tsx
|
ㄴ (.)book/[id]
ㄴ page.tsx이제 해당 인터셉팅할 페이지 내부에 page.tsx 파일을 만들어준다. 그럼 클라이언트 사이드 렌더링 시에 해당 페이지가 대신 나타난다.
export default function Page(props : any){
return (
<Modal>
<BookPage {...props}/>
</Modal>
)
}인터셉팅 페이지에서 기존의 페이지를 반환해주는데 <Modal> 이라는 컴포넌트를 만들어서 감싸서 반환해주면 될 것이다.
//Modal.tsx
export default function Modal({children} : {children : ReactNode}){
return (
<dialog>
{children}
</dialog>
)
}모달 컴포넌트에서는 적당히 스타일링을 한 후, 모달로 감싸서 반환해주면 된다.
//Modal.tsx
import { createPortal } from "react-dom";
export default function Modal({children} : {children : ReactNode}){
return createPortal(
<dialog>
{children}
</dialog>
, document.getElementById("modal-root") as HTMLElement)
}이때 페이지를 감싸는 모달인 dialog 태그는 페이지 최상단의 태그로 위치하는 것이 모달의 기능으로 자연스러우므로 createPortal 함수를 사용해서 레이아웃 최상단으로 끌어올려 준다.
createPortal(끼워넣을 컴포넌트, 끼워넣을 위치) 로 지정해주면 된다. id를 modal-root 으로 지정했으므로 루트 레이아웃에 해당 id값을 가진 태그를 추가해주면 된다.
// Modal.tsx
<dialog
onClose={() => router.back()}
onClick={(e) => {
if ((e.target as any).nodeName === "DIALOG") {
router.back();
}
}}>
{children}
</dialog>router 훅을 사용해서 모달 바깥 클릭 or 모달 닫을 시 이전 페이지로 보내주는 로직을 넣어주면 더 완성도 있는 모달을 만들 수 있다.
하지만 이렇게만 하면 완성도가 부족하다.
기존의 페이지가 이렇게 있다.
책을 선택해 인터셉팅 라우트를 해주면 미리 설정해놓은 모달로 페이지가 나타난다.
그러나 뒷 배경에는 기존의 인덱스 페이지가 아니라 임시로 채워넣은 페이지가 존재한다.
export default function Page(props : any){
return (
<Modal>
<BookPage {...props}/>
</Modal>
)
}당연하다 모달로 감싸주었을 뿐, 해당 페이지는 기본 레이아웃을 제외하고 텅 비어있기 때문이다.
때문에 뒷배경에 기존의 메인 페이지를 병렬로 띄워주는 것이 좋다.
병렬 라우팅은 이미 공부한 바가 있다.
app
ㄴ book/[id]
| ㄴ page.tsx
|
ㄴ (.)book/[id]
ㄴ page.tsx // <- 모달 위치현재 다음과 같은 폴더 구조에 슬롯을 생성해준다.
app
ㄴ book/[id]
| ㄴ page.tsx
|
ㄴ @modal
ㄴ (.)book/[id]
ㄴ page.tsx // <- 모달 위치다음과 같이 슬롯 내부에 인터셉팅 라우트 페이지를 넣어주면 해당 페이지는 루트레이아웃의 프롭스에 들어가게 된다.
// layout.tsx
export default function Layout({children, modal}){
return <div>{children} {modal}</div>
}그러면 루트 레이아웃에서 그냥 꺼내서 써주면 된다. 주의할 점은 default.tsx 파일도 슬롯 내부에 추가해주어 modal 이 없는 경로의 경우에도 오류가 나지 않도록 해주는 것이다.
// @modal/default.tsx
export default function Default(){
return null;
}위와 같이 작성해주면 모달이 없는 페이지에서는 null이 반환되어 아무 오류도 일어나지 않는다.
사용자가 /index에서 /books/1로 이동
↓
Intercepting Route가 경로 가로챔
↓
┌─────────────────────────────────────┐
│ RootLayout │
│ │
│ children → 기존 인덱스 페이지 │
│ modal → 인터셉팅된 북 상세 페이지 │
└─────────────────────────────────────┘
↓
인덱스 페이지 + 모달 동시 렌더링
이렇게 하면 기존의 인덱스 페이지와 병렬로 잘 표시되는 것을 볼 수 있다.
앱과 웹을 만들고, 직접 운영하며 기록합니다.
giscus로 동작하며 GitHub Discussions에 저장됩니다.