next.js 13부터 추가된 앱 라우터에서는 parallel routes(병렬 경로)이라는 기능을 제공하는데, 이를 이용하여 간단하게 조건부 모달 처리를 할 수 있다.
병렬 경로란 하나의 레이아웃에서 여러 페이지를 동시에 보여줄 수 있는 기능으로, 슬롯(@slot)을 통해 구현할 수 있다.
슬롯은 앱 라우터에서 추가된 개념으로, 두 개 이상의 페이지를 동시에 띄울 수 있는 기능이다.
주로 메인 페이지 위에 별도 페이지를 모달 형태로 띄우기 위해 사용된다.
ex) 메인 페이지(children)가 떠있는 상태에서 로그인 페이지를 노출
기존에는 이러한 모달 처리는 Portal + useState(혹은 query parameter) 방식으로 구현했었다.
Portal이란?
부모 컴포넌트 바깥 위치에 컴포넌트를 렌더링할 수 있는 기능
React에서는 createPortal로 생성 후 사용할 수 있음
병렬 경로 기능을 사용하면 모달 오픈 여부를 별도 상태값으로 관리하지 않고 경로(/path)로 관리할 수 있다.
병렬 경로로 모달 노출하기
병렬 경로 기능을 이용하여 /sign-in 이라는 경로로 접근했을 때 로그인 모달이 노출되게 작업해 보자.
(/another-page -> /sign-in)
최종 페이지 폴더 구조를 살펴보면 다음과 같다.
- app
- @modal
- sign-in
- page.tsx
- default.tsx
- page.tsx
- layout.tsx
- another-page
- ...
먼저 root 경로에 @modal 이라는 이름으로 슬롯을 만들고, 슬롯 하위에 경로명으로 사용할 sign-in 폴더를 추가한 후 page.tsx 내에 모달을 작성한다.
// app/@modal/sign-in/page.tsx
const Page = () => {
return (
<div>
<p>이 곳에 로그인 모달을 구현해 줍니다.</p>
</div>
);
};
export default Page;
그리고 매칭되는 슬롯이 없을 때 기본으로 노출될 default.tsx 컴포넌트를 @modal 슬롯 하위에 추가한다.
default.tsx는 fallback과 동일한 기능이라고 보면 되며, 보통의 경우는 위 예시 코드처럼 null을 리턴하도록 간단하게 작성하면 된다.
// app/@modal/default.tsx
const Default = () => null; // 아무것도 노출하지 않음
export default Default;
이제 layout.tsx에 모달 슬롯이 위치할 수 있도록 추가하자.
슬롯은 상위 경로 layout.tsx의 props로 전달되므로, 아래와 같이 슬롯명으로 받아서 추가해주면 된다.
// app/layout.tsx
import Header from '@/components/header';
import Footer from '@/components/footer';
const RootLayout = ({
children,
modal,
}: Readonly<{
children: ReactNode;
modal: ReactNode;
}>) => {
return (
<html lang="en">
<body>
<main>
<Header />
{children}
{modal}
<Footer />
</main>
</body>
</html>
);
};
export default RootLayout;
테스트를 해보면 아래와 같이 모달이 잘 열리는 것을 확인할 수 있다.
Trouble Shooting - 새로고침시 404 페이지 노출 수정
페이지 내에서 클릭하여 이동하는 경우에는 위와 같이 모달이 잘 뜨지만, 새로고침 시에는 모달 뒤 페이지가 (혹은 빈 페이지 404)로 뜨는 이슈가 있었다.
이유는 /sign-in은 @modal 슬롯 내에만 존재하는 가상의 경로이고, 실제로는 존재하지 않기 때문이다.
이를 해결하는 방법은 2가지가 있는데, fallback으로 노출할 default.tsx를 추가하거나 Intercepting Routes(경로 가로채기)를 사용하여 슬롯 영역 밖에 있는 sign-in/page.tsx을 렌더링하는 것이다.
fallback으로 노출할 default.tsx를 추가
모달 외에 별도의 페이지를 만들고 싶지 않을 때 사용하는 방법이다.
새로 고침 시에 뒷 페이지로 루트(/) 페이지를 렌더링하도록 하고 싶다면 아래와 같이 작성하면 된다.
// app/default.tsx
import Page from './page';
const Default = () => <Page />;
export default Default;
페이지에서 접근했을 때만 모달 띄우기(경로 가로채기를 통한 조건부 렌더링)
Intercepting Routes(경로 가로채기)이란 경로를 가로채서 현재 경로의 페이지가 아닌 상위 레벨의 다른 경로 페이지를 보여줄 수 있는 기능이다.
병렬 경로와 함께 사용한다면, 페이지 내에서 이동하는 경우엔 병렬 경로(슬롯)으로 동작하고 새로고침 혹은 직접 접근하는 경우에는 경로 가로채기로 동작한다.
경로 가로채기 명명 규칙은 다음과 같으며, 상대 경로 규칙과 상당히 유사하다.
명명 규칙 | 설명 |
(.)path | 동일한 레벨의 경로와 일치 ex) /app/a/(.)b => /a/b 경로와 일치 |
(..)path | 한 단계 위의 경로와 일치 ex) /app/a/b/(..)c => /a/c 경로와 일치 |
(..)(..)path | 두 단계 위의 경로와 일치 ex) /app/a/b/(..)(..)c => /c 경로와 일치 |
(...)path | 루트 단계의 경로(1 depth)와 일치 ex) /app/a/(...)b => /b 경로와 일치 |
예시를 통해 이해해 보자.
아래처럼 갤러리 목록에서 사진을 클릭했을 때는 이미지를 모달로 보여주고, 새로고침을 했을 때는 전체 화면으로 보여줄 수 있다.
위 스크린샷과 같은 페이지 구조를 갖는다면,
- 갤러리 목록 경로는 /feed
- 모달 이미지 경로는 /feed/[id]
- 페이지 내에서 접근시 병렬 경로 동작 -> @modal 슬롯 내의 화면으로 보여짐
- 새로고침 시에는 경로 가로채기 동작 -> 통해 /photo/[id]와 동일한 화면으로 보여짐
가 된다.
위 예시는 nextjs에서 제공하는 데모 페이지인 https://nextgram.vercel.app/에서도 테스트해볼 수 있다.
(깃허브 주소는 https://github.com/vercel/nextgram 참고)
상위 경로가 없는 경우(1 depth)에서 조건부 모달 띄우기
위에서는 2 depth + 동적 경로 파라미터 사용시 조건부 모달을 띄우는 방법을 알아보았다.
위 방식과 비슷하게 / -> /sign-in 처럼 1 depth 경로에서는 (..) 대신 (.)를 사용하여 경로를 가로채면 조건부 모달을 띄울 수 있다.
- app
- @modal
- (.)sign-in
- page.tsx
- default.tsx
- sign-in
- page.tsx
테스트 겸 codesandbox에도 올려놓았다.
참고 링크
'Frontend > Next.js' 카테고리의 다른 글
Next.js에서 locomotive-scroll 사용하기 (2) | 2024.06.03 |
---|---|
Next.js - Warning: Prop `...` did not match 이슈 해결 (1) | 2023.11.27 |
Next.js - getStaticProps vs getServerSideProps vs getStaticPaths vs getInitialProps (0) | 2022.06.12 |