Recoil
을 통해 오픈된 모달의 리스트를 배열에 key로 저장해서 전역적으로 관리했다.
지도 기반 웹이기 때문에 마커를 클릭하면 하단에 정보가 담긴 박스가 보여지고 박스 안의 '자세히 보기' 버튼을 누르면 window.history.pushState(null, '', /place/${id}
)를 통해 url만 변경시켜서 모달 내 정보를 보여줌으로써 뒤로가기가 가능하도록 했다.
지도 페이지가 다시 렌더링 되는 것을 방지하고 상태를 그대로 유지하기 위해 모달로 구현했다.
마커 클릭 시에는 url 변경 없이 모달1이 오픈되고, 모달1 내의 '자세히 보기' 버튼 클릭 시에는 url이 변경되고 모달2가 오픈된다.
=> 같은 모달 형태이지만 일관된 사용자 경험을 제공하기 어렵다. (특히 뒤로가기 발생 시)
=> 개발하는 입장에서 이벤트 발생에 따라 모달을 열고 닫을 때 Recoil을 통한 모달 상태 관리와 url 변경 모두 신경 써야 한다.
url로만 모달의 상태를 변경하고 관리하기 위해 Next.js의 Intercepting Routes
를 사용하여 코드를 전체적으로 수정했다.
Intercepting Routes
는 쉽게 말해 경로 가로채기이다.
Intercepting Routes
를 사용하기 위해서는 반드시 아래와 같은 컨벤션을 따라야한다.
디렉토리 구조에 맞게 결정하면 된다.
다음은 Next.js 공식문서의 예제이다.
/feed
경로에서 리스트 카드를 클릭하면 /photo/123
으로 경로가 변경되고 이미지를 보여주는 모달창이 보여진다.
모달을 구현하는 위 예제에서 Parallel Routes
기능이 함께 쓰인다.
먼저 @modal
이라는 슬롯을 생성하여 상위 Layout에 prop으로 전달하고 children prop과 함께 렌더링될 수 있게 한다. (@foldername 컨벤션 따름)
📍 참고로 @modal
은 세그먼트가 아니라 슬롯!!!이기 때문에 위 디렉토리 구조에서 2개의 depth 차이가 나지만 한 수준의 segment 차이이다.
한 수준 위의 segment와 일치시키기 위해 (..)photo
폴더를 생성한 것이다.
🧐 경로 가로채기가 발생한다면 빨간색 ②로 표시된 부분은 언제 필요할까?
경로 이동에 대한 이벤트가 발생하면 경로 가로채기가 발생하여 (..)photo
폴더 내의 page가 보여지는 것은 맞다.
하지만 refresh(새로고침)이나 initial load(초기 로드)하는 경우에는 기존 photo
폴더 내의 페이지가 보여지기 때문에 둘다 필요하다.
기존에 알던 Next.js의 폴더 구조에 따른 동작 방식을 생각해보면 acme.com/feed
에서 Link
태그를 통해 /photo/123
으로 경로를 이동하면photo/[id]
로 페이지 전환된다.
하지만 (..)photo
폴더가 정의되어 있으므로 경로 가로채기가 발생하여 위 이미지와 같이 url이 변경되고 모달창이 보여진다.
서비스 흐름
page.tsx
에서(경로: '/') 지도 위 마커 클릭 시/place/[id]
로 경로 이동+음식점의 간단한 정보 보여주는 모달1 open- 모달1 내의
자세히 보기
버튼 클릭 시/place/[id]/detail
로 경로 이동+음식점 상세 정보 보여주는 모달 2 open- +) 경로 앞, 뒤로 이동하면서 따라 모달 상태 변경 가능
1.Intercepting Route
와 기본 route
에서 공통으로 사용할 SimplePlace, DetailPlace 컴포넌트를 생성
2.@modal/(.)place/[id]/page.tsx
, /place/[id]/page.tsx
생성(Detail도 생성)
refresh(새로고침)이나 initial load(초기 로드)하는 경우에는 기존 photo
폴더 내의 페이지가 보여지기 때문에 필요하다.
3.src/app/layout.tsx
에 @modal 슬롯을 렌더링해주기 위해 prop으로 넘겨줌
export default function RootLayout({
children,
modal
}: Readonly<{
children: React.ReactNode;
modal: React.ReactNode
}>) {
return (
<html lang="en" className="font-Pretendard text-black">
<AuthContext>
<body className={inter.className && `flex flex-col w-full h-screen`}>
<Header />
<div className="w-full h-full">
{children}
{modal}
</div>
</body>
</AuthContext>
</html>
);
}
📦app
┣ 📂@modal
┃ ┣ 📂(.)place
┃ ┃ ┗ 📂[id]
┃ ┃ ┃ ┣ 📂detail
┃ ┃ ┃ ┃ ┗ 📜page.tsx
┃ ┃ ┃ ┗ 📜page.tsx
┃ ┣ 📜default.tsx
┃ ┗ 📜page.tsx
┣ 📂place
┃ ┗ 📂[id]
┃ ┃ ┣ 📂detail
┃ ┃ ┃ ┗ 📜page.tsx
┃ ┃ ┗ 📜page.tsx
┣ 📜default.tsx
┣ 📜globals.css
┣ 📜layout.tsx
┗ 📜page.tsx
지도 위 마커 클릭 시 url 변경과 함께 모달이 오픈되고 뒤로가기를 클릭하거나 모달 배경을 클릭 시 경로를 변경 시켜 모달을 닫을 수 있다.
모달이 오픈된 상태에서 새로고침을 하면 예상대로 /place/[id]
에 해당하는 기본 페이지가 렌더링되는 것을 확인할 수 있다.
참고자료
Next.js 공식문서-Intercepting Routes
https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#modals
https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#modals
https://medium.com/@noelchiwamba1/how-to-create-modals-with-unique-routes-in-next-js-a-guide-to-intercepting-routes-ef8dd0e36c3a
질문드려요 ~
/place/[id], /place/[id]/detail에 page.tsx가 없으면 모달을 켠 상태에서 새로고침을 하면 404가 나와서 page.tsx를 만든 걸로 생각이 드는데
/place/[id], /place/[id]/detail에 page.tsx에는 /place의 page.tsx 내용과 같은가요?