Welcome to Tech Athletes | テック・アスリート   Click to listen highlighted text! Welcome to Tech Athletes | テック・アスリート

【2026年最新】Next.js 15完全ガイド|App Router・Server Components・PPRから本番デプロイまで

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アプリケーションを効率的に開発できます。

投稿者 kasata

IT企業でエンジニアとして勤務後、テクノロジー情報メディア「Tech Athletes(テック・アスリート)」を運営。プログラミング、クラウドインフラ(AWS/GCP/Azure)、AI活用、Webサービス開発を専門とする。エンジニア・ビジネスパーソン向けに、実際に使ってみた経験をもとに信頼できる技術情報を発信中。資格:AWS認定ソリューションアーキテクト、Python 3 エンジニア認定試験合格。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Click to listen highlighted text!