useRouter の import 元を間違えると "NextRouter was not mounted" で詰まる話
Next.js の App Router でページ遷移を実装しようとして、ネットの古い記事や AI が出したサンプルをそのまま使うと、次のエラーに出会うことがあります。
Error: NextRouter was not mounted.
原因は、useRouter の import 元を間違えている ことです。今回はこの詰まりを実例で解説します。
Before: 動かないコード
"use client";
import { useRouter } from "next/router"; // ← Pages Router 用
import { useState } from "react";
export default function LoginForm() {
const router = useRouter();
const [email, setEmail] = useState("");
const handleSubmit = async () => {
// ログイン処理
router.push("/dashboard");
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">ログイン</button>
</form>
);
}
一見、何も間違っていないように見えます。TypeScript のエラーも出ません。ですが、ブラウザで実際にこのコンポーネントを描画すると、コンソールに NextRouter was not mounted というエラーが出て処理が止まります。
原因: 2つの useRouter は別物
Next.js には、ルーティング方式ごとに 2つの useRouter が存在します。
| import元 | 対象 | 提供する情報 |
|---|---|---|
next/router |
Pages Router(pages/ ディレクトリ) |
router.query / router.pathname / router.asPath など |
next/navigation |
App Router(app/ ディレクトリ) |
router.push() / router.back() など(ページ情報は別フックで取得) |
next/router の useRouter は、内部で NextRouter という React Context を参照しています。この Context は Pages Router が動いている時にだけ用意されるもので、App Router のプロジェクトにはそもそも存在しません。だからこそ「実行はできるがコンテキストが無い=mount されていない」というエラーになります。
AI にコードを書いてもらう時、学習データに古い Pages Router 時代の記事が多く含まれているとみられ、next/router からの import を提案してくることがあります。エラーメッセージを読んだだけでは「router が使えない」ようにしか見えないので、原因が import 元だと気づきにくいのが厄介な点です。
After: 正しいコード
"use client";
import { useRouter } from "next/navigation"; // ← App Router 用
import { useState } from "react";
export default function LoginForm() {
const router = useRouter();
const [email, setEmail] = useState("");
const handleSubmit = async () => {
// ログイン処理
router.push("/dashboard");
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">ログイン</button>
</form>
);
}
import 元を next/navigation に変えるだけで直ります。ただし、ここで注意が必要なのは、next/navigation の useRouter() は router.push() や router.back() などの「移動」機能しか持っていない点です。
Pages Router 時代に router.query や router.pathname を使っていた場合は、それぞれ以下のフックに置き換える必要があります。
import { useSearchParams, usePathname, useParams } from "next/navigation";
const searchParams = useSearchParams(); // ?id=1 のようなクエリ文字列
const pathname = usePathname(); // 現在のパス(/dashboard など)
const params = useParams(); // 動的ルート [id] の値
AIへの確認の仕方
AIが出したコードに useRouter が出てきたら、次のように確認すると事故を防げます。
このコンポーネントは app/ ディレクトリ配下(App Router)です。
useRouterはnext/navigationからimportされていますか?
router.query や router.pathname を使っていたら、
next/navigationの対応するフックへの置き換えも一緒に教えてください。
「動かない」と結果だけ伝えるより、確認してほしい観点をこちらから渡した方が、AIも的確に直してくれることが多いです。
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ Qiita 読者の方には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。