プロトタイプをたくさん作る事になりついに Next.js に入信しました。
今回 user id ごとに表示を切り替える Dynamic Routing をしているページで user id が無効な時 404 にリダイレクトさせようとして次のようなコードを作成しました。
/pages/posts/[uid].tsx
import { useRouter } from 'next/router'; export default function Post() { const router = useRouter(); const { uid } = router.query; const posts: null | PostData[] = getPostListByUserId(uid); if (!posts) { router.push('/404'); return null; } return <DisplayUserPosts posts={posts} />; }
cf. Routing: Dynamic Routes | Next.js
このコードを npm run dev
で確認してみた所、 user id が正しい場合でも無効な場合でも次のようなエラーが表示されてしまいました。
No router instance found. you should only use "next/router" inside the client side of your app.
初回ロード時に query の uid が undefined になっている
export default function Post() { const router = useRouter(); const { uid } = router.query; console.log({ router, uid }) // => { router: {...}, uid: undefined } // => { router: {...}, uid: 'XXXX' } }
console.log
を追加して確認してみた所、コンポーネントがロードされた直後 router
オブジェクトは存在していたけど、router.query
で取得している uid
が undefined
になっていました。
初回の出力が Next がサーバーサイドで実行しているものなのかどうか判断はついてないのですが、 useRouter
は hooks で router
オブジェクトを取ってくれば直ぐに使える訳ではないようです。初回 router.query
の値が undefined
になるのと同様のタイミングで router.push
メソッドを呼ぼうとしても準備ができておらずエラーは発生したものと思われます。
router.isReady を使う
Add a `ready: boolean` to `Router` returned by `useRouter` · Issue #8259 · vercel/next.js · GitHub
ここで同じような問題が議論されていて、isReady
というプロパティが追加されたっぽい。
/pages/posts/[uid].tsx
import { useRouter } from 'next/router'; export default function Post() { const router = useRouter(); const isReady = router.isReady; if (!isReady) { return <Loading />; } const { uid } = router.query; const posts: null | PostData[] = getPostListByUserId(uid); if (!posts) { router.push('/404'); return null; } return <DisplayUserPosts posts={posts} />; }
これでエラー無く router の準備がまだな時は <Loading />
が表示され、 user id が無効な時に /404
にリダイレクトされるようになりました!
router.isReady は useEffect の中で使わなければならない
これでヨシ!と思ったのですが、公式ドキュメントには下記のように書かれていました。
isReady
:boolean
- Whether the router fields are updated client-side and ready for use. Should only be used inside ofuseEffect
methods and not for conditionally rendering on the server.
cf. next/router | Next.js
isReady
はクライアントサイドで router
が使用できる状態になっているかを表すが、 useEffect
内では使えるが、サーバーサイドの条件付きレンダリングでは使用できない。トノコト。
SSR(?) では利用できず、useEffect
がクライアントサイドでだけ実行されるので useEffect
無いだけで使ってくれということっぽい。恐らくサーバーサイドでは window オブジェクトが無いから isReady が正しく動作しないってことなのかなと想像。 (Next.js まだ始めたばかりで深く理解できてないので想像で…)
SSR 利用してなければ上記な書き方でも問題なさそうですが、公式ドキュメントに useEffect
内で使うように書かれているのでコードを書き直します。
/pages/posts/[uid].tsx
import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; export default function Post() { const [loading, setLoading] = useState<boolean>(false); const [posts, setPosts] = useState<null | PostData[]>(null); const { query, isReady } = useRouter(); const { uid } = query; // ⚠ ここに if (!loading) { return <Loading /> } を書くと useEffect が永遠に実行されなくなる useEffect(() => { if (isReady) { setPosts(getPostListByUserId(uid)); setLoading(true); } }, [isReady, uid]); if (!loading) { return <Loading />; } if (!posts) { router.push('/404'); return null; } return <DisplayUserPosts posts={posts} />; }
loading
と posts
と state が増えてしまいましたが、これなら useEffect
内で router.isReady
の完了を正しく確認できるので良さそうです。
所管
Next.js 始めたばかりですが、色々とサクット実装できてるしそのまま vercel にデプロイもできるしスゴイ〜と感じています。
その反面、どれは非同期的なの?とか CSR, SSR, SSG とか色々あってどういう時にどの書き方だとどの処理が行われるの?とか理解しきれてない部分が多く。今回の useRouter
のように画面で見てるのに何で you should only use "next/router" inside the client side で使ってというエラーが出てるの?とハマってしまうことも多いです。
もう少し使い込んで慣れていきたいと思いました。おわり
[参考]
- Next.jsでリダイレクトを行う方法をまとめてみた
- Routing: Dynamic Routes | Next.js
- How to redirect to home page instead of 404 page? · Discussion #16749 · vercel/next.js · GitHub
- next.js - useRouter/withRouter receive undefined on query in first render - Stack Overflow
- Add a `ready: boolean` to `Router` returned by `useRouter` · Issue #8259 · vercel/next.js · GitHub
- `router.query` returns undefined parameter on first render in Next.js · Discussion #11484 · vercel/next.js · GitHub
- next/router | Next.js
- router.queryがundefinedになる - rmaruon
- Next.jsのrouter周りのバグ