• Feed
  • Explore
  • Ranking
/
/
    ⚛️ React

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

    Next.jsReact
    m
    morethanmin
    2024.09.13
    ·
    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.Thumbnail 과 Article.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

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







    - 컬렉션 아티클