Next.js 15はReactベースのフルスタックフレームワークで、現代のWebアプリケーション開発のデファクトスタンダードとなっています。本記事では、Next.js 15の新機能から実践的な開発手法まで網羅的に解説します。
Next.js 15の主要な新機能
- React 19サポート:React Compiler統合による自動最適化
- Turbopackの安定化:開発サーバーの起動・HMRが大幅高速化
- 部分的プリレンダリング(PPR):静的と動的コンテンツの組み合わせ
- キャッシュの改善:よりコントロールしやすいキャッシュ設計
- after()API:レスポンス送信後の処理実行
プロジェクト作成とセットアップ
# Next.js 15プロジェクト作成
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
# 依存関係のインストール
cd my-app && npm install
# 開発サーバー起動(Turbopackで高速起動)
npm run dev
App Routerの基本構造
app/
layout.tsx # ルートレイアウト
page.tsx # トップページ
loading.tsx # ローディングUI
error.tsx # エラーUI
not-found.tsx # 404ページ
globals.css
(auth)/
login/
page.tsx # /loginページ
blog/
[slug]/
page.tsx # 動的ルート /blog/[slug]
api/
users/
route.ts # APIルート /api/users
components/
ui/
Server Componentsの活用
// app/blog/page.tsx(Server Component)
import { Suspense } from "react";
import { PostList } from "@/components/PostList";
import { PostListSkeleton } from "@/components/PostListSkeleton";
// サーバーサイドでデータ取得(fetchは自動でキャッシュ)
async function getPosts() {
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 (
<div>
<h1>ブログ記事一覧</h1>
<Suspense fallback={<PostListSkeleton />}>
<PostList posts={posts} />
</Suspense>
</div>
);
}
Client Componentsの使い分け
"use client"; // クライアントコンポーネントの宣言
import { useState, useEffect } from "react";
export function Counter({ initialCount = 0 }: { initialCount?: number }) {
const [count, setCount] = useState(initialCount);
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
setIsHydrated(true);
}, []);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(c => c - 1)}>-1</button>
{isHydrated && <p>ハイドレーション完了</p>}
</div>
);
}
データフェッチングのパターン
// 1. Static Generation(SSG)
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post: Post) => ({ slug: post.slug }));
}
// 2. Server-Side Rendering(SSR)
export const dynamic = "force-dynamic";
export default async function DynamicPage() {
const data = await fetch("...", { cache: "no-store" });
// リクエストごとに最新データを取得
}
// 3. Incremental Static Regeneration(ISR)
export const revalidate = 60; // 60秒ごとに再生成
// 4. 部分的プリレンダリング(PPR - Next.js 15新機能)
import { unstable_noStore as noStore } from "next/cache";
export default async function PPRPage() {
const staticData = await getStaticData(); // 静的
return (
<div>
<h1>{staticData.title}</h1> {/* 静的 */}
<Suspense fallback={<Spinner />}>
<DynamicContent /> {/* 動的 */}
</Suspense>
</div>
);
}
Route Handler:APIエンドポイントの作成
// app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { z } from "zod";
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
slug: z.string().regex(/^[a-z0-9-]+$/)
});
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "1");
const posts = await db.post.findMany({
take: 10,
skip: (page - 1) * 10,
orderBy: { createdAt: "desc" }
});
return NextResponse.json({ posts });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const result = CreatePostSchema.safeParse(body);
if (!result.success) {
return NextResponse.json({ error: result.error.flatten() }, { status: 400 });
}
const post = await db.post.create({ data: result.data });
return NextResponse.json(post, { status: 201 });
}
Middleware:認証とリダイレクト
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { verifyToken } from "@/lib/auth";
export async function middleware(request: NextRequest) {
const token = request.cookies.get("token")?.value;
// 保護されたルートのチェック
if (request.nextUrl.pathname.startsWith("/dashboard")) {
if (!token || !await verifyToken(token)) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"]
};
Vercelへのデプロイ
# Vercel CLIインストール
npm install -g vercel
# デプロイ
vercel
# 本番デプロイ
vercel --prod
# 環境変数設定
vercel env add NEXT_PUBLIC_API_URL production
パフォーマンス最適化のベストプラクティス
- ✅ 画像最適化:next/imageを使ってWebP変換・遅延読み込み
- ✅ フォント最適化:next/fontでWebフォントの自動最適化
- ✅ Bundle分析:@next/bundle-analyzerで不要コードを特定
- ✅ Dynamic import:重いコンポーネントを遅延ロード
- ✅ Server Components優先:クライアントJSを最小化
Next.js 15はフロントエンドエンジニアにとって必須スキルです。App RouterとServer Componentsを使いこなすことで、パフォーマンスとSEOに優れたWebアプリケーションを効率的に開発できます。