[Next.js] 모달 열면서 URL 변경하기 (Parallel Routes, Intercepting Routes)

문지은·2024년 3월 1일
1

Next.js - App Router

목록 보기
20/20

구현 내용

  • 모달 열고 닫으면서 URL 변경하기
  • 모달을 열때 뒤 컨텐츠는 유지되어야 하며, 새로고침 또는 URL 로 접속시에는 모달 컨텐츠만 보여야 한다.

위 기능을 구현하기 위한 Next.js 의 Parallel Routes, Intercepting Routes에 대해 알아보자.

Parallel Routes

  • 병렬 라우팅(Parallel Routing)은 동시에 또는 조건에 따라 동일한 레이아웃에서 하나 이상의 페이지를 렌더링할 수 있게 해준다.
  • 대시보드나 소셜 사이트의 피드와 같은 매우 동적인 앱 섹션에서는 병렬 라우팅을 사용하여 복잡한 라우팅 패턴을 구현할 수 있다.
  • 예를 들어, @team 페이지와 @analytics 페이지를 동시에 렌더링 할 수 있다.

  • 병렬 라우트는 명명된 슬롯(named slots)을 사용하여 생성할 수 있다.
    • 슬롯은 @foldername 컨벤션으로 정의되며, 같은 수준의 레이아웃 속성으로 전달된다.
    • 슬롯은 라우트 세그먼트가 아니며, URL 구조에 영향을 미치지 않는다.
      • 예를 들어, /dashboard/@analytics/views의 경우 URL은 /dashboard/views 이다.

  • 위의 폴더 구조는 app/layout.js 컴포넌트가 @analytics@team 슬롯 props를 받고, 이를 children 속성과 함께 병렬로 렌더링할 수 있다.
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

default.js

  • 초기 로드 또는 새로고침 중에 일치하지 않는 슬롯에 대해 렌더링할 default.js 파일을 정의할 수 있다.
  • 다음과 같은 폴더구조를 보면, @team 슬롯에는 settings 디렉토리가 없지만, @analytics 에는 없다.

  • /dashboard/settings로 이동할 때 @team 슬롯은 @analytics 슬롯의 현재 활성 페이지를 유지한 채로 /settings 페이지를 렌더링한다.
  • 새로 고침할 경우, Next.js는 @analytics에 대한 default.js를 렌더링합니다. default.js가 없으면 404가 대신 렌더링된다.
  • 또한, children은 암묵적인 슬롯이기 때문에 부모 페이지의 활성 상태를 Next.js가 복구할 수 없을 때 children에 대한 대체를 렌더링하기 위해 default.js 파일을 생성해야 한다.

Conditional Routes

  • 사용자 역할과 같은 특정 조건에 따라 조건부로 경로를 렌더링할 수 있다.
    • 예를 들어, /admin 또는 /user 역할에 대해 다른 대시보드 페이지를 렌더링 할 수 있다.

import { checkUserRole } from '@/lib/auth'
 
export default function Layout({
  user,
  admin,
}: {
  user: React.ReactNode
  admin: React.ReactNode
}) {
  const role = checkUserRole()
  return <>{role === 'admin' ? admin : user}</>
}

Intercepting Routes

  • Intercepting routes 는 현재 레이아웃 내에서 라우트를 로드하면서 현재 페이지의 컨텍스트를 유지하게 해준다.
  • 이런 라우팅 패러다임은 특정 경로를 가로채서 다른 경로를 표시하고 싶을 때 유용하다.
  • 예를 들어, feed 내부에서 사진을 클릭할 때, feed 위에 모달이 나타나야 한다.
    • 이 경우, Next.js 는 /feed 경로를 가로채고 URL을 /photo/123 로 보여준다.

  • 그러나, 공유 가능한 URL을 클릭하거나 페이지를 새로고침하는 경우와 같이 사진에 직접 이동할 때는 모달 대신 전체 사진 페이지가 렌더링 되어야 한다.

  • Intercepting routes 는 (..) 구문을 사용하여 정의할 수 있다.
    • (.)는 동일한 수준의 세그먼트와 일치
    • (..)는 한 수준 위의 세그먼트와 일치
    • (..)(..)는 두 수준 위의 세그먼트와 일치
    • (...)는 루트 앱 디렉토리부터의 세그먼트와 일치
  • 예를 들어, (..)photo 디렉토리를 생성함으로써 feed 세그먼트 내부에서 photo 세그먼트를 가로챌 수 있다.

Parallel Routes + Intercepting Routes

  • Parallel Routes 와 Intercepting routes 를 같이 사용하면 모달을 만드는 데 사용할 수 있다.
  • 이를 통해 다음과 같은 작업을 수행할 수 있다.
    • URL을 통해 모달 내용을 공유할 수 있게 함
    • 페이지를 새로고침할 때 모달을 닫지 않고 컨텍스트를 보존함
    • 이전 경로로 이동하는 대신 뒤로 가기로 모달을 닫을 수 있음
    • 앞으로 가기로 모달을 다시 열 수 있음
  • 다음과 같은 구조를 고려해보자.
    • 사용자는 클라이언트 측에서 로그인 모달을 열거나 별도의 /login 페이지에 접근할 수 있다.

  • 먼저 로그인 페이지를 렌더링하는 /login 라우트를 만들어야 한다.

app/login/page.tsx

import { Login } from '@/app/ui/login'
 
export default function Page() {
  return <Login />
}
  • 그런 다음 @auth 슬롯 내에서는 default.js 파일을 추가하여 모달이 활성 상태가 아닐 때 렌더링되지 않도록 한다.

app/@auth/default.tsx

export default function Default() {
  return null
}
  • @auth 슬롯 내에서 /(.)login 폴더를 업데이트하여 /login 라우트를 가로채도록 하고, 그런 다음 <Modal> 컴포넌트와 해당 자식을 /(.)login/page.tsx 파일로 가져온다.

app/@auth/(.)login/page.tsx

import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
 
export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

모달 열기

  • 이제 Next.js 라우터를 활용하여 모달을 열고 닫을 수 있다.
  • 이렇게 하면 모달이 열릴 때 URL이 올바르게 업데이트되고 뒤로 가기 및 앞으로 가기를 할 때도 올바르게 작동한다.
    • 사용자가 <Link>를 클릭하면 /login 페이지로 이동하는 대신 모달이 열린다.
    • 그러나 새로 고침이나 초기 로드 시 /login으로 이동하면 사용자는 주 로그인 페이지로 이동한다.

app/layout.tsx

import Link from 'next/link'
 
export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">Open modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

모달 닫기

  • 모달을 닫으려면 router.back()을 호출하거나 Link 컴포넌트를 사용할 수 있다.

app/ui/modal.tsx

'use client'
 
import { useRouter } from 'next/navigation'
 
export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()
 
  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Close modal
      </button>
      <div>{children}</div>
    </>
  )
}
  • 페이지에서 @auth 슬롯을 더 이상 렌더링하지 않아야 하는 경우 Link 컴포넌트를 사용하여 페이지를 떠날 때 모달을 닫을 수 있다.
// app/ui/modal.tsx

import Link from 'next/link'
 
export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">Close modal</Link>
      <div>{children}</div>
    </>
  )
}
profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글