next.js 에서 라우트로 관리되는 모달 구현하기 (route as modal)
언틸의 피드에서는 유저들이 작성한 아티클을 모달의 형태로 볼 수 있는데요.
이런 모달 형태는 유저들의 글을 빠르게 전환하며 볼 수 있다는 장점을 가지고 있어요.
하지만 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
이를 통해 유저는 새로고침을 하더라도 게시글 상세페이지를 유지할 수 있고, 뒤로가기를 통해서도 모달을 닫을 수 있게 되었습니다. 👍