Next.jsはReactをベースにしたフルスタックフレームワークで、2026年現在、最も人気のあるWebフレームワークの一つです。本記事では、Next.js 15の主要機能から実践的なアプリ開発まで体系的に解説します。
Next.jsとは?Reactとの違い
Next.jsはVercelが開発するReactフレームワークです。Reactだけではフロントエンドしか構築できませんが、Next.jsはサーバーサイドレンダリング(SSR)、静的サイト生成(SSG)、APIルート、App Routerなどを提供し、フルスタックアプリを構築できます。
Next.jsの主要な特徴
- App Router:Next.js 13以降の新しいルーティングシステム。Server Componentsをデフォルトで使用
- Server Components:サーバーサイドでコンポーネントを実行。データフェッチが高速
- Server Actions:フォームの送信やデータ変更をサーバーで安全に処理
- Image最適化:自動的にWebP変換・遅延読み込み・サイズ最適化
- Turbopack:Rustベースの超高速バンドラーでビルドが爆速
Next.jsのセットアップ
# Next.jsプロジェクトの作成
npx create-next-app@latest my-next-app
# オプション選択画面
# ✅ TypeScript を使用
# ✅ ESLint を使用
# ✅ Tailwind CSSを使用
# ✅ App Router を使用
# ✅ src/ ディレクトリを使用
cd my-next-app
npm run dev # 開発サーバー起動(http://localhost:3000)
プロジェクト構造
my-next-app/
├── src/
│ └── app/
│ ├── layout.tsx # ルートレイアウト
│ ├── page.tsx # トップページ
│ ├── loading.tsx # ローディングUI
│ ├── error.tsx # エラーUI
│ ├── not-found.tsx # 404ページ
│ ├── blog/
│ │ ├── page.tsx # /blog
│ │ └── [slug]/
│ │ └── page.tsx # /blog/:slug
│ └── api/
│ └── posts/
│ └── route.ts # /api/posts エンドポイント
├── public/ # 静的ファイル
├── next.config.ts # Next.js設定
└── tailwind.config.ts # Tailwind設定
Server ComponentsとClient Components
Next.js App Routerでは、デフォルトでServer Componentsを使用します。インタラクティブな機能(useState、useEffect、イベントハンドラー)が必要な場合はClient Componentsを使います。
// Server Component(デフォルト)
// app/blog/page.tsx
interface Post {
id: number;
title: string;
excerpt: string;
slug: string;
}
async function getPosts(): Promise<Post[]> {
// サーバーサイドで直接データベースやAPIを呼び出せる
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // 1時間キャッシュ
});
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts(); // サーバーで実行される
return (
<main>
<h1>ブログ記事一覧</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => (
<article key={post.id} className="border rounded-lg p-4">
<h2 className="text-xl font-bold">{post.title}</h2>
<p className="text-gray-600">{post.excerpt}</p>
<a href={`/blog/${post.slug}`} className="text-blue-600 hover:underline">
続きを読む
</a>
</article>
))}
</div>
</main>
);
}
'use client'; // Client Componentの宣言
import { useState } from 'react';
interface SearchBarProps {
onSearch: (query: string) => void;
}
export function SearchBar({ onSearch }: SearchBarProps) {
const [query, setQuery] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSearch(query);
};
return (
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="記事を検索..."
className="border rounded px-4 py-2 flex-1"
/>
<button type="submit" className="bg-blue-600 text-white px-6 py-2 rounded">
検索
</button>
</form>
);
}
APIルートの実装
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
const PostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
published: z.boolean().default(false)
});
// GET /api/posts
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') ?? '1');
const limit = parseInt(searchParams.get('limit') ?? '10');
const posts = await prisma.post.findMany({
where: { published: true },
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
});
return NextResponse.json({ posts, page, limit });
}
// POST /api/posts
export async function POST(request: NextRequest) {
const body = await request.json();
const result = PostSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: result.error.issues },
{ status: 400 }
);
}
const post = await prisma.post.create({ data: result.data });
return NextResponse.json(post, { status: 201 });
}
Server Actions:フォーム送信を安全に処理
'use server'; // Server Actionsの宣言
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
const ContactSchema = z.object({
name: z.string().min(1, '名前は必須です'),
email: z.string().email('有効なメールアドレスを入力してください'),
message: z.string().min(10, 'メッセージは10文字以上で入力してください')
});
export async function submitContactForm(formData: FormData) {
const data = {
name: formData.get('name') as string,
email: formData.get('email') as string,
message: formData.get('message') as string
};
const result = ContactSchema.safeParse(data);
if (!result.success) {
return { error: result.error.issues[0].message };
}
await prisma.contact.create({ data: result.data });
// メール通知を送信
await sendNotificationEmail(result.data);
revalidatePath('/contact');
redirect('/contact/thanks');
}
Next.jsのデプロイ
Vercelへのデプロイ(最も簡単)
# Vercel CLIでデプロイ
npm install -g vercel
vercel login
vercel --prod # 本番デプロイ
# または GitHub連携で自動デプロイも可能
Docker + セルフホスティング
# next.config.ts
const nextConfig = {
output: 'standalone', // Dockerデプロイ向けの設定
};
# Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
まとめ:Next.js学習ロードマップ
Next.jsはReactの知識を持つエンジニアが最初に学ぶべきフレームワークです。App Router・Server Components・Server Actionsなどの新機能を習得することで、フルスタック開発が可能になります。Udemyの「Next.js 15 & React完全ガイド」などのコースや、Vercelの公式ドキュメントを活用して学習を進めましょう。Next.jsの求人は増加しており、フロントエンドエンジニアの年収500万〜1,000万円以上を目指せる実践的なスキルです。