Next에서 더 효과적인 modal 개발하기 (Parallel Routes & Intercepting Routes) 😎
기존에 React modal을 개발할 때 React Portal을 사용하기도 하고, 조건부 렌더링을 사용하기도 했다.
내가 개발해봤던 이런 기존 방식의 모달은 단점이 있는데, 단순히 컴포넌트를 JS로 화면에 띄우는 방식이기에 브라우저 기능과 웹페이지가 연동된 느낌은 아니다.
즉, 모달을 띄우고 브라우저 뒤로가기를 눌렀을 때 사용자는 모달이 닫히는 것을 생각할 수 있지만 실제로는 모달과 그 뒤의 화면이 통째로 날아가는 경험을 할 수 있다는 점이다.
Next.js 15 app router에서는 Parallel Routes과 Intercepting Routes를 사용하여 좀 더 나은 형태의 모달을 개발할 수 있다고 들었기에, 직접 알아보고 예제를 작성해보기로 했다 🥜
Parallel Routes
Next의 앱 디렉토리 구조에서는 병렬(Parallel)로 실행되는 UI 영역(세그먼)을 정의할 수 있다.
예를 들어, 메인 콘텐츠와 동시에 모달 같은 보조 UI를 함께 렌더링할 수 있습니다.
병렬 라우트를 사용하면,
- 페이지 전환 없이 동일한 레이아웃내에 여러 UI 영역을 동시에 업데이트 가능
- 모달이나 사이드바 같은 UI 컴포넌트를 독립적으로 관리할 수 있음
예제로 보면,
import { ReactNode } from 'react';
interface RootLayoutProps {
children: ReactNode;
modal?: ReactNode;
}
export default function RootLayout({ children, modal }: RootLayoutProps) {
return (
<html lang="ko">
<head>
<title>Next Modal</title>
</head>
<body>
<main>{children}</main>
{/* Parallel Routes를 통해 렌더링될 추가 UI 영역 */}
{modal}
</body>
</html>
);
}
layout.tsx에서 기본적으로 보여지는 컨텐츠인 {children} 뿐만 아니라, 추가적인 UI 영역인 {modal} 을 동시에 렌더링할 수 있다.
해당 레이아웃이 있는 폴더 내부의 @modal/page.tsx를 찾아서 렌더링하게 된다.
Intercepting Routes
Intercepting Routes는 사용자가 특정 라우트로 이동할 때, 기존 페이지를 유지하면서 모달이나 오버레이 UI를 덧씌우도록 라우트를 가로채는 기능이다.
폴더 이름에 괄호( )를 사용해 라우트 그룹을 정의하면, 모달 인터셉팅 그룹을 손쉽게 만들 수 있다.
일단 /next-modal/screen/layout.tsx를 다음과 같이 구성한다.
import { ReactNode } from 'react';
interface RootLayoutProps {
children: ReactNode;
modal?: ReactNode;
}
export default function Layout({ children, modal }: RootLayoutProps) {
return (
<html lang="ko">
<head>
<title>Next Modal</title>
</head>
<body>
<main>{children}</main>
{/* Parallel Routes를 통해 렌더링될 추가 UI 영역 */}
{modal}
</body>
</html>
);
}
위의 병렬 라우트에 인터셉트 라우트를 함께 적용하면, 위의 구조에서 /next-modal/screen/page.tsx에서 Link를 통해서 next-modal로 이동했을 때,
import Link from 'next/link';
import React from 'react';
// next-modal/screen/page.tsx
const NextModal = () => {
return (
<div>
<div>NextModal</div>
<Link href="/next-modal">모달 띄우기</Link>
</div>
);
};
export default NextModal;
(즉 http://localhost:3000/next-modal/screen에서 http://localhost:3000/next-modal로 이동)
폴더 상의 /next-modal/page.tsx로 이동하는 것이 아니라, /next-modal/screen/@modal/(...)next-modal/page.tsx의 컴포넌트를 보여주게 된다. (인터셉트)
이를 이용해서 모달을 구현할 수 있다.
next-modal/screen 페이지를 메인 화면으로 사용하면서 모달이 떴을 때 페이지를 next-modal로 구현하면 next-modal/screen에서 next-modal로 이동 시 인터셉트가 일어나 /next-modal/page.tsx가 아니라, /next-modal/screen/@modal/(...)next-modal/page.tsx 컴포넌트가 보여지게 되고 이 컴포넌트에 모달을 구현하면 된다.
import Link from 'next/link';
import React from 'react';
// /next-modal/screen/@modal/(...)next-modal/page.tsx
const Modal = () => {
return (
<div
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
}}
>
<h1>Modal 🍔</h1>
<Link href="/next-modal/screen">닫기</Link>
</div>
);
};
export default Modal;
모달을 닫을 떄에는 다시 /next-modal/screen으로 돌아가면 된다.
이렇게 구현했을 때, 자연스럽게 모달의 뒤로가기도 구현할 수 있다.
다만, 직접 /next-modal URL에 접근하면 /next-modal/page.tsx 컴포넌트가 보여지게된다.
직접 URL로 접근했을 때에도 같은 화면을 보여주고자 한다면, /next-modal/screen/@modal/(...)next-modal/page.tsx 컴포넌트를 재활용할 수도 있다 🙌
헷갈릴 수 있는 내용인데, 메인 화면 URL은 /next-modal가 아니라, /next-modal/screen이고 모달 URL이 /next-modal가 된다
- app 하위 프로젝트 폴더 구조
next-modal/
├── screen/
│ ├── @modal/ # Parallel Routes용 모달 컨텐츠
│ ├── (...)next-modal/ # Intercepting Route (부모 경로 유지)
│ │ ├── page.tsx # 인터셉팅된 페이지
├── layout.tsx # 전체 레이아웃
├── page.tsx # 루트 페이지
├── default.tsx # 기본 컴포넌트 (fallback?)
+ tanstack router에서도 비슷한 내용을 route-masking로 구현할 수 있지 않을까?
작성한 코드 🔭
https://github.com/citron03/practice-next-15/commit/51194ac07e11d690eabfe3f43016279eaeac4f9b
feat: modal with Parallel Routes & Intercepting Routes · citron03/practice-next-15@51194ac
citron03 committed Feb 18, 2025
github.com
참고 자료 🙏
https://nextjs.org/docs/app/building-your-application/routing/parallel-routes
Routing: Parallel Routes | Next.js
Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.
nextjs.org
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes
Routing: Intercepting Routes | Next.js
Use intercepting routes to load a new route within the current layout while masking the browser URL, useful for advanced routing patterns such as modals.
nextjs.org
[Next.js] 모달 열면서 URL 변경하기 (Parallel Routes, Intercepting Routes)
Parallel Routes, Intercepting Routes 를 사용하여 모달을 열면서 URL을 변경하는 방법에 대해 알아보자.
velog.io
https://tanstack.com/router/latest/docs/framework/react/guide/route-masking
Route Masking | TanStack Router React Docs
Route masking is a way to mask the actual URL of a route that gets persisted to the browser's history and URL bar. This is useful for scenarios where you want to show a different URL than the one that...
tanstack.com
https://www.heropy.dev/p/n7JHmI#h3_%EA%B2%BD%EB%A1%9C_%EB%B3%91%EB%A0%AC_%EC%B2%98%EB%A6%AC
Next.js 15 핵심 정리
Next.js는 Vercel에서 개발한 React 프레임워크로, 서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR), API 라우팅 등의 다양한 최적화 기능을 제공합니다. Next.js를 사용하면, React의 기본 기능을
www.heropy.dev