Lento con forza

大学生気分のIT系エンジニアが色々書いてく何か。ブログ名決めました。

Next.jsでTwitterライクにモーダルにURLを与える

Next.jsはpages以下に置いたファイルに応じてURLが決定します。一方、あるコレクションページのコンテンツをモーダルで表示するが、そのモーダルにURLを与えたいケースがあります。

例えば昔のTwitterのWebはそのようになっていて*1、タイムライン上のツイートをタップすると、モーダルでツイートが開き、URLがツイート単体を示すものに変更されていました。そのURLに直接遷移すると、今度はモーダルではなく、直接該当ツイートの情報を読めるページが表示されていました。

同じようなルーティングをNext.jsで実装したいな、と思ったんですが、pagesとの関係がわからなくて悩んでいたところ、公式のExampleで解決策が紹介されていました。

github.com

以下のコマンドでExampleを起動すると

npx create-next-app --example with-route-as-modal with-route-as-modal-app
cd with-route-as-modal-app
npm run dev

トップページにはモーダルを表示するリンクが置かれていて f:id:kouki_dan:20210602233107p:plain

リンクをクリックすると、モーダルが表示されURLが変更されます。 f:id:kouki_dan:20210602233124p:plain

URLに直接アクセスすると、モーダルに表示されていたコンテンツが表示されます。 f:id:kouki_dan:20210602233148p:plain

このように、まさに実装したいことがExampleとしてありました。Next.jsは大量のExampleが公式リポジトリにあるので便利ですね。


せっかくなので実装を見ていきます。hrefとasを使って、モーダル内の遷移はqueryを使い、見た目上のURLはasで pages/post/[postId].jsx のファイルを参照するようにしています。

https://github.com/vercel/next.js/blob/d27cbf00a9325c76cc7696c3cc06fd5251e4b58b/examples/with-route-as-modal/components/Grid.js#L12-L14

<Link key={index} href={`/?postId=${id}`} as={`/post/${id}`}>
  <a className={styles.postCard}>{id}</a>
</Link>

モーダルの表示には router.query.postId を使用して表示します。queryを使うのでNext.jsのルーティングが行われなく、モーダルとして表示できます。 https://github.com/vercel/next.js/blob/d27cbf00a9325c76cc7696c3cc06fd5251e4b58b/examples/with-route-as-modal/pages/index.js#L13-L19

<Modal
  isOpen={!!router.query.postId}
  onRequestClose={() => router.push('/')}
  contentLabel="Post modal"
>
  <Post id={router.query.postId} pathname={router.pathname} />
</Modal>

URLに直接アクセスした時はNext.jsのpages以下のルーティングがそのまま使われます。

https://github.com/vercel/next.js/blob/d27cbf00a9325c76cc7696c3cc06fd5251e4b58b/examples/with-route-as-modal/pages/post/%5BpostId%5D.js

const PostPage = () => {
  const router = useRouter()
  const { postId } = router.query

  return <Post id={postId} pathname={router.pathname} />
}
export default PostPage

Modalとpagesで共通のコンポーネントを表示しているため、同じコンテンツを表示できます。モーダル表示の時はqueryで表現するのが上手いですね。

hrefとasはrouter.pushでも使えるので、Linkが使えないケースでも、こちらを使用すると同じ挙動を実現できました。

https://nextjs.org/docs/api-reference/next/router#routerpush

router.push(`/?postId=${id}`, `/post/${id}`)

*1:今もなってると思ってたけど今はモーダルじゃない感じに変わっていた・・・。いつ変わったんだろう?