Next.jsはpages以下に置いたファイルに応じてURLが決定します。一方、あるコレクションページのコンテンツをモーダルで表示するが、そのモーダルにURLを与えたいケースがあります。
例えば昔のTwitterのWebはそのようになっていて*1、タイムライン上のツイートをタップすると、モーダルでツイートが開き、URLがツイート単体を示すものに変更されていました。そのURLに直接遷移すると、今度はモーダルではなく、直接該当ツイートの情報を読めるページが表示されていました。
同じようなルーティングをNext.jsで実装したいな、と思ったんですが、pagesとの関係がわからなくて悩んでいたところ、公式のExampleで解決策が紹介されていました。
以下のコマンドでExampleを起動すると
npx create-next-app --example with-route-as-modal with-route-as-modal-app cd with-route-as-modal-app npm run dev
トップページにはモーダルを表示するリンクが置かれていて
リンクをクリックすると、モーダルが表示されURLが変更されます。
URLに直接アクセスすると、モーダルに表示されていたコンテンツが表示されます。
このように、まさに実装したいことがExampleとしてありました。Next.jsは大量のExampleが公式リポジトリにあるので便利ですね。
せっかくなので実装を見ていきます。hrefとasを使って、モーダル内の遷移はqueryを使い、見た目上のURLはasで pages/post/[postId].jsx
のファイルを参照するようにしています。
<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以下のルーティングがそのまま使われます。
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:今もなってると思ってたけど今はモーダルじゃない感じに変わっていた・・・。いつ変わったんだろう?