はじめに
Next.jsのDynamic Routingとは/posts/:idみたいなpathでルーティングしたい場合pages/postsディレクトリ配下に[id].tsxを置くことで実現できる機能です。
idの値を使用してデータをフェッチすると思いますが、1回目のレンダリングではidの値を取得できない問題にハマり悩んだので共有しておきます。
パラメータの取得
パスパラメータはクエリパラメータとしてページに送信され、他のクエリパラメータとマージされます。「はじめに」の例で言うと、idの値は以下のように取得できます。
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
実際に使用する場合はこんな感じになるかと思います。
import { useRouter } from 'next/router'
const Post = () => {
const ref = useRef()
const router = useRouter()
const id = router.query.id
useEffect(() => {
// ここでidの値を使用してデータをフェッチする
}, [ref]);
// fetchしたデータを表示
return <p>{hoge}</p>
}
export default Post
ここで最初のレンダリングrouter.query.idはundefinedとなります。
公式のドキュメントでは以下のように書かれています
自動静的最適化によって静的に最適化されたページは、ルートパラメータが提供されないままハイドレーションされます。
ハイドレーションの後、Next.jsはアプリケーションの更新をトリガーにして、クエリオブジェクトにルートパラメータを提供します。
なので、再レンダリング時にidを取得できるということになります。
ここでuseEffectでデータをfetchするときにidに値が入っていないのでデータを取得できないし、stateも変化しないので再レンダリングできないという問題が起きました。
解決方法
最初のレンダリングでは、router.asPathとrouter.routeは等しくなります。ですが、パスパラメータがqueryで利用可能になり、router.asPathに反映されたあとはパスは違うのでそれによって検知することができます。
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const [id, setId] = useState<number>()
// この部分を追加
useEffect(() => {
// idがqueryで利用可能になったら処理される
if (router.asPath !== router.route) {
setId(Number(router.query.id));
}
}, [router]);
// idが取得されてセットされたら処理される
useEffect(() => {
if (id) {
// ここでidの値を使用してデータをフェッチする
}
}, [id]);
// fetchしたデータを表示
return <p>{hoge}</p>
}
export default Post
まとめ
- Next.jsのDynamic Routingは最初のレンダリングでパスパラメータを取得できない
- router.asPathを使用して処理することでパスパラメータがqueryで利用可能になったときのイベントを発火させることができる
もし、もっと良い実装方法があればぜひコメントで教えてください