2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

useRouter の import 元を間違えると "NextRouter was not mounted" で詰まる話

2
Posted at

useRouter の import 元を間違えると "NextRouter was not mounted" で詰まる話

Next.js の App Router でページ遷移を実装しようとして、ネットの古い記事や AI が出したサンプルをそのまま使うと、次のエラーに出会うことがあります。

Error: NextRouter was not mounted.

原因は、useRouterimport 元を間違えている ことです。今回はこの詰まりを実例で解説します。

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/routeruseRouter は、内部で 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/navigationuseRouter()router.push()router.back() などの「移動」機能しか持っていない点です。

Pages Router 時代に router.queryrouter.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)の設計までセットで含みます。

※ Qiita 読者の方には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?