{post.title}
{post.publishedAt}
{/* インタラクティブな部分だけClient Component */}
Next.js 14で安定版となったApp Routerは、Pages Routerから大きく設計思想が変わりました。React Server Components(RSC)を基盤とし、サーバーとクライアントの境界を明確に分離することで、パフォーマンスと開発体験の両方を向上させています。
筆者は実際にPages Routerで構築された中規模プロジェクトをApp Routerに移行した経験があります。その過程で得た知見と、設計上の重要なポイントを本記事で共有します。
App Routerではappディレクトリ配下のファイル構成がそのままルーティングに対応します。Pages Routerとの最大の違いは、レイアウトの入れ子構造がフォルダ構成で自然に表現できる点です。
app/
├── layout.tsx # ルートレイアウト
├── page.tsx # トップページ (/)
├── globals.css
├── dashboard/
│ ├── layout.tsx # ダッシュボード共通レイアウト
│ ├── page.tsx # /dashboard
│ ├── analytics/
│ │ └── page.tsx # /dashboard/analytics
│ └── settings/
│ └── page.tsx # /dashboard/settings
├── blog/
│ ├── page.tsx # /blog(記事一覧)
│ └── [slug]/
│ └── page.tsx # /blog/:slug(記事詳細)
└── api/
└── posts/
└── route.ts # API Route: /api/posts
レイアウトはページ遷移時に再レンダリングされません。これにより、サイドバーやナビゲーションの状態が保持され、ユーザー体験が大幅に向上します。
// app/dashboard/layout.tsx
import { Sidebar } from '@/components/Sidebar'
import { Header } from '@/components/Header'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
App Routerでは、デフォルトで全てのコンポーネントがServer Componentです。クライアント側のインタラクションが必要な場合のみ、'use client'ディレクティブを付与します。この判断基準が設計上最も重要なポイントです。
useState、useEffectなどのReact Hooksの使用// app/blog/[slug]/page.tsx — Server Component
import { getPost } from '@/lib/posts'
import { LikeButton } from '@/components/LikeButton'
import { CommentSection } from '@/components/CommentSection'
export default async function BlogPost({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)
return (
{post.title}
{post.publishedAt}
{/* インタラクティブな部分だけClient Component */}
)
}
App Routerでは、Server Component内で直接async/awaitを使ってデータを取得できます。これはPages RouterのgetServerSidePropsやgetStaticPropsに代わる仕組みです。
// デフォルト: 静的にキャッシュ(ビルド時に取得、再利用)
const data = await fetch('https://api.example.com/posts')
// キャッシュ無効: リクエストごとに最新データを取得
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store',
})
// 時間ベースの再検証: 60秒ごとにキャッシュを更新
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
})
実務では、ページの特性に応じてキャッシュ戦略を選択します。ブログ記事のような更新頻度の低いコンテンツには時間ベースの再検証を、ダッシュボードのリアルタイムデータにはno-storeを適用するのが一般的です。
Server Actionsは、フォームの送信処理をサーバー側で直接実行できる機能です。API Routeを別途作成する必要がなく、コードの見通しが良くなります。
// app/contact/page.tsx
export default function ContactPage() {
async function submitForm(formData: FormData) {
'use server'
const name = formData.get('name') as string
const email = formData.get('email') as string
const message = formData.get('message') as string
await db.insert(contacts).values({ name, email, message })
redirect('/contact/thanks')
}
return (
)
}
Server Actionsはプログレッシブエンハンスメントにも対応しており、JavaScriptが無効な環境でもフォーム送信が機能します。これは従来のSPAでは実現が難しかった利点です。
Pages RouterからApp Routerへの移行は段階的に行うことを強く推奨します。Next.jsは両方のルーターを同時に使用できるため、新規ページからApp Routerで作成し、既存ページは順次移行するアプローチが安全です。
App Routerは学習コストが高いものの、正しく活用すればパフォーマンスと開発効率の両面で大きな恩恵があります。まずは小規模なプロジェクトで試し、チーム全体の理解を深めてから本格導入することをお勧めします。