Next.js App Router入門:ルーティング・Server Component・Server Actionsの使い分け
はじめに
Next.jsを学習する中で、基本的なルーティングだけでなく、次のような機能について学びました。
- 基本ルーティング
- パラレルルート
- インターセプトルート
- Server Component
- Client Component
useState- ストリーミング
- API Routes(Route Handlers)
- Server Actions
Next.jsは、Reactを使って画面を作るだけのフレームワークではありません。
画面表示、データ取得、APIの作成、フォーム送信、データの登録・更新・削除など、Webアプリケーションに必要なフロントエンドとサーバー側の処理を一つのプロジェクト内で実装できます。
この記事では、Next.jsのApp Routerで利用する各機能の特徴と使い分けを、初心者向けに整理します。
Next.jsとは
Next.jsは、ReactをベースにしたWebアプリケーション開発用のフレームワークです。
React単体では、ルーティング、サーバー処理、データ取得方法などを別途設計する必要があります。
Next.jsでは、次のような機能があらかじめ用意されています。
- ファイル構成を利用したルーティング
- サーバー側でのレンダリング
- Server Component
- APIの作成
- フォームからのデータ更新
- ページ単位のストリーミング
- 画像やフォントの最適化
現在のApp Routerは、ファイルシステムベースのルーティングを採用し、React Server Components、Suspense、Server Functionsなどの機能を利用できる仕組みになっています。
1. 基本ルーティング
基本ルーティングとは
Next.jsのApp Routerでは、appフォルダ内のフォルダ構成によってURLが決まります。
例えば、次のような構成があるとします。
app/
├── page.tsx
├── about/
│ └── page.tsx
└── users/
└── page.tsx
それぞれのURLは次のようになります。
| ファイル | URL |
|---|---|
app/page.tsx |
/ |
app/about/page.tsx |
/about |
app/users/page.tsx |
/users |
Next.jsでは、フォルダがURLの区切りを表し、page.tsxがそのURLで表示されるページになります。
トップページの例
export default function HomePage() {
return (
<main>
<h1>トップページ</h1>
<p>Next.jsのトップページです。</p>
</main>
)
}
このファイルを次の場所に作成します。
app/page.tsx
ブラウザで / にアクセスすると、この内容が表示されます。
/aboutページの例
export default function AboutPage() {
return (
<main>
<h1>このサイトについて</h1>
<p>Next.jsで作成したWebサイトです。</p>
</main>
)
}
ファイル構成は次のとおりです。
app/
└── about/
└── page.tsx
URLは次のようになります。
/about
ページ間を移動する
Next.jsでは、ページ間のリンクにLinkコンポーネントを使用します。
import Link from 'next/link'
export default function HomePage() {
return (
<main>
<h1>トップページ</h1>
<Link href="/about">
このサイトについて
</Link>
</main>
)
}
Linkは、Next.jsのページ間移動に使用するコンポーネントです。
通常のaタグに近い使い方ができますが、Next.jsによるプリフェッチやクライアントサイドナビゲーションを利用できます。
動的ルーティング
ユーザーIDや記事IDなど、URLの一部が変化するページでは、角括弧を使用します。
app/
└── users/
└── [id]/
└── page.tsx
この構成では、次のようなURLに対応できます。
/users/1
/users/2
/users/100
実装例です。
type Props = {
params: Promise<{
id: string
}>
}
export default async function UserPage({ params }: Props) {
const { id } = await params
return (
<main>
<h1>ユーザー詳細</h1>
<p>ユーザーID:{id}</p>
</main>
)
}
[id]の部分が動的な値として取得されます。
現在のApp Routerでは、paramsはPromiseとして扱う構成が公式ドキュメントで案内されています。
2. レイアウト
レイアウトとは
layout.tsxは、複数のページで共通して使用する画面構成を定義するファイルです。
例えば、ヘッダーやサイドバー、フッターなどを共通化できます。
app/
├── layout.tsx
├── page.tsx
└── about/
└── page.tsx
ルートレイアウトの例です。
import type { ReactNode } from 'react'
type Props = {
children: ReactNode
}
export default function RootLayout({ children }: Props) {
return (
<html lang="ja">
<body>
<header>
<h1>Next.js学習サイト</h1>
</header>
{children}
<footer>
<p>Copyright 2026</p>
</footer>
</body>
</html>
)
}
childrenの部分に、各ページの内容が表示されます。
3. パラレルルート
パラレルルートとは
パラレルルートは、同じレイアウト内に複数のページ領域を同時に表示する仕組みです。
例えば、管理画面で次の情報を並べて表示する場合に利用できます。
- 売上情報
- ユーザー情報
- お知らせ
- アクセス解析
公式ドキュメントでは、パラレルルートは一つのレイアウト内で複数のページを同時または条件付きで表示する仕組みと説明されています。ダッシュボードやSNSのフィードなど、動的な画面構成で利用できます。
フォルダ構成
パラレルルートでは、フォルダ名の先頭に@を付けます。
app/
└── dashboard/
├── layout.tsx
├── page.tsx
├── @analytics/
│ └── page.tsx
└── @users/
└── page.tsx
@analyticsや@usersはスロットと呼ばれます。
これらはURLには含まれません。
レイアウト
import type { ReactNode } from 'react'
type Props = {
children: ReactNode
analytics: ReactNode
users: ReactNode
}
export default function DashboardLayout({
children,
analytics,
users,
}: Props) {
return (
<main>
<h1>ダッシュボード</h1>
{children}
<div className="dashboard-grid">
<section>{analytics}</section>
<section>{users}</section>
</div>
</main>
)
}
@analyticsはanalyticsとして、@usersはusersとしてレイアウトへ渡されます。
アクセス解析領域
export default function AnalyticsPage() {
return (
<div>
<h2>アクセス解析</h2>
<p>本日のアクセス数:1,200件</p>
</div>
)
}
ユーザー領域
export default function UsersPage() {
return (
<div>
<h2>ユーザー情報</h2>
<p>登録ユーザー数:500人</p>
</div>
)
}
これにより、一つのダッシュボード画面で複数の領域を並行して表示できます。
パラレルルートが向いている場面
- 管理画面
- ダッシュボード
- SNSのフィード
- 複数の情報パネルを持つ画面
- 条件によって一部の表示を切り替える画面
単純なページ分割だけであれば、通常のコンポーネントでも実装できます。
パラレルルートは、各領域が独立したルーティング状態やローディング状態を持つ場合に特に有効です。
4. インターセプトルート
インターセプトルートとは
インターセプトルートは、現在表示している画面を維持しながら、別のルートを現在のレイアウト内へ表示する仕組みです。
よくある例は、一覧画面から詳細画面をモーダルで開く処理です。
例えば、次のような動作を実現できます。
- 写真一覧を表示する
- 写真をクリックする
- URLは写真詳細ページへ変わる
- 一覧画面の上に詳細モーダルを表示する
- URLを直接開いた場合は通常の詳細ページとして表示する
公式ドキュメントでは、別の場所にあるルートを現在のレイアウト内で読み込み、ユーザーが現在の画面コンテキストを維持したまま内容を表示できる仕組みとされています。
使用例
Instagramなどで、投稿一覧から写真をクリックするとモーダルが開く動作がイメージしやすい例です。
一方、同じURLを新しいタブで直接開くと、通常の詳細ページとして表示されます。
フォルダ構成例
app/
├── photos/
│ ├── page.tsx
│ └── [id]/
│ └── page.tsx
└── @modal/
├── default.tsx
└── (.)photos/
└── [id]/
└── page.tsx
(.)は、同じ階層にあるルートをインターセプトする記法です。
モーダル用コンポーネント
'use client'
import { useRouter } from 'next/navigation'
import type { ReactNode } from 'react'
type Props = {
children: ReactNode
}
export default function Modal({ children }: Props) {
const router = useRouter()
return (
<div className="modal-backdrop" onClick={() => router.back()}>
<div
className="modal-content"
onClick={(event) => event.stopPropagation()}
>
<button type="button" onClick={() => router.back()}>
閉じる
</button>
{children}
</div>
</div>
)
}
モーダルを閉じる際にはrouter.back()を使い、直前の画面へ戻します。
インターセプトルートが向いている場面
- 写真や動画の詳細モーダル
- 商品詳細の簡易表示
- ログイン画面
- ユーザープロフィール
- 一覧画面を残したまま開きたい詳細画面
通常のページ遷移だけで十分な場合は、インターセプトルートを使用する必要はありません。
5. Server Component
Server Componentとは
Server Componentは、サーバー側で実行されるReactコンポーネントです。
Next.jsのApp Routerでは、コンポーネントは基本的にServer Componentとして扱われます。
Server Componentでは、次のような処理ができます。
- APIからデータを取得する
- データベースへ直接アクセスする
- サーバー側でHTMLを生成する
- 秘密情報をサーバー側に保持する
- クライアントへ送るJavaScriptを減らす
Next.jsでは、Server Componentからfetch、ORM、データベースなどを利用して非同期データを取得できます。
データを取得する例
type Post = {
id: number
title: string
}
export default async function PostsPage() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts'
)
if (!response.ok) {
throw new Error('記事の取得に失敗しました')
}
const posts: Post[] = await response.json()
return (
<main>
<h1>記事一覧</h1>
<ul>
{posts.slice(0, 10).map((post) => (
<li key={post.id}>
{post.title}
</li>
))}
</ul>
</main>
)
}
コンポーネント自体をasync関数にし、その中でデータを取得しています。
Server Componentのメリット
ブラウザへ送るJavaScriptを減らせる
Server Componentの処理はサーバー側で実行されます。
データ取得に使用したライブラリやサーバー側の処理を、そのままブラウザへ送る必要がありません。
データベースへ直接アクセスできる
Server ComponentからORMなどを使用して、直接データを取得できます。
import { prisma } from '@/lib/prisma'
export default async function UsersPage() {
const users = await prisma.user.findMany()
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
秘密情報をサーバー側で扱える
APIキー、データベースの接続情報、認証情報などをブラウザへ公開せずに処理できます。
Server Componentでできないこと
Server Componentでは、次のようなブラウザ上の操作は基本的に扱えません。
useStateuseEffectonClickonChangewindowdocumentlocalStorage
ユーザー操作が必要な部分には、Client Componentを使用します。
6. Client Component
Client Componentとは
Client Componentは、ブラウザ側で動作するコンポーネントです。
ファイルの先頭に次の記述を追加します。
'use client'
Client Componentでは、次の機能を使用できます。
useStateuseEffect- クリックイベント
- 入力イベント
- ブラウザAPI
localStoragewindowdocument
Client Componentは、状態、エフェクト、イベントハンドラー、ブラウザAPIが必要なインタラクティブな部分に使用します。
カウンターの例
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>現在の数:{count}</p>
<button
type="button"
onClick={() => setCount(count + 1)}
>
1増やす
</button>
</div>
)
}
ボタンをクリックすると、countの値が変化して画面へ反映されます。
Server ComponentとClient Componentの使い分け
基本的には、可能な範囲をServer Componentにし、ユーザー操作が必要な部分だけClient Componentにします。
例えば、商品詳細ページの場合は次のように分けられます。
商品詳細ページ:Server Component
├── 商品名:Server Component
├── 商品説明:Server Component
├── 商品価格:Server Component
└── カート追加ボタン:Client Component
商品情報の取得と表示はサーバーで行い、クリック処理が必要なボタンだけをClient Componentにします。
組み合わせ例
Server Componentです。
import AddToCartButton from './AddToCartButton'
export default async function ProductPage() {
const product = {
id: 1,
name: 'キーボード',
price: 8000,
}
return (
<main>
<h1>{product.name}</h1>
<p>{product.price.toLocaleString()}円</p>
<AddToCartButton productId={product.id} />
</main>
)
}
Client Componentです。
'use client'
import { useState } from 'react'
type Props = {
productId: number
}
export default function AddToCartButton({ productId }: Props) {
const [added, setAdded] = useState(false)
const handleClick = () => {
console.log('商品ID:', productId)
setAdded(true)
}
return (
<button type="button" onClick={handleClick}>
{added ? '追加しました' : 'カートへ追加'}
</button>
)
}
7. useState
useStateとは
useStateは、ユーザー操作によって変化する値をコンポーネント内で保持するためのReact Hookです。
例えば、次のような値を管理できます。
- カウンターの数値
- 入力フォームの内容
- モーダルの開閉状態
- チェックボックスの状態
- 選択中のタブ
- API通信中かどうか
入力フォームの例
'use client'
import { useState } from 'react'
export default function NameForm() {
const [name, setName] = useState('')
return (
<div>
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
/>
<p>入力内容:{name}</p>
</div>
)
}
入力欄の内容が変更されると、setNameによってnameが更新されます。
setName(event.target.value)
nameが変化すると、画面の表示も更新されます。
useStateの構成
const [name, setName] = useState('')
それぞれの役割は次のとおりです。
| 名前 | 役割 |
|---|---|
name |
現在の値 |
setName |
値を更新する関数 |
'' |
初期値 |
useStateを使用する場所
useStateはClient Componentで使用します。
そのため、ファイルの先頭に'use client'が必要です。
'use client'
import { useState } from 'react'
ページ全体をClient Componentにする必要はなく、状態管理が必要な部分だけを分割することが重要です。
8. ストリーミング
ストリーミングとは
ストリーミングは、ページ全体の処理が完了するまで待つのではなく、準備できた部分から順番に画面へ表示する仕組みです。
例えば、一つのページに次の情報があるとします。
- ヘッダー:すぐに表示できる
- ユーザー情報:1秒かかる
- 売上集計:3秒かかる
ストリーミングを使用しない場合、すべての処理が終わるまで画面が表示されない可能性があります。
ストリーミングを使用すると、ヘッダーなどの準備できた部分を先に表示し、時間がかかる部分にはローディング表示を出せます。
App Routerは、loading.tsxやReactのSuspenseを使用したストリーミングに対応しています。非同期処理が完了するまで代替UIを表示し、完了後に本来の内容へ置き換えます。
loading.tsxを使用する
フォルダ構成です。
app/
└── posts/
├── loading.tsx
└── page.tsx
loading.tsxを作成します。
export default function Loading() {
return (
<p>記事を読み込んでいます...</p>
)
}
page.tsxの処理が完了するまで、loading.tsxの内容が表示されます。
Suspenseを使用する
import { Suspense } from 'react'
import PostList from './PostList'
export default function PostsPage() {
return (
<main>
<h1>記事一覧</h1>
<Suspense fallback={<p>記事を読み込んでいます...</p>}>
<PostList />
</Suspense>
</main>
)
}
非同期処理を行うコンポーネントです。
type Post = {
id: number
title: string
}
export default async function PostList() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts'
)
if (!response.ok) {
throw new Error('記事の取得に失敗しました')
}
const posts: Post[] = await response.json()
return (
<ul>
{posts.slice(0, 10).map((post) => (
<li key={post.id}>
{post.title}
</li>
))}
</ul>
)
}
loading.tsxとSuspenseの使い分け
loading.tsx
ルート全体の読み込み表示に向いています。
app/posts/loading.tsx
Suspense
ページの一部分だけ読み込み表示を出したい場合に向いています。
<Suspense fallback={<p>読み込み中...</p>}>
<PostList />
</Suspense>
9. API Routes(Route Handlers)
Route Handlersとは
Route Handlersは、Next.jsのプロジェクト内にHTTP APIを作成する仕組みです。
以前のPages Routerでは一般的にAPI Routesと呼ばれていましたが、App Routerではroute.tsを使用するRoute Handlersを利用します。
Route Handlersでは、Web標準のRequestとResponseを使用してリクエストを処理します。対応する主なHTTPメソッドには、GET、POST、PUT、PATCH、DELETEなどがあります。
GET APIを作成する
ファイル構成です。
app/
└── api/
└── users/
└── route.ts
実装例です。
export async function GET() {
const users = [
{
id: 1,
name: '山田太郎',
},
{
id: 2,
name: '佐藤花子',
},
]
return Response.json(users)
}
次のURLへGETリクエストを送ると、JSONが返されます。
/api/users
POST APIを作成する
export async function POST(request: Request) {
const body = await request.json()
const user = {
id: 3,
name: body.name,
}
return Response.json(
{
message: 'ユーザーを登録しました',
user,
},
{
status: 201,
}
)
}
リクエスト例です。
{
"name": "鈴木一郎"
}
URLパラメータを受け取る
フォルダ構成です。
app/
└── api/
└── users/
└── [id]/
└── route.ts
実装例です。
type Context = {
params: Promise<{
id: string
}>
}
export async function GET(
request: Request,
context: Context
) {
const { id } = await context.params
return Response.json({
id,
name: '山田太郎',
})
}
Route Handlersが向いている場面
- 外部システムから呼び出されるAPI
- モバイルアプリから利用するAPI
- Webhookの受信
- 外部サービスとの連携
- JSONやファイルを返す処理
- HTTPメソッドやレスポンスを細かく制御したい処理
10. Server Actions
Server Actionsとは
Server Actionsは、サーバー上で実行される非同期関数です。
フォーム送信やデータ更新などのサーバー処理を、専用のRoute Handlerを作らずにコンポーネントから呼び出せます。
現在の公式ドキュメントでは、React Server Actionsはサーバー上で実行されるServer Functionsであり、Server ComponentとClient Componentの両方からフォーム処理などに利用できると説明されています。
基本的なフォーム送信
export default function ContactPage() {
async function sendMessage(formData: FormData) {
'use server'
const name = formData.get('name')
const message = formData.get('message')
console.log({
name,
message,
})
}
return (
<main>
<h1>お問い合わせ</h1>
<form action={sendMessage}>
<div>
<label htmlFor="name">
名前
</label>
<input
id="name"
name="name"
type="text"
required
/>
</div>
<div>
<label htmlFor="message">
内容
</label>
<textarea
id="message"
name="message"
required
/>
</div>
<button type="submit">
送信
</button>
</form>
</main>
)
}
formのactionにServer Actionを指定しています。
<form action={sendMessage}>
フォームが送信されると、sendMessageがサーバー上で実行されます。
別ファイルに定義する
再利用する場合は、Server Actionを別ファイルへ分けられます。
app/
├── actions.ts
└── users/
└── page.tsx
actions.tsです。
'use server'
export async function createUser(formData: FormData) {
const name = formData.get('name')
if (typeof name !== 'string' || name.trim() === '') {
throw new Error('名前を入力してください')
}
console.log('登録するユーザー:', name)
}
ページ側です。
import { createUser } from '../actions'
export default function UsersPage() {
return (
<main>
<h1>ユーザー登録</h1>
<form action={createUser}>
<input
type="text"
name="name"
required
/>
<button type="submit">
登録
</button>
</form>
</main>
)
}
データベースへ登録する例
'use server'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
if (
typeof title !== 'string' ||
typeof content !== 'string'
) {
throw new Error('入力内容が正しくありません')
}
await prisma.post.create({
data: {
title,
content,
},
})
revalidatePath('/posts')
}
データを登録したあと、revalidatePathで記事一覧ページを再検証しています。
Server Actionsが向いている場面
- フォーム送信
- データベースへの登録
- データの更新
- データの削除
- ログイン処理
- ページ内部から呼び出すサーバー処理
11. Route HandlersとServer Actionsの違い
Route HandlersとServer Actionsは、どちらもサーバー側の処理を実装できます。
ただし、利用目的が異なります。
| 比較項目 | Route Handlers | Server Actions |
|---|---|---|
| 主な用途 | HTTP APIの作成 | フォーム送信やデータ更新 |
| 呼び出し元 | 外部システム、アプリ、ブラウザ | Next.jsのコンポーネント |
| ファイル | route.ts |
任意のサーバーファイル |
| HTTPメソッド | 明示的に定義する | 通常は意識せず呼び出せる |
| URL | API用URLを持つ | API用URLを自分で作成しない |
| Webhook | 向いている | 基本的に向いていない |
| フォーム処理 | 実装可能 | 特に向いている |
Route Handlersを選ぶ場合
外部から利用されるHTTP APIを作る場合です。
モバイルアプリ
↓
POST /api/users
↓
Route Handler
Server Actionsを選ぶ場合
Next.js内のフォームから直接サーバー処理を呼び出す場合です。
Next.jsのフォーム
↓
Server Action
↓
データベース更新
12. 各機能の使い分け
基本ルーティング
通常のページを作る場合に使用します。
app/about/page.tsx
パラレルルート
同じ画面に独立した複数のページ領域を表示したい場合に使用します。
ダッシュボード
├── 売上
├── ユーザー
└── お知らせ
インターセプトルート
現在の画面を維持しながら、別ページをモーダルなどで表示したい場合に使用します。
商品一覧
↓ 商品をクリック
商品詳細モーダル
Server Component
データ取得、静的な表示、データベースアクセスなど、サーバー側で処理できる部分に使用します。
Client Component
クリック、入力、状態管理、ブラウザAPIなど、ユーザー操作が必要な部分に使用します。
useState
Client Component内で、画面上の一時的な状態を管理する場合に使用します。
ストリーミング
時間がかかる処理を待つ間も、準備できた画面から順番に表示したい場合に使用します。
Route Handlers
外部から呼び出せるHTTP APIやWebhookを作る場合に使用します。
Server Actions
Next.js内のフォーム送信やデータ更新処理を、専用APIを作成せずに実装したい場合に使用します。
13. 簡単なCRUD構成例
Next.jsで投稿管理アプリを作る場合、次のように役割を分けられます。
app/
├── posts/
│ ├── page.tsx
│ ├── new/
│ │ └── page.tsx
│ └── [id]/
│ ├── page.tsx
│ └── edit/
│ └── page.tsx
├── actions/
│ └── posts.ts
└── api/
└── posts/
└── route.ts
それぞれの役割です。
| 処理 | 実装方法 |
|---|---|
| 投稿一覧の表示 | Server Component |
| 投稿詳細の表示 | 動的ルーティング |
| 投稿登録フォーム | Server Action |
| 投稿更新 | Server Action |
| 投稿削除 | Server Action |
| 外部向け投稿API | Route Handler |
| フォームの入力状態 | Client Component、useState
|
| 読み込み表示 |
loading.tsx、Suspense
|
まとめ
Next.jsのApp Routerについて、次の機能を学びました。
- 基本ルーティング
- パラレルルート
- インターセプトルート
- Server Component
- Client Component
useState- ストリーミング
- Route Handlers
- Server Actions
特に重要だと感じたのは、Server ComponentとClient Componentの使い分けです。
すべてをブラウザ側で動かすのではなく、データ取得や静的な表示はServer Componentで処理し、クリックや入力などの操作が必要な部分だけをClient Componentにすることで、役割を明確に分けられます。
また、Route Handlersを使用すれば外部向けのAPIを作成でき、Server Actionsを使用すればフォーム送信やデータ更新をコンポーネントから直接実行できます。
Next.jsは画面表示だけでなく、データ取得、APIの作成、フォーム送信、登録・更新・削除などのサーバー処理まで、一つのプロジェクト内で一貫して実装できるフレームワークだと理解しました。
今後は、投稿管理や掲示板などの実際のWebアプリケーションを作成しながら、各機能をどの場面で使用するべきか、より実践的な使い分けを身につけていきたいと思います。