React Parallel Routes
AOS 웹뷰에서 뒤로가기시 모달을 닫으려면?
2025-04-14
React Parallel Routes와 View Transitions API
웹뷰 개발시 AOS에서 뒤로가기 버튼을 누르면 모달이 닫히지 않고, 앱이 종료되거나 웹뷰가 닫히는 이슈가 생길 수 있다. 보통 모달을 만들 때 열고 닫히는 상태 기반으로 모달을 만든다. 이런 경우 애니메이션 처리나 URL 동기화, 뒤로가기 동작을 처리하기가 불편할 수 있다.
이러한 단점을 해결하기위해 react-router-dom
으로 병렬 라우팅을 구현할 수 있다.
이 글에서는 react-router-dom
을 사용하여 병렬 라우팅을 구현하는 방법과 View Transitions API
를 활용하여 모달 애니메이션을 처리하는 방법에 대해 작성하고자 한다.
Nextjs 에서는 appRouter에서는 기본적으로 병렬 라우팅을 지원한다.
app/
├── layout.tsx
├── page.tsx # 홈
├── inbox/
│ ├── page.tsx # 리스트
│ ├── @modal/ # 병렬 슬롯
│ │ ├── default.tsx # 모달 없는 기본 화면
│ │ └── [id]/page.tsx # 특정 모달 열릴 때
export default function InboxLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<div>
{children}
{modal}
</div>
);
}
대충 이런식으로 구현이 가능하다. 그럼 React에서는 어떻게 구현할 수 있을까?
먼저 간단하게 라우팅을 구성해보자
// App.tsx
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import ListPage from "./routes/ListPage.tsx";
import DetailModal from "./components/DetailModal.tsx";
const router = createBrowserRouter([
{
path: '/list',
element: <ListPage />,
children: [
{
path: ':id',
element: <DetailModal />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App
/list/1 로 접근했을 때, createPortal로 구성한 모달이 열리게하고, 뒤로가기 버튼을 누르면 모달이 닫히게 구현할 수 있다.
// ListPage.tsx
import {Outlet} from 'react-router-dom';
import useViewTransitionNavigate from "../hooks/useViewTransitionnavigate.ts";
export default function ListPage() {
const navigate = useViewTransitionNavigate();
return (
<div>
<h2>리스트 페이지</h2>
<ul>
{[1, 2, 3].map(id => (
<li key={id}>
<button onClick={() => navigate(`/list/${id}`)}>Item {id} 보기</button>
</li>
))}
</ul>
<Outlet />
</div>
);
}
ListPage에서는 Outlet을 사용하여 병렬 라우팅을 구현했다.
여기서 Outlet은 중첩 라우트를 렌더링하는 컴포넌트인데, 자식 컴포넌트를 렌더링하는 위치를 나타낼 수 있다.
<Outlet />
을 사용하여 ListPage의 자식인 모달을 렌더링할 수 있다.
children: [
{
path: ':id',
element: <DetailModal />,
},
]
// DetailModal.tsx
import {useParams} from 'react-router-dom';
import Modal from './Modal';
import useViewTransitionNavigate from "../hooks/useViewTransitionnavigate.ts";
export default function DetailModal() {
const { id } = useParams();
const navigate = useViewTransitionNavigate();
return (
<Modal onClose={() => navigate('/list')}>
<h3>Item {id} 상세</h3>
<p>이건 모달입니다</p>
</Modal>
);
}
// useViewTransitionNavigate.tsx
import {useNavigate} from 'react-router-dom';
export default function useViewTransitionNavigate() {
const navigate = useNavigate();
return (to: string | number) => {
if (typeof to === 'number') {
return navigate(to);
}
if ('startViewTransition' in document) {
(document).startViewTransition(() => navigate(to));
} else {
navigate(to);
}
};
}
간단한 hook을 만들어서 startViewTransition
이 지원되는 경우에만 애니메이션을 적용하도록 했다.
View Transitions API는 DOM 변경을 애니메이션으로 처리할 수 있는 API로, 브라우저에서 지원하는 경우에만 사용할 수 있다.
대부분 크롬계열의 브라우저만 지원하고, 아직 지원하지 않는 브라우저가 많아서 모바일 환경의 웹뷰에서는 사용하기 힘들것 같다.
View Transitions API 대해서는 다음에 작성하는걸로하고,, 위처럼 구현해서 모달을 띄우면 URL이 /list/1로 변경되고, /list 페이지에서 모달이 열리게 된다.

이런 식으로 모달을 구현하면 좀 더 앱스러운 UX를 제공할 수 있다. 뒤로가기시에도 모달이 닫히고, URL도 변경되기 때문에 사용자가 뒤로가기 버튼을 눌렀을 때 모달이 닫히는 동작을 자연스럽게 처리할 수 있다.