avatar
morethan-log

next.js 에서 라우트로 관리되는 모달 구현하기 (route as modal)

Next.jsReact
3 months ago
·
4 min read

until-1342

언틸의 피드에서는 유저들이 작성한 아티클을 모달의 형태로 볼 수 있는데요.

이런 모달 형태는 유저들의 글을 빠르게 전환하며 볼 수 있다는 장점을 가지고 있어요.

하지만 React State로 관리되기 때문에 몇가지 단점이 존재했어요. 예를 들어 새로고침시에 보고 있던 게시글이 사라지거나, 브라우저의 history stack을 쌓지 않기 때문에 뒤로가기(history.back)로 닫을 수 없었어요.

이런 경험은 데스크탑에서는 그렇다 쳐도 모달이 풀페이지로 나타나는 모바일에서는 뒤로가기를 했을 때 아예 페이지가 닫혀버리는 등 유저 경험이 좋지 않았어요.

때문에 Route로 동작하는 Modal로 변경하기로 했어요. 어떤 방식으로 구현했는지 간단하게 공유해볼게요.

ArticleCard

우선 ArticleCard는 기존에는 onClick handler를 통해 isOpen state를 변경해주고 있었는데요. 이것을 next/Link 컴포넌트로 변경해주었어요.

import { LinkProps } from 'next/link'
import { useRouter } from 'next/router'
import * as _ArticleCard from 'src/components/ArticleCard'
import { ArticleItem } from 'src/types/article'

type Props = {
  data: ArticleItem
}

export const ArticleCard = ({ data }: Props) => {
  const router = useRouter()

  const href: LinkProps['href'] = {
    query: {
      ...router.query,
      'article-id': data.articleId,
    },
  }

  const as = `@${data.blog.username}/${data.urlSlug}`

  return (
    <_ArticleCard.Root data={data}>
      <_ArticleCard.Thumbnail shallow href={href} as={as} />
      <_ArticleCard.AuthorInfo showGroup />
      <_ArticleCard.ArticleInfo shallow href={href} as={as} />
    </_ArticleCard.Root>
  )
}

Article.ThumbnailArticle.ArticleInfo가 Link의 props을 대신 받아오고 있습니다.

이제 아티클 카드를 클릭하면 next/router는 href에 의해서 until.blog?article-id=219 로 이동하게 되요.

또한 as prop을 통해 article-hash-id를 브라우저 검색창에 노출하지 않고 실제 게시글 상세 route인 것처럼 변경해주었어요.

ArticleDetailDialog

이제 서치파라미터에 등록된 article-id를 읽어와서 모달을 띄워주면 돼요.

import { useRouter } from 'next/router'
import { Dialog, useCurrentSize } from 'ui'
import Content from './Content'
import { boxStyle } from './styles'

const ArticleDetailDialog = () => {
  const router = useRouter()
  const currentSize = useCurrentSize()

  const articleId =
    typeof router.query['article-id'] === 'string' &&
    router.query['article-id'] !== ''
      ? Number(router.query['article-id'])
      : null

  const handleClose = () => {
    const query = { ...router.query }
    delete query['article-id']

    router.push(
      {
        query,
      },
      undefined,
      { shallow: true, scroll: false },
    )
  }

  return (
    <Dialog
      open={!!articleId}
      onClose={handleClose}
      align="top"
      alignOffset={currentSize === 'desktop' ? 60 : 40}
      css={{
        maxWidth: '920px',
        width: '100%',
      }}
    >
      <div css={boxStyle}>
        <Content articleId={articleId} onClose={handleClose} />
      </div>
    </Dialog>
  )
}

export default ArticleDetailDialog

이를 통해 유저는 새로고침을 하더라도 게시글 상세페이지를 유지할 수 있고, 뒤로가기를 통해서도 모달을 닫을 수 있게 되었습니다. 👍


- 컬렉션 아티클






몰댄민입니다