최근 회사에서 여러 실험을 진행하는 중인데 로그인 페이지를 모달로 띄워보자는 제안이 나왔다. 그래서 기존 로그인 페이지를 모달로 띄우는 UX를 만드려고 하는데 이전에도 유사한 시도를 했었고, 탭을 구현하면서 적용한 패러렐 라우트에 인터셉팅 라우트를 적용하면 간단히 해결될 듯했다. 사실 로그인 모달은 아니지만 다른 모달 페이지 처리를 할 때도 적용하려고 했는데 인터셉팅 라우트 적용 부분을 잘 이해 못해서 의도와 다르게 구현되는 바람에 그냥 GG를 친 적이 있었다. 그래서 이번엔 간단하게라도 정리를 해보려고 한다.
우선 들어가기 전에 간략히 설명하자면, 로그인 페이지를 페이지 전환 없이 모달로 열고, 주소창엔 /login이 표시되면서 뒤로 가기로도 닫히는 구조를 Next.js에서 구현하고자 할 때 그걸 가능하게 해주는 게 바로 패러렐 라우트 + 인터셉팅 라우트 조합이다.
app/
├── @modal/
│ └── (.)login/
│ └── page.tsx // 모달용 로그인
├── login/
│ └── page.tsx // 전체 페이지용 로그인
├── layout.tsx
├── page.tsx // 홈 페이지
@modal은 패러렐 라우트 슬롯 이름이고,(.)login은 인터셉팅 라우트.
(.)는 현재 경로를 인터셉트해서 모달만 띄우게 해줌.
// app/layout.tsx
export default function RootLayout({ children, modal }: {
children: React.ReactNode,
modal: React.ReactNode
}) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
)
}
// app/@modal/(.)login/page.tsx
'use client'
import { useRouter } from 'next/navigation'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
export default function LoginModal() {
const router = useRouter()
const handleClose = () => {
router.back()
}
return (
<Dialog open onOpenChange={handleClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>로그인</DialogTitle>
</DialogHeader>
<p>여기에 로그인 폼 들어갈 자리</p>
</DialogContent>
</Dialog>
)
}
open을 항상 true로 주고onOpenChange에서 닫기 액션(router.back()) 처리
@/components/ui/dialog는shadcn/ui설치 후 자동 생성된 컴포넌트 폴더 기준
// app/login/page.tsx
export default function LoginPage() {
return (
<div>
<h1>로그인 페이지</h1>
<p>전체 페이지용 로그인 화면</p>
</div>
)
}
/에서 "로그인" 버튼을 클릭 → /login으로 이동@modal/(.)login/page.tsx가 렌더링됨 (모달처럼 보임)Dialog의 닫기 액션 → router.back() 호출/login으로 진입하면 전체 페이지가 보임이 방식으로 구현하면: