Next.js 15 App Router完全ガイド!サーバーコンポーネントを使いこなす実践チュートリアル
アフィリエイト開示

はじめに
こんにちは!プログラミングの世界へようこそ。この記事では、モダンなWeb開発の最前線で活躍するフレームワーク「Next.js」の最新バージョン、Next.js 15で中心的な役割を担う「App Router」と「サーバーコンポーネント」について、一緒に手を動かしながら学んでいきます。
「サーバーコンポーネントって何だか難しそう…」と感じているかもしれません。でも、心配はいりません!この記事は、まさにそんなあなたのために書きました。一つひとつの概念を、身近な例え話を交えながら丁寧に解説し、実際にコードを書きながら「なるほど、こう動くのか!」という小さな成功体験を積み重ねていけるように構成しています。
このチュートリアルを終える頃には、あなたはNext.jsのApp Routerの基本的な考え方を理解し、サーバーコンポーネントとクライアントコンポーネントを適切に使い分け、簡単なWebアプリケーションを自信を持って構築できるようになっているでしょう。技術を学ぶ楽しさを感じながら、フルスタック開発者への第一歩を踏み出しましょう!
前提知識の確認
新しい技術を学ぶとき、どこから手をつけていいか分からなくなりますよね。まずは、このチュートリアルを進める上で必要な知識と、今はまだ知らなくても大丈夫なことを整理しておきましょう。
必要な基礎知識
- HTMLとCSSの基本: Webページの構造を作るHTMLと、見た目を整えるCSSの基本的な知識は必須です。タグの意味やセレクタが分かれば十分です。
- JavaScript (ES6+) の基本: 変数(
const
,let
)、関数、アロー関数、配列の操作(map
など)、非同期処理(async/await
)の基本的な構文を理解しているとスムーズに進められます。 - Reactの基礎: Next.jsはReactのフレームワークなので、Reactの基本は欠かせません。具体的には、コンポーネント、JSX、Props、State(
useState
フック)の概念を理解していることが望ましいです。
事前に理解しておきたい概念
- フロントエンドとバックエンド: ユーザーが直接触れるブラウザ側(フロントエンド)と、データを処理したり保存したりする裏方(バックエンド)の役割の違いを、なんとなくイメージできていると理解が深まります。
- API: フロントエンドとバックエンドが情報をやり取りするための「窓口」のようなものです。今回は外部のAPIを使ってデータを取得する例を扱います。
「分からなくても大丈夫」な部分
- サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)の複雑な仕組み: Next.jsはこれらの技術を裏側でうまく処理してくれます。今は「サーバー側でページを作ってからブラウザに送るんだな」くらいの理解で問題ありません。
- WebpackやBabelなどのビルドツール: これらのツールがコードをブラウザで動く形に変換してくれますが、Next.jsが自動で設定してくれるため、詳細を知らなくても開発は始められます。
完璧にすべてを理解していなくても大丈夫。実践しながら学んでいくのが一番の近道です。気軽にいきましょう!
環境構築:最初の一歩
何事も最初の一歩が肝心です。ここでは、Next.jsでの開発を始めるための環境を整えていきましょう。丁寧に進めれば、つまずくことはありません。
開発環境の準備(初心者向け解説)
Web開発を行うには、お使いのコンピュータに「Node.js」というプログラムを動かすための環境が必要です。これは、JavaScriptをブラウザの外(サーバーなど)で実行するための土台となるものです。Next.jsもこのNode.js上で動作します。
必要なツールとインストール方法
-
Node.jsのインストール: 公式サイトから推奨版(LTS)をダウンロードしてインストールしてください。インストールが完了したら、ターミナル(WindowsではコマンドプロンプトやPowerShell、Macではターミナル)を開き、以下のコマンドを実行してバージョンが表示されれば成功です。
node -v npm -v
-
Next.jsプロジェクトの作成: 準備が整ったら、いよいよNext.jsプロジェクトを作成します。好きな作業ディレクトリに移動し、以下のコマンドを実行してください。いくつか質問されますが、すべてEnterキーを押してデフォルト設定で進めて問題ありません。
npx create-next-app@latest my-blog-app
my-blog-app
という名前のフォルダが作成され、必要なファイルがすべてダウンロードされます。 -
開発サーバーの起動: 作成されたプロジェクトのフォルダに移動し、開発サーバーを起動します。
cd my-blog-app npm run dev
ターミナルに
ready started server on 0.0.0.0:3000, url: http://localhost:3000
と表示されたら、ブラウザでhttp://localhost:3000
を開いてみましょう。Next.jsのウェルカムページが表示されれば、環境構築は完了です!
環境構築でつまずきやすいポイント
- Node.jsのバージョンが古い:
create-next-app
は比較的新しいバージョンのNode.jsを要求します。エラーが出たら、まずNode.jsを最新のLTS版にアップデートしてみてください。 - コマンドが通らない:
npx
コマンドが認識されない場合、Node.jsのインストール時にパスが正しく設定されていない可能性があります。PCを再起動するか、Node.jsを再インストールしてみましょう。 - ポートが既に使用されている:
Port 3000 is already in use.
というエラーが出たら、他のプログラムが3000番ポートを使っている証拠です。そのプログラムを停止するか、npm run dev -- -p 3001
のように別のポート番号を指定して起動しましょう。

基本概念の理解
環境が整ったところで、App Routerの心臓部である「サーバーコンポーネント」と「クライアントコンポーネント」の考え方を理解しましょう。ここが分かれば、Next.js開発がぐっと楽しくなります。
核となる考え方
Next.jsのApp Routerでは、**デフォルトですべてのコンポーネントが「サーバーコンポーネント」**として扱われます。これが最も重要なポイントです。
-
サーバーコンポーネント (Server Components): サーバー側でのみレンダリング(描画)されるコンポーネントです。データベースへのアクセスやAPIキーを使った外部へのリクエストなど、セキュリティが重要な処理や重い処理をここで行います。ユーザーのブラウザには、最終的に生成されたHTMLだけが送られるため、JavaScriptの量を減らし、ページの表示を高速化できます。
-
クライアントコンポーネント (Client Components): ユーザーの操作に応じて動的に変化する、インタラクティブな部分を担当します。ボタンのクリックイベントやフォームの入力、アニメーションなど、ブラウザ上での対話的な処理が必要です。これらを使いたい場合は、ファイルの先頭に
"use client";
というおまじないを書く必要があります。
身近な例での説明
レストランの食事に例えてみましょう。
-
サーバーコンポーネントは「シェフが厨房で完成させる料理」: ステーキやスープのように、厨房(サーバー)で完全に調理され、完成品としてテーブル(ブラウザ)に運ばれてきます。お客さん(ユーザー)は、調理過程を知る必要はなく、ただ美味しい料理を食べるだけです。これにより、素早く料理が提供されます。
-
クライアントコンポーネントは「テーブルで仕上げるサラダ」: ドレッシングが別添えになっていて、お客さん(ユーザー)が自分で混ぜて完成させるサラダのようなものです。ユーザーの好み(操作)に応じて状態が変わるため、テーブル(ブラウザ)での仕上げ作業が必要になります。
このように、役割分担をすることで、効率的でパフォーマンスの高いWebアプリケーションが作れるのです。
「なぜそうなるのか」の理解
この仕組みの最大のメリットはパフォーマンスの向上です。従来の方法では、ページ内のすべての要素がブラウザで動くJavaScriptとして送られていました。しかし、App Routerでは、情報の表示だけを行う静的な部分はサーバーコンポーネントとして処理し、HTMLだけを送るため、ブラウザがダウンロード・実行するJavaScriptの量を大幅に削減できます。これにより、特に通信環境が良くない場所や低スペックのデバイスでも、Webサイトがサクサク表示されるようになります。
実践編:手を動かして学ぶ

理論を学んだら、次は実践です!簡単なブログアプリケーションを作りながら、サーバーコンポーネントとクライアントコンポーネントの使い方をマスターしましょう。
ステップ1: 基本的な実装
まずは、記事一覧ページをサーバーコンポーネントで作ります。サーバーコンポーネントは async/await
を直接使えるのが特徴です。外部のテスト用APIからブログ記事のデータを取得して表示してみましょう。
app/posts/page.tsx
というファイルを作成し、以下のコードを記述してください。(app
ディレクトリの中にposts
フォルダを作り、その中にpage.tsx
ファイルを作成します)
// app/posts/page.tsx
// 記事データの型を定義します
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
// データを取得するための非同期関数
async function getPosts(): Promise<Post[]> {
// 1秒待機させてローディングをシミュレート
await new Promise(resolve => setTimeout(resolve, 1000));
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!res.ok) {
// エラーハンドリング
throw new Error('Failed to fetch posts');
}
return res.json();
}
// ページコンポーネント自体をasyncにする
export default async function PostsPage() {
const posts = await getPosts();
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">記事一覧</h1>
<ul className="list-disc list-inside">
{posts.map((post) => (
<li key={post.id} className="mb-2">
{post.title}
</li>
))}
</ul>
</main>
);
}
ブラウザで http://localhost:3000/posts
にアクセスしてみてください。データ取得中に少し待機した後、記事のタイトル一覧が表示されれば成功です。このページは完全にサーバー側で生成されています。
ステップ2: 機能の拡張
次に、記事一覧の各タイトルをクリックしたら、その記事の詳細ページに移動できるようにします。これは「動的ルーティング」という機能を使います。
app/posts/[id]/page.tsx
というファイルを作成します。(posts
フォルダの中に[id]
という名前のフォルダを作り、その中にpage.tsx
を作成します)
// app/posts/[id]/page.tsx
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
async function getPost(id: string): Promise<Post> {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (!res.ok) {
throw new Error('Failed to fetch post');
}
return res.json();
}
// paramsプロパティでURLの動的な部分([id])を受け取る
export default async function PostDetailPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-700">{post.body}</p>
</main>
);
}
そして、先ほどの app/posts/page.tsx
を少し修正して、各記事にリンクを追加します。Next.jsのLink
コンポーネントを使いましょう。
// app/posts/page.tsx の修正版
import Link from 'next/link';
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
async function getPosts(): Promise<Post[]> {
await new Promise(resolve => setTimeout(resolve, 1000));
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">記事一覧</h1>
<ul>
{posts.map((post) => (
<li key={post.id} className="mb-2 hover:text-blue-600">
<Link href={`/posts/${post.id}`}>
{post.title}
</Link>
</li>
))}
</ul>
</main>
);
}
これで記事一覧ページから詳細ページへ移動できるようになりました。
ステップ3: 実用的な応用
記事詳細ページに、ユーザーが操作できる「いいね!」ボタンを追加してみましょう。ユーザーのクリックに反応する必要があるので、これはクライアントコンポーネントで作ります。
まず、components
フォルダをプロジェクトのルートに作成し、その中に LikeButton.tsx
ファイルを作成します。
// components/LikeButton.tsx
'use client';
import { useState } from 'react';
export default function LikeButton() {
const [likes, setLikes] = useState(0);
const handleClick = () => {
setLikes(likes + 1);
};
return (
<button
onClick={handleClick}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
👍 いいね! {likes}
</button>
);
}
ファイルの先頭に "use client";
を書くことで、このコンポーネントがクライアントコンポーネントであることを宣言しています。これにより、useState
のようなフックが使えるようになります。
次に、このクライアントコンポーネントを、サーバーコンポーネントである記事詳細ページ(app/posts/[id]/page.tsx
)で呼び出します。
// app/posts/[id]/page.tsx の修正版
import LikeButton from '@/components/LikeButton'; // @ はプロジェクトのルートを指すエイリアス
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
async function getPost(id: string): Promise<Post> {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (!res.ok) {
throw new Error('Failed to fetch post');
}
return res.json();
}
export default async function PostDetailPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-700">{post.body}</p>
<LikeButton />
</main>
);
}
これで、サーバーで生成された静的な記事内容と、ブラウザで動くインタラクティブなボタンが共存するページが完成しました!
ステップ4: チーム開発を意識した改善
実際の開発では、コードの整理整頓がとても重要です。今のままでも動きますが、より良くするためにリファクタリングしましょう。
-
データ取得ロジックの分離: ページコンポーネント内にデータ取得のコードが混在していると、見通しが悪くなります。
lib
フォルダなどを作成し、データ関連の関数をまとめましょう。lib/data.ts
を作成:// lib/data.ts export interface Post { userId: number; id: number; title: string; body: string; } export async function getPosts(): Promise<Post[]> { const res = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!res.ok) { throw new Error('Failed to fetch posts'); } return res.json(); } export async function getPost(id: string): Promise<Post> { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); if (!res.ok) { throw new Error('Failed to fetch post'); } return res.json(); }
ページコンポーネントからはこの関数をインポートして使います。これにより、コンポーネントは表示に専念でき、コードがすっきりします。
実際の開発現場での活用
このサーバーコンポーネント中心のアプローチは、実際の業務でどのように役立つのでしょうか。
業務での使用例
- CMSとの連携: ヘッドレスCMS(ContentfulやmicroCMSなど)から記事や商品情報を取得する際に、サーバーコンポーネント内で直接APIを叩きます。APIキーなどの秘密情報をブラウザに漏らすことなく、安全にデータを取得できます。
- データベースアクセス: サーバーコンポーネントはサーバー環境で実行されるため、データベースに直接クエリを発行することも可能です。これにより、APIサーバーを別途用意する手間を省ける場合があります。
- 認証: ユーザーがログインしているかどうかをサーバーサイドでチェックし、ログインユーザーにしか見せないコンテンツを表示する、といった処理をページコンポーネントの冒頭で行えます。
チーム開発でのベストプラクティス
- コンポーネントの粒度: サーバーコンポーネントとクライアントコンポーネントの境界線をどこに引くかが重要です。基本はすべてサーバーコンポーネントで作り、「どうしてもユーザーの操作が必要な部分だけ」を小さなクライアントコンポーネントとして切り出すのが良いプラクティスです。
- 状態管理: サーバーから取得できるデータはURLやサーバーコンポーネントで管理し、クライアント側での状態管理(
useState
など)は最小限に留めることで、シンプルで予測しやすいアプリケーションになります。 - 明確なファイル構成:
components
,lib
,app
などのディレクトリ構造をチームで統一し、誰が見てもどこに何があるか分かるようにしましょう。
保守性を意識した書き方
- エラーハンドリング: Next.jsには
error.tsx
というファイルを使って、ルート単位でエラー発生時のUIを定義する仕組みがあります。予期せぬエラーが発生してもユーザーに真っ白な画面を見せない、親切な設計を心がけましょう。 - ローディングUI: データ取得に時間がかかる場合に備え、
loading.tsx
ファイルを配置することで、自動的にローディング中の表示(スケルトンスクリーンなど)を出せます。これにより、ユーザーの体感速度が向上します。
よくあるつまずきポイントと解決策
学習の過程でエラーはつきものです。エラーは敵ではなく、成長のヒントをくれる味方です。
初心者が陥りやすい問題
- 「
useState
is not available in Server Components.」: このエラーは、サーバーコンポーネント内でuseState
やuseEffect
などのクライアントサイドのフックを使おうとしたときに出ます。解決策は、そのコンポーネントをクライアントコンポーネントにする("use client";
を追加する)か、ロジックを見直してサーバーコンポーネントのままで実装できないか検討することです。 - イベントハンドラが動かない:
onClick
などのイベントハンドラはクライアントコンポーネントでしか機能しません。ボタンなどを設置する場合は、その部分をクライアントコンポーネントとして切り出す必要があります。
エラーメッセージの読み方
Next.jsのエラー表示は非常に親切です。エラーメッセージを怖がらずにしっかり読みましょう。「どのファイルの何行目で」「何が原因で」エラーが起きているのかが具体的に書かれています。多くの場合、解決策のヒントも示唆されています。
デバッグの基本的な考え方
- サーバーコンポーネントのデバッグ:
console.log
を書いた場合、その出力はブラウザの開発者コンソールではなく、開発サーバーを動かしているターミナルに表示されます。 - クライアントコンポーネントのデバッグ: こちらは通常通り、ブラウザの開発者コンソールに
console.log
の内容が表示されます。
この違いを理解することが、App Routerでのデバッグの第一歩です。
継続的な学習のために
基本をマスターしたら、さらにNext.jsの世界を深く探求していきましょう。
次に学ぶべきこと
- Server Actions: フォームの送信などを、クライアントサイドのJavaScriptなしでサーバー側の関数を直接呼び出して処理する強力な機能です。
- キャッシュ戦略:
fetch
関数のオプションを使って、データのキャッシュ方法を細かく制御できます。パフォーマンスチューニングに欠かせない知識です。 - ストリーミング: サーバーコンポーネントを使い、ページの重い部分を後から段階的に表示させることで、初期表示を高速化する技術です。
おすすめの学習リソース
何よりもまず、Next.jsの公式ドキュメントを読むことを強くお勧めします。最新かつ最も正確な情報がここにあります。最初は難しく感じるかもしれませんが、実際にコードを書きながら参照することで、理解が深まっていきます。
コミュニティとの関わり方
一人で悩まず、コミュニティの力を借りましょう。技術系のカンファレンスや勉強会に参加したり、SNSで同じ技術を学ぶ仲間と繋がったりすることで、新しい情報を得られたり、問題解決のヒントをもらえたりします。質問する際は、自分が何を試したのかを具体的に書くと、回答が得られやすくなります。
まとめ:成長のための次のステップ
ここまで本当にお疲れ様でした!このチュートリアルを通して、あなたはNext.js 15のApp Routerの基本を学び、サーバーコンポーネントとクライアントコンポーネントを組み合わせたアプリケーションを実際に作ることができました。これは大きな一歩です。
一番大切なのは、ここで学んだ知識を使って、あなた自身のアイデアを形にしてみることです。このブログアプリにコメント機能を追加してみたり、全く新しいポートフォリオサイトを作ってみたり。試行錯誤する中でこそ、本当の実力が身につきます。
プログラミングは、問題解決の連続であり、創造的な活動です。これからも学ぶ楽しさを忘れずに、一歩一歩着実にスキルアップしていきましょう。あなたのこれからの活躍を心から応援しています!
関連商品・おすすめアイテム
![これからはじめるReact実践入門 コンポーネントの基本からNext.jsによるアプリ開発まで [ 山田 祥寛 ]](https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/9480/9784815619480_1_2.jpg?_ex=128x128)

![実装で学ぶフルスタックWeb開発 エンジニアの視野と知識を広げる「一気通貫」型ハンズオン【電子書籍】[ 株式会社オープントーン ]](https://thumbnail.image.rakuten.co.jp/@0_mall/rakutenkobo-ebooks/cabinet/1430/2000014211430.jpg?_ex=128x128)
実装で学ぶフルスタックWeb開発 エンジニアの視野と知識を広げる「一気通貫」型ハンズオン【電子書籍】[ 株式会社オープントーン ]
販売店: 楽天Kobo電子書籍ストア