はじめに:App RouterとServer Componentsの登場
Next.js 13で導入されたApp Routerは、これまでのPages Routerとは異なる新しいルーティングとレンダリングの仕組みを提供します。その中心的な機能が「React Server Components」です。デフォルトで全てのコンポーネントがサーバーサイドで実行されるようになり、パフォーマンスと開発者体験が大きく向上しました。
この記事では、Next.js App Routerの基本的なファイル構造を理解し、Server Componentsの強力な機能をどのように活用していくか、具体的なコード例を交えながら解説します。Pages Routerに慣れている方も、これからNext.jsを始める方も、App Routerの基本をしっかり押さえていきましょう。
App Routerの基本的なファイル構造
App Routerでは、ルーティングはappディレクトリ内のフォルダ構造によって決まります。各フォルダにpage.tsx(または.js)ファイルを置くことで、そのパスに対応するページが作成されます。非常に直感的ですね。
例えば、app/dashboard/settings/page.tsxというファイルは/dashboard/settingsというURLに対応します。他にも、レイアウトを定義するlayout.tsxや、ローディングUIを表示するloading.tsxなど、特別な意味を持つファイルがあります。
以下は基本的なファイル構造の例です。
app/
├── layout.tsx # 全ページ共通のルートレイアウト
├── page.tsx # ホームページ (URL: /)
│
└── posts/
├── [slug]/
│ └── page.tsx # 動的ルート (URL: /posts/hello-world)
│
├── layout.tsx # /posts以下のページに適用されるレイアウト
└── page.tsx # 投稿一覧ページ (URL: /posts)このように、ファイルシステムベースのルーティングにより、アプリケーションの全体像が把握しやすくなっています。
Server Componentsとは?そのメリットを理解する
App Routerでは、コンポーネントはデフォルトで「Server Components」として扱われます。これは、サーバーサイドでのみレンダリングされ、クライアントサイドのJavaScriptバンドルに含まれないコンポーネントです。これにより、いくつかの大きなメリットが生まれます。
- バンドルサイズの削減: クライアントに送信されるJavaScriptが減るため、初期表示速度が向上します。
- データフェッチの簡素化: サーバーサイドで直接
async/awaitを使ってデータを取得できます。useEffectや外部ライブラリは不要です。 - セキュリティ向上: APIキーやデータベース接続情報など、機密情報をクライアントに公開することなくサーバー内に留めることができます。
例えば、以下のように簡単に外部APIからデータを取得して表示できます。
// app/posts/page.tsx (Server Component)
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<main>
<h1>Posts</h1>
<ul>
{posts.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
インタラクティブなUIのための "use client"
Server Componentsは強力ですが、useStateやuseEffectといったReact Hooksや、onClickなどのイベントハンドラは使用できません。これらはブラウザ(クライアントサイド)で実行される必要があるためです。そこで登場するのが「Client Components」です。
コンポーネントをClient Componentにするには、ファイルの先頭に"use client";というディレクティブを記述するだけです。これにより、このコンポーネントとその子コンポーネントはクライアントサイドでレンダリングされるようになります。
// components/Counter.tsx (Client Component)
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}重要なのは、Server Components内でClient Componentsをインポートして使用できるという点です。これにより、静的な部分はサーバーで、インタラクティブな部分だけをクライアントで処理するという、最適なパフォーマンスを実現できます。
実践:ServerとClientコンポーネントの連携
それでは、Server ComponentsとClient Componentsを組み合わせた実践的な例を見てみましょう。サーバーでデータを取得し、そのデータをクライアントコンポーネントに渡してインタラクティブなUIを構築します。
まず、Server Componentでデータをフェッチします。
// app/users/page.tsx (Server Component)
import UserList from '@/components/UserList';
async function getUsers() {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
// Client Componentにデータをpropsとして渡す
return <UserList users={users} />;
}次に、データを受け取るClient Componentを作成します。このコンポーネントでは、ユーザーリストの表示/非表示を切り替えるボタンを実装します。
// components/UserList.tsx (Client Component)
"use client";
import { useState } from 'react';
export default function UserList({ users }: { users: any[] }) {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? 'Hide' : 'Show'} Users
</button>
{isVisible && (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
)}
</div>
);
}この構成により、重いデータフェッチはサーバーで一度だけ行い、UIのインタラクションはクライアントで軽快に動作させることができます。
まとめ
今回はNext.jsのApp RouterとServer Componentsの基本について解説しました。要点をまとめます。
- App Routerは
appディレクトリ内のファイル構造に基づいたルーティングを提供します。 - コンポーネントはデフォルトでServer Componentsとなり、サーバーサイドでレンダリングされます。
- Server Componentsでは
async/awaitを使って直接データフェッチが可能です。 - インタラクティブ性が必要な場合は、
"use client";を付けてClient Componentに切り替えます。 - Server Componentsでデータを取得し、Client Componentsにpropsで渡すのが基本的な設計パターンです。
Server Componentsを基本に考え、必要な部分だけをClient Componentsにするというアプローチは、Next.jsアプリケーションのパフォーマンスを飛躍的に向上させます。ぜひこの新しいパラダイムをマスターして、より高速で優れたWebアプリケーションを構築してください。