Next.jsのappディレクトリで発生する NextRouter was not mounted エラーの解決方法

# はじめに
職務経歴書を登録できる簡単なアプリを開発していた際に、リダイレクト処理の実装中に `NextRouter was not mounted` エラーが発生しました。本記事では、このエラーの発生状況とその解決方法について説明します。

# 問題
以下のようなフォームを作成していました。

```tsx
"use client";
import React, { useState } from "react";
import { Career, Project } from "../types/career";
import "./customStyles.css"; // カスタムCSSファイルをインポート
import { addDoc, collection } from "firebase/firestore";
import { db } from "@/firebase/config";
import { useRouter } from "next/router";

const CareerForm = () => {
  const router = useRouter();

  const [company, setCompany] = useState("");
  const [position, setPosition] = useState("");
  const [startDate, setStartDate] = useState("");
  const [endDate, setEndDate] = useState("");
  const [isCurrent, setIsCurrent] = useState(false);
  const [projects, setProjects] = useState<Project[]>(
    [
      { title: '', description: '' }
    ]
  )

  // 入力したデータをFirestoreに送信
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const careerData: Career = {
      company,
      position,
      startDate,
      endDate: isCurrent ? "" : endDate,
      isCurrent,
      projects,
    };

    try {
      await addDoc(collection(db, "careers"), careerData);
      // フォームのリセットや成功メッセージの表示
      setCompany("");
      setPosition("");
      setStartDate("");
      setEndDate("");
      setIsCurrent(false);
      setProjects([{ title: "", description: "" }]);
      alert("経歴が保存されました");
      router.push("/careers");
    } catch (error) {
      console.error("Error adding document: ", error);
    }
  };


  return (
    <div className="container">
      <h1 className="heading">経歴の作成</h1>
      <div className="form-container">
        <form onSubmit={handleSubmit}>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2">
              会社名
            </label>
            <input
              type="text"
              value={company}
              onChange={(e) => setCompany(e.target.value)}
              className="input"
            />
          </div>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2">
              肩書・ポジション
            </label>
            <input
              type="text"
              value={position}
              onChange={(e) => setPosition(e.target.value)}
              className="input"
            />
          </div>
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2">
              在籍期間
            </label>
            <div className="flex space-x-4">
              <input
                type="month"
                value={startDate}
                onChange={(e) => setStartDate(e.target.value)}
                className="input"
              />
              <input
                type="month"
                value={endDate}
                onChange={(e) => setEndDate(e.target.value)}
                className="input"
              />
            </div>
            <div className="mt-2">
              <label className="inline-flex items-center">
                <input type="checkbox" className="form-checkbox" />
                <span className="ml-2 text-gray-700">現在在籍している</span>
              </label>
            </div>
          </div>
          <div className="mb-6">
            <label className="block text-gray-700 text-sm font-bold mb-4">
              主なプロジェクト・業務内容
            </label>
            {projects.map((project, index) => (
              <div key={index} className="border-t border-b py-6">
                <input
                  type="text"
                  name="title"
                  value={project.title}
                  onChange={(e) => handleProjectChange(index, e)}
                  className="input mb-8"
                  placeholder="見出しとなるタイトルを入力してください"
                />
                <textarea
                  name="description"
                  value={project.description}
                  onChange={(e) => handleProjectChange(index, e)}
                  className="input"
                  placeholder="業務内容を入力してください"
                  rows={4}
                ></textarea>
                <div className="flex justify-end">
                  <button
                    type="button"
                    onClick={() => handleRemoveProject(index)}
                    className="remove-button mt-4"
                  >
                    削除
                  </button>
                </div>
              </div>
            ))}
            <div className="my-8 flex justify-center">
              <button
                type="button"
                onClick={handleAddProject}
                className="button"
              >
                主なプロジェクト・業務内容を追加する
              </button>
            </div>
          </div>
          <div className="flex items-center justify-center">
            <button type="submit" className="submit-button mr-8">
              作成する
            </button>
            <button type="button" className="cancel-button">
              キャンセル
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default CareerForm;

```

Firestore にデータを送信後、特定のページにリダイレクトする機能を実装していました。具体的には、フォームの送信後に `router.push("/careers")` でリダイレクトを試みると、以下のエラーが発生しました。


![スクリーンショット 2024-07-08 4.55.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536873/9c1ebe44-dc82-e72b-431f-de61a99cdb0a.png)


# 解決方法
解決方法として、`next/router` を `next/navigation` に置き換えることが有効でした。以下のようにインポート文を変更します。

```tsx
- import { useRouter } from "next/router";
+ import { useRouter } from "next/navigation";
```

# 原因
原因は、**appディレクトリ内で`next/router`のuseRouterフックを使っていたから**です。

Next.jsのappディレクトリ（App Router）とpagesディレクトリ（Pages Router）では、useRouterフックの動作が異なります。appディレクトリ内では、 `next/router` ではなく `next/navigation` のuseRouterフックを使う必要があります。もし、app ディレクトリ内で `next/router` のuseRouterフックを使おうとすると、動作が異なるためにエラーが発生します。

### 参考
https://nextjs.org/docs/messages/next-router-not-mounted

# おわりに
本記事では、職務経歴書を登録するアプリ開発中に発生した `NextRouter was not mounted` エラーの原因と解決方法について説明しました。エラーの原因を理解し、適切な修正を加えることで、正常にリダイレクト処理を行うことができるようになります。同様のエラーに直面した際には、ぜひ参考にしてください。