【完全版】App Routerで最初に知っておくとよさそうな基礎を全部まとめてみた

Featured image of the post

はじめに

Next.js13で登場した新しいルーティングの機能「App Router」についてまとめる😊

この記事で伝えたいこと
  • App Routerとは何か
  • 基本的な考え方
  • 新しい機能

結論

✅App Routerは1フォルダ = 1ページのルーティング。

✅フォルダの中にpage.jsを作るとページができる。

✅ルーティング以外にも追加機能、廃止機能がある。

App Routerを学ぶ際の心構え

説明を読む前にApp Routerを「どんなメンタルで学ぶといいか」個人的に感じたことをお伝えする😊

学ぶ前の勝手なイメージ

App Routerという名前を聞いて

  • ルーティングの仕方が変わったのかな〜💭
  • フォルダ構成がちょっと変わるくらいかな〜💭

軽く見ていた…💦

学び始めてからのギャップ

しかし変更点はルーティングの方法だけではなかった😢

追加機能、廃止機能がありそれなりに学習コストがかかる💦

最初からそのつもりで学習するとよさそう!

💡
個人的にはPages Routerよりも少し難しい印象…

【補足】公式ドキュメントも2個になった

App Router用のページがちょっと増えたとかではなく、App Router用のドキュメントが丸々1個増えた。

それくらいApp Routerは大規模💦

左上でApp RouterとPages Routerのドキュメントを切り替えられる。

Image in a image block
Image in a image block

【前提】App Routerって何?

App Routerの仕組みを理解する前に、まずは全体像を理解する。

App Routerとは

✅新しいルーティングのこと。

1フォルダ = 1ページのルーティングになった!

ルーティング以外の変更点もある

他にも追加機能や廃止機能がある。

後で解説するので、一旦は「ルーティング以外にも変わった部分があるのね💭」くらいでOK!

App Routerの導入方法

✅Next.js13以降は何もしなくてもApp Routerが使える。

【補足】Next.js12以前からアップデートする場合

以下の2段階でApp Routerに移行できる。

1️⃣App Routerに以降する前に、Next.js13で書き方が変わった部分を対応する。

2️⃣App Routerに移行する。

Pages RouterとApp Routerは併用できるので1ページずつゆっくり移行してもOK!)

App Routerのルーティング

Pages Routerとの比較

フォルダ構成がそのままURLになるのは変わらない😊

主な変更点をざっくり比較する。

Pages Router App Router
1ファイル = 1ページ 1フォルダ = 1ページ
ファイル名がURLになる フォルダ名がURLになる
ファイル名は何でもOK ファイル名が決まっている
pagesフォルダを使う appフォルダを使う

App Routerでページを作る方法

「フォルダ」と「page.js」で1ページ作れる😊

例:2ページだけのフォルダ構成

🚫Pages Routerの場合

pages/
├── index.js        --> /
└── about.js        --> /about

✅App Routerの場合

app/
├── page.js           --> /
└── about/
    └── page.js       --> /about

→ページの数だけpage.jsが必要。

特別な意味を持つファイルが登場した

appフォルダ内では「page.js」など特別な意味を持つファイルがある。

ファイル名の早見表

各フォルダの中で以下のファイルは特別な意味を持つ。

app/
├── page.js           --> ページの中身。これが一番よく使うファイル⭐️
├── route.js          --> APIの定義(page.jsと共存不可)
├── layout.js         --> 共通の見た目
├── loading.js        --> 読み込み中の画面
├── error.js          --> エラー時の画面
├── global-error.js   --> グローバルエラー画面
├── templete.js       --> 共通の見た目
├── default.js        --> デフォルトの画面
└── not-found.js      --> notFound関数がスローされたときの画面

💡
代表的なファイルを1つずつ詳しく見ていく!

page.js

✅ページの中身を書くところ。ページを作るとき必須。

(逆に言うと、APIを作るときは不要。)

app/
└── page.js           --> /(トップページ)

例:「/about」ページ

「aboutフォルダ」と「page.js」を作るだけでOK😊

フォルダ構成

app/
└── about/
    └── page.js       --> /about

app/about/page.js

// Aboutと表示するだけのページ
const Page = () => {
  return <div>About</div>;
};

export default Page;

URL「/about」にアクセスする。

Image in a image block

route.js

✅APIの定義を書くところ。APIエンドポイントを作るときに必須。

(逆に言うと、ページを作るときは不要。)

例1:GETメソッド(ユーザーリストを取得するAPI)

route.jsにGETメソッドを書くだけでOK😊

フォルダ構成

app/
└── users/
    └── route.js      --> /users でアクセスできるAPIエンドポイント

app/users/route.js

import { NextResponse } from "next/server";

// ユーザーリストを取得するAPI
export function GET() {
  return NextResponse.json([
    {
      id: 1,
      name: "山田 太郎",
    },
    {
      id: 2,
      name: "佐藤 次郎",
    },
  ]);
}

URL「/users」にアクセスする。

Image in a image block

例2:POSTメソッド(form送信された値を保存するAPI)

route.jsにPOSTメソッドを書くだけでOK😊

フォルダ構成

app/
└── users/
    └── route.js      --> /users でアクセスできるAPIエンドポイント

app/users/route.js

import { NextResponse } from "next/server";

// form送信された値を保存するAPI
export async function POST(request) {
	// 送信されたデータを受け取る
  const res = await request.json();

	// DBに保存する処理など
	// ...
}
【動作確認用】POST送信するページ

app/user-register/page.js

"use client";

import React, { useState } from "react";

export default function Register() {
  const [username, setUsername] = useState("");

  const handleSubmit = async (event) => {
    event.preventDefault();

    try {
      const response = await fetch("/users", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({name: username}),
      });

      if (!response.ok) {
        throw new Error("Response is not OK");
      }

      const data = await response.json();
      console.log(data);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 text-3xl">
      <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
        ユーザー名:
      </label>
      <input
        className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline mb-4"
        id="username"
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <button
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
        type="submit"
      >
        送信
      </button>
    </form>
  );
}

【補足】requestを受け取る

GETメソッドに引数をつけるだけでリクエスト情報(NextRequest型のデータ)を受け取ることができる。

例:リクエスト情報からクエリパラメータを受け取る

/app/users/route.js

import { NextResponse } from "next/server";

export function GET(request) {
	// リクエスト情報からクエリパラメータを取得
  const { searchParams } = new URL(request.url);

	// クエリパラメータ「name」の値を取得
  const name = searchParams.get("name");

	// 「http://localhost:3000/users?name=佐藤」のとき「佐藤」が表示される
  console.log(name);

	// 省略
}

layout.js

✅共通の見た目、処理、metaタグを書く。

「layout.jsがあるフォルダ以下」すべてに共通の見た目が適用される。

例1:全ページで共通のレイアウト

「/app/layout.js」は全ページ共通の見た目を定義できる😊

主にmetaタグ、<html>や<body>といった全体のレイアウトを定義するのに使われる。

フォルダ構成

app/
├── page.js
└── layout.js         --> 全ページで共通の見た目

app/layout.js

// 共通のCSS
import { Inter } from 'next/font/google'. // (*1)
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

// 共通のmetaタグ
export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

// 共通の見た目
export default function RootLayout({ children }) { // (*2)
  return (
    <html lang="en">
      <body className={`${inter.className} text-red-500`}>{children}</body>
    </html>
  )
}

URL「/」や「/about」にアクセスする。

→全ページでlayout.jsに書いたCSSが適用される。

Image in a image block

細かいコードの解説

(*1) import { Inter } from 'next/font/google'

✅Next.jsのフォント最適化の機能。

詳細は公式ドキュメントを参照。

(*2) children

✅childrenの部分にpage.jsが埋め込まれる。

<body className={inter.className}>{children}</body>

例2:「/product」以下で共通のレイアウト

「/app/product/layout.js」を作るだけで/product以下のページで共通のレイアウトを定義できる😊

フォルダ構成

app/
├── page.js
└── products/
    ├── page.js
    ├── layout.js     -->/products」以下のページで共通の見た目
    └── ...

app/products/layout.jsx

// products以下のページは、sectionタグで囲む
export default function ProductLayout({ children }) {
  return <section className="任意のCSS">{children}</section>
}

URL「/products」や「/products/abc」にアクセスするとlayout.jsが適用される。

【補足】下位の階層にもlayout.jsがある場合はどうなる?

✅結論、両方のlayout.jsが適用される。

app/
├── page.js           --> /
├── layout.js         --> 1️⃣「/」以下で共通の見た目
└── about/
    ├── page.js       --> /about
    └── layout.js     --> 2️⃣「/about」以下で共通の見た目

1️⃣app/layout.tsx

→「/」以下で共通の見た目

→<html>や<body>といった全体のレイアウトを定義するのに使われる。

import './globals.css'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}

2️⃣app/about/layout.tsx

→「/about」以下で共通の見た目

→「/about/〇〇」ページ特有のスタイルの定義に使われる。

export default function AboutLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex justify-center items-center h-screen">{children}</div>
  );
}
💡
この<div>は親である1️⃣app/layout.tsxの{children}に埋め込まれる。

最終的に出力されるHTML

両方のlayout.tsxが適用される。

<html lang="en">
  <body className={inter.className}>
		<div className="flex justify-center items-center h-screen">page.jsの中身</div>
	</body>
</html>

【補足】下位のページでmetaタグを上書きする方法

✅各ページのpage.jsでmetaタグを上書きできる。

app/
├── page.js           --> /
├── layout.js         --> 全ページで共通の見た目
└── about/
    └── page.js       --> /about (ここでmetaタグを上書きしたい)

静的なメタデータの場合:metadataに指定するだけでOK😊

/app/about/page.js

// metaタグを上書き
export const metadata = {
  title: 'Aboutページ',
  description: 'Aboutページの説明文',
}

const Page = () => {
  return <div>About</div>;
};

export default Page;

動的なメタデータの場合:generateMetadata関数の戻り値でmetaタグが設定できる😊

/app/about/page.js

export async function generateMetadata({params}) {
	// 仮想のデータ取得処理
  const user = await getUser(params.id);
	// メタデータを返す
  return { title: user.name };
}

const Page = async ({ params }) => {
	// 省略
};

export default Page;

loading.js

サーバーでレンダリングしている間に表示される画面。

例:最小限のローディング画面

loading.jsを作るだけでローディング画面が作れる😊

フォルダ構成

app/
├── page.js           --> /
└── loading.js        --> 読み込み中の画面

app/loading.js

// ローディング中に表示する画面
export default function Loading() {
  return (
    <div className="flex justify-center items-center h-screen font-bold">
      ローディング中
    </div>
  );
}
【動作確認用】レンダリングに時間がかかるpage.js

3秒待って、ユーザー一覧データを取得するページ。

app/page.js

const Page = async () => {
    // 3秒待機
    await new Promise((resolve) => setTimeout(resolve, 3000));
    // ユーザ一覧を取得(前述のroute.jsで作ったAPIエンドポイントを叩く)
    const response = await fetch('http://localhost:3000/users');
    const users = await response.json();
    console.log(users);
    
    return (
      <div className="m-4">
        <h1 className="text-lg font-bold">ユーザ一覧</h1>
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      </div>
    );
  };
  
  export default Page;

URL「/」にアクセスする。

→ローディング画面が表示される。

Image in a image block

error.js

エラーが発生したときに表示される画面。

例:最小限のエラー画面

error.jsを作るだけでエラー時の画面が作れる😊

フォルダ構成

app/
├── page.js           --> /
└── error.js.         --> エラー時の画面

app/error.js

'use client';

// エラー時に表示する画面
const Error = ({ error }) => {
  return (
    <div>
      <p>{error.message}</p>
    </div>
  );
}

export default Error;
【動作確認用】エラーを投げるpage.js

データの取得に失敗するページ。

app/page.js

const Page = async () => {
  // ユーザ一覧を取得(存在しないURLを指定しているため失敗する)
  const response = await fetch('http://localhost:3000/hoge');
  // レスポンスがエラーの場合はエラーを投げる
  if (!response.ok) throw new Error("データの取得に失敗しました");

  const users = await response.json();
  console.log(users);

  return (
    <div>失敗するとここは表示されない。error.jsが表示される</div>
  );
};

export default Page;

URL「/」にアクセスする。

→エラー画面が表示される。

Image in a image block

【発展】再読み込みボタン

✅reset関数を実行するとコンポーネントを再読み込みできる。

app/error.js

"use client";

// ✅引数にresetを付ける
const Error = ({ error, reset }) => {
  return (
    <div>
      <p>{error.message}</p>
			// ✅クリック時にreset関数を実行する
      <button className="bg-blue-500 py-2 px-4 rounded mt-10" onClick={() => reset()}>
        再読み込み
      </button>
    </div>
  );
};

export default Error;

再読み込みボタンをクリックを作った場合の画面イメージ

Image in a image block

特別な意味を持つフォルダが登場した

appフォルダ内では[ ]で囲んだフォルダなど特別な意味を持つフォルダがある。

フォルダ名の早見表

以下のフォルダ名は特別な意味を持つ。

[〇〇]動的パス

app/
└── [○○]          --> 動的パス
    └── page.js   --> /abc, /xyz, /12345, ...

[〇〇]/[〇〇]動的パス(2階層)

app/
└── [○○]              --> 2階層の動的パス
    └── [○○]          --> 2階層の動的パス
        └── page.js   --> /abc/123, /xyz/456 ...

[[ ]]動的パス(省略可)

app/
└── [[○○]]        --> 動的パス(省略可)
    └── page.js   --> /, /abc, /xyz, /12345, ...

[...〇〇]複数階層の動的パス

app/
└── [...○○]           --> 複数階層の動的パス(何階層でもOK)
    └── page.js       --> /abc, /abc/123 ...

(〇〇)URLには影響しないフォルダになる

app/
└── (○○)               --> パスには影響しない
    ├── hoge
    │   └── page.js    --> /hoge
    └── fuga
        └── page.js    --> /fuga

@〇〇直接アクセスできないページが作れる

app/
├── layout.js         --> ここから「@任意の名前/page.js」が読み込める
└── @○○/
    └── page.js       --> 直接アクセスできないページ(読み込まれる用)

(.)〇〇直接アクセスしたときと、Linkで飛んだときで表示するページを分けられる

app/
├── (.)○○
│   └── page.js/         -->Linkで飛んだときに表示される
└── ○○
    └── page.js/         -->直接アクセスしたとき表示される

_〇〇ルーティングに含まれないフォルダが作れる

app/
└── _○○               --> ルーティングに含まれない
    └── ...
💡
1つずつ解説していく!

[〇〇]:動的パス

✅フォルダ名をapp/[○○]/page.jsにするだけで動的なパスになる。

(考え方はPages Routerと同じ)

例:/products/〇〇

app/products/[id]/page.js ファイルを作成するだけで動的パスが実現できる😊

app/
└── products/
    └── [id]
        └── page.js   --> /products/1, /products/2, ...

/products/〇〇にアクセスできる。

[〇〇]/[〇〇]:動的パス(2階層)

✅フォルダ名をapp/[○○]/[○○]/page.jsにすると2階層に対応した動的なパスになる。

例:/products/〇〇/〇〇

app/products/[id]/[userid]/page.js ファイルを作成するだけで2階層の動的パスが実現できる😊

app/
└── products/
    └── [id]
        └── [userid]
            └── page.js   --> /products/1/101, /products/1/102, ...

/products/〇〇/〇〇にアクセスできる。

[[ ]]:動的パス(省略可)

✅フォルダ名をapp/[[○○]]/page.jsにするとパスの省略が可能になる。

例:/products/〇〇

app/products/[[id]]/page.jsファイルを作成するだけで動的パスが実現できる😊

(末尾のパスを省略可能)

app/
└── products/
    └── [[id]]
        └── page.js   --> /product, /products/1, /products/2, ...

→「/products/1」 や 「/products/2」 だけでなく「/products」にもアクセス可能。

[...〇〇]:動的パス(複数階層)

✅フォルダ名をapp/[...○○]/page.jsにするだけで複数階層に対応した動的なパスになる。

→Cathc-all Routesという機能

例:/products/〇〇/〇〇/.../〇〇

app/products/[...id]/page.js ファイルを作成するだけで何階層でもOKな動的パスが実現できる😊

app/
└── products/
    └── [...id]
        └── page.js   --> /products/1, /products/2/abc, ...

/products/〇〇/〇〇/.../〇〇にアクセスできる。(何階層でもOK!)

idの部分は何でもOK。

(〇〇):URLには影響しないフォルダになる

✅フォルダ名をapp/(○○)にするとルーティングをグループ化できる。

→Route Groupsという機能。

これにより、URLは同じ階層のまま、異なるlayout.jsを適用できる。

Route Groupsがない場合の問題点

🚫通常はフォルダ名がURLに含まれてしまう。

app/
└── products/
    ├── layout.js        --> products以下でこのレイアウトを適用したい
    ├── abc/
    │   └── page.js      --> /products/abc
    └── xyz/
        └── page.js      --> /products/xyz

→当然URLにproductsが含まれてしまう。

💡
URLに/productsを含めたくない場合もある!

Route Groupsによる解決

(○○)とするだけでURLに含まないパターンを実現できる!

app/
├── page.js              --> /
└── (products)/
    ├── layout.js        --> products以下でこのレイアウトを適用したい
    ├── abc/
    │   └── page.js      --> /abc
    └── xyz/
        └── page.js      --> /xyz

→URLにproductsが含まれない!

💡
app/products/(product)グループ化するためだけのフォルダ。URLには影響しない。

layout.jsの継承を区切るのに便利!

(.)〇〇:直接アクセスしたときと、Linkで飛んだときで表示するページを分けられる

✅フォルダ名をapp/(.)○○にするとルーティングを横取りできる。

→Intercepting Routesという機能。

例:リンクをクリックしたときに横取りする

app/(.)photoを作るだけで、Linkで飛んだときのルーティングを横取りできる😊

フォルダ構成

app/
├── (.)photo
│   └── page.js/         --> /photo ※Linkで飛んだときに表示される(下記のpage.jsを表示せずに横取りする)
└── photo
    └── page.js/         --> /photo ※直接アクセスしたときに表示される

動作イメージ

直接ページを開いたときや、aタグで開いたときは通常どおりphotoページが表示される。

Image in a image block

Linkタグで遷移した場合は(.)photoのページが表示される。

(通常のphotoページが表示されずに横取りされる)

Image in a image block

動作まとめ

  • 基本的に(.)photophotoはセットで使う。
  • 通常はphotoのページが表示されるが、Linkで遷移したときは(.)photoのページが表示される。

_〇〇:ルーティングに含まれないフォルダが作れる

✅フォルダ名をapp/_○○にするとルーティングの対象外になる。

例:/components/〇〇

app/_componentsフォルダはルーティング対象外になる😊

app/
└── _components/    --> ルーティング対象外
    └── ...

→コンポーネントをまとめる用のフォルダが簡単に作れる。


App Routerによるルーティングの説明はここまで。

以降は、App Routerに関係する機能についてまとめる📝


App Routerで廃止・変更された機能

App Routerでは一部の機能の廃止され、書き方が変わる。

データの取得方法(SSR、SSGのやり方も変わる)

App Routerではfetch関数でデータを取得するようになった😊

🚫Pages Router
  • データを取得するときはgetStaticProps関数、getServerSideProps関数を使っていた。

✅App Router
  • データを取得するときはfetch関数を使う。

    (getStaticProps関数、getServerSideProps関数が廃止された。)

useRouter

App Routerではインポートの仕方が変わった。

🚫Pages Router
  • 'next/router'からインポートしていた。
  • どのコンポーネントでも使用できた。

    (そもそもサーバーコンポーネント、クライアントコンポーネントという概念がなかった)

import { useRouter } from 'next/router'

✅App Router
  • 'next/navigation'からインポートするように変わった。
  • クライアントコンポーネントでのみ使用可能になった。
'use client'
import { useRouter } from 'next/navigation'

静的HTMLの出力方法

HTMLを出力するときにnext exportが不要になった。

🚫Page Router
  • 以下のコマンドを実行する。
    next build & next export

✅App Router
  • next.config.jsにoutput: "export"を追加する。
  • 以下のコマンドを実行する。
    next build

    next exportは不要になった!

App Routerで追加された機能

App Routerではいくつか便利な機能が追加された。

サーバーコンポーネント

Reactのサーバーコンポーネントが使えるようになった!

(パフォーマンスの向上が期待できる)

🚫Pages Router
  • クライアントコンポーネントしかなかった💦

✅App Router
  • サーバーコンポーネントが使えるようになった✨

    (デフォルトはサーバーコンポーネントだが、従来どおりクライアントコンポーネントも使える。)

Server Actions

クライアント側のコードの中に、サーバー側のコードを書けるようになった!

🚫Pages Router
  • サーバー側のコードを書くにはAPIエンドポイントを作る必要があった💦

✅App Router
  • クライアント側のコードに中に直接サーバー側のコードを書くことができるようになった✨

    →Server Actions

  • 従来どおりAPIエンドポイントを作ることもできる。

フック

新しいフックが複数追加された✨

  • useSearchParams

    URLのクエリ文字列を取得する

  • usePathname

    URLのパス名を取得する

  • useParams

    URLの動的パラメータを取得する

  • useReportWebVitals

    Core Web Vitalsのデータを取得する

  • useSelectedLayoutSegments

    フック実行場所以降のURLを取得する。

  • useSelectedLayoutSegment

    フック実行場所の1階層下のURLを取得する。

fetchしたデータを自動でキャッシュしてくれる

2回目以降のfetchはキャッシュされたデータが使われるようになった!

🚫Pages Router
  • 同じfetchを何回もするとパフォーマンスが悪化してしまう💦
  • fetchを1回で済ませるために、取得したデータを親→子にpropsで受け渡す。

✅App Router
  • 同じfetchを何回してもパフォーマンスが悪化しない✨
  • 好きなところで好きなだけfetchしていいので、取得したデータを親→子にpropsで受け渡す必要がない。

【補足】他にも全部で4種類のキャッシュがある

✅4種類キャッシュがあり、適切に使い分けることでパフォーマンスが向上する。

しかし開発者はあまり意識する必要がなく、デフォルトでいい感じに4種類を使わけてくれる✨

💡

💡
公式でもこの知識は必須ではないと書かれているのでスルーしてもOK

よくある疑問

App Routerのいいところは?

✅実際に使ってみて個人的にいいと感じたのは以下の2点。

ファイルを管理しやすい

1ページ = 1フォルダになったことで、ページごとにファイルをまとめやすくなった。

app/
├── page.js
├── about/
│   └── ...           --> ✅aboutページで使うファイルはここにまとめる
└── products/
    └── ...           --> ✅productsページで使うファイルはここにまとめる

共通のレイアウトが作りやすい

layout.jsというファイルが使えるようなったことで、共通のレイアウトが作りやすくなった。

app/
├── page.js
├── layout.js         --> ✅全ページで共通の見た目
└── products/
    ├── page.js
    └── layout.js     --> ✅products以下のページで共通の見た目

App Routerの悪いところは?

✅実際に使ってみて個人的に悪いと感じたのは以下の2点。

学習コストがかかる

これまでNext.jsを使っていた人でもそれなりに学習しないといけないのが大変。

Server Actionsの使い分けが難しい

バックエンドの処理が2パターンで書けるようになって、どっちで書けばいいか迷う。

  • 従来どおりAPIエンドポイントを作る。
  • Server Actionsでクライアント側のコードの中に、バックエンドのコードを書く。

💡
初心者目線だときちんとルールを決めておかないとソースがぐちゃぐちゃになりそう…

Pages Routerとどっちを使えばいい?

✅公式ドキュメントではこれからはApp Routerが推奨されている!

まとめ

App Routerは全体的にPages Routerから大きく変わっている印象を受けた💦

それなりに学習コストもかかるが、今後はApp Routerが主流になるようなので少しずつ慣れていくのが大事そう😊

参考サイト