Next.js 15がリリース - Turbopack安定化とReact 19対応

2025.12.02

公式ドキュメント

Next.js 15の概要

Next.js 15は、React 19の正式サポートとTurbopackの安定化を実現したメジャーリリースです。開発体験とパフォーマンスの両面で大幅な改善が行われています。

flowchart TB
 subgraph Next15["Next.js 15 Highlights"]
 subgraph Turbo["Turbopack (Stable for Dev)"]
 T1["開発サーバー起動: 76% faster"]
 T2["Fast Refresh: 96% faster"]
 T3["初回コンパイル: 45% faster"]
 end

 subgraph React19["React 19 Support"]
 R1["Server Actions (Stable)"]
 R2["use() Hook"]
 R3["React Compiler (Experimental)"]
 end

 subgraph PPR["Partial Prerendering (Beta)"]
 P1["静的シェル + 動的コンテンツのストリーミング"]
 end

 subgraph Cache["New Caching Defaults"]
 C1["fetch: no-store (デフォルト)"]
 C2["Route Handlers: no-store (デフォルト)"]
 end
 end

Turbopackの安定化

Next.js 15では、開発サーバー用のTurbopackが安定版になりました。

# Turbopackで開発サーバー起動
next dev --turbo

# ビルド時はまだWebpack(Turbopackは開発中)
next build

パフォーマンス比較

大規模プロジェクト (5000モジュール) でのパフォーマンス比較:

項目WebpackTurbopack改善率
開発サーバー起動8.2秒2.0秒76% faster
Fast Refresh(1ファイル変更)800ms30ms96% faster
ルート初回コンパイル600ms330ms45% faster
メモリ使用量1.6GB800MB50% reduction

React 19サポート

Server Actions

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
 const title = formData.get('title') as string;
 const content = formData.get('content') as string;

 // バリデーション
 if (!title || title.length < 3) {
 return { error: 'Title must be at least 3 characters' };
 }

 // データベース操作
 await db.posts.create({ title, content });

 // キャッシュの再検証
 revalidatePath('/posts');

 // リダイレクト
 redirect('/posts');
}

// 楽観的更新付きのアクション
export async function likePost(postId: string) {
 await db.posts.update({
 where: { id: postId },
 data: { likes: { increment: 1 } },
 });

 revalidatePath(`/posts/${postId}`);
}
// app/posts/new/page.tsx
import { createPost } from '../actions';

export default function NewPostPage() {
 return (
 <form action={createPost}>
 <input name="title" placeholder="Title" required />
 <textarea name="content" placeholder="Content" required />
 <button type="submit">Create Post</button>
 </form>
 );
}

useActionState

'use client';

import { useActionState } from 'react';
import { createPost } from '../actions';

export function CreatePostForm() {
 const [state, formAction, isPending] = useActionState(createPost, null);

 return (
 <form action={formAction}>
 <input name="title" placeholder="Title" disabled={isPending} />
 <textarea name="content" placeholder="Content" disabled={isPending} />

 {state?.error && (
 <p className="text-red-500">{state.error}</p>
 )}

 <button type="submit" disabled={isPending}>
 {isPending ? 'Creating...' : 'Create Post'}
 </button>
 </form>
 );
}

useOptimistic

'use client';

import { useOptimistic } from 'react';
import { likePost } from '../actions';

export function LikeButton({ postId, initialLikes }: Props) {
 const [optimisticLikes, addOptimisticLike] = useOptimistic(
 initialLikes,
 (state, _) => state + 1
 );

 async function handleLike() {
 addOptimisticLike(null);
 await likePost(postId);
 }

 return (
 <form action={handleLike}>
 <button type="submit">
 ❤️ {optimisticLikes}
 </button>
 </form>
 );
}

Partial Prerendering (PPR)

静的シェルと動的コンテンツを組み合わせた新しいレンダリング戦略です。

flowchart TB
 subgraph BuildTime["ビルド時"]
 subgraph StaticShell["Static Shell"]
 Header["Header (静的)"]
 subgraph Layout["Layout"]
 Content["Content (静的)"]
 Suspense["Suspense Fallback<br/>(Skeleton)"]
 end
 end
 end

 subgraph RequestTime["リクエスト時"]
 Step1["1. 静的シェルを即座に返却 (TTFB: 数ms)"]
 Step2["2. 動的部分をストリーミング"]
 Step3["3. Suspense境界が順次ハイドレート"]
 Step1 --> Step2 --> Step3
 end

 BuildTime --> RequestTime
// next.config.js
module.exports = {
 experimental: {
 ppr: true,
 },
};
// app/products/[id]/page.tsx
import { Suspense } from 'react';

// 静的に生成される部分
export default function ProductPage({ params }: Props) {
 return (
 <div>
 {/* 静的コンテンツ */}
 <ProductDetails id={params.id} />

 {/* 動的コンテンツ(ストリーミング) */}
 <Suspense fallback={<RecommendationsSkeleton />}>
 <PersonalizedRecommendations userId={getUserId()} />
 </Suspense>

 <Suspense fallback={<ReviewsSkeleton />}>
 <RecentReviews productId={params.id} />
 </Suspense>
 </div>
 );
}

// 動的データを使用するコンポーネント
async function PersonalizedRecommendations({ userId }: { userId: string }) {
 // この部分はリクエスト時に実行される
 const recommendations = await getRecommendations(userId);
 return <RecommendationList items={recommendations} />;
}

新しいキャッシュセマンティクス

注意: Next.js 15ではfetchとRoute Handlerのデフォルトがno-store(キャッシュなし)に変更されました。既存アプリの移行時はキャッシュ設定を確認してください

Next.js 15では、キャッシュのデフォルト動作が変更されました。

// Next.js 14以前: デフォルトでキャッシュ
// Next.js 15: デフォルトでキャッシュなし

// fetch APIのデフォルト変更
async function getData() {
 // Next.js 14: cache: 'force-cache' がデフォルト
 // Next.js 15: cache: 'no-store' がデフォルト
 const res = await fetch('https://api.example.com/data');
 return res.json();
}

// キャッシュを有効にする場合は明示的に指定
async function getCachedData() {
 const res = await fetch('https://api.example.com/data', {
 cache: 'force-cache',
 next: { revalidate: 3600 }, // 1時間
 });
 return res.json();
}

Route Handlersのキャッシュ

// app/api/data/route.ts

// Next.js 14: GET は静的にキャッシュ
// Next.js 15: デフォルトでキャッシュなし

// キャッシュを有効にする
export const dynamic = 'force-static';
// または
export const revalidate = 3600;

export async function GET() {
 const data = await fetchData();
 return Response.json(data);
}

セキュリティ強化

ポイント: Server Actionsには自動的なCSRF保護が組み込まれていますが、許可するオリジンの指定など追加のセキュリティ設定も可能です

Server Actionsのセキュリティ

// Server Actionsのエンドポイント保護
// next.config.js
module.exports = {
 experimental: {
 serverActions: {
 // 許可するオリジンを指定
 allowedOrigins: ['my-app.com', 'staging.my-app.com'],
 },
 },
};
// app/actions.ts
'use server';

import { headers } from 'next/headers';

export async function sensitiveAction() {
 // リファラーチェック
 const referer = headers().get('referer');
 if (!referer?.startsWith('https://my-app.com')) {
 throw new Error('Invalid origin');
 }

 // CSRF保護は自動的に行われる
 // ...
}

静的エクスポートの改善

// next.config.js
module.exports = {
 output: 'export',

 // 静的エクスポート時の画像最適化
 images: {
 unoptimized: true,
 // または外部ローダーを使用
 loader: 'custom',
 loaderFile: './image-loader.js',
 },
};
// image-loader.js
export default function customLoader({ src, width, quality }) {
 return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`;
}

instrumentation.ts

アプリケーションのブートストラップ時にコードを実行できます。

// instrumentation.ts
export async function register() {
 if (process.env.NEXT_RUNTIME === 'nodejs') {
 // Node.jsランタイムでのみ実行
 await import('./monitoring/sentry');
 }

 if (process.env.NEXT_RUNTIME === 'edge') {
 // Edgeランタイムでのみ実行
 await import('./monitoring/edge-logger');
 }
}

export function onRequestError(error: Error, request: Request) {
 // エラーログの送信
 console.error('Request error:', error);
}

マイグレーション

# アップグレード
npm install next@15 react@19 react-dom@19

# 自動コードモッド
npx @next/codemod@latest upgrade latest

実践メモ: 自動コードモッド npx @next/codemod@latest upgrade latest を使うと、多くの破壊的変更を自動修正できます

主な破壊的変更

変更点Next.js 14Next.js 15
fetch デフォルトcache: ‘force-cache’cache: ‘no-store’
Route Handler静的キャッシュキャッシュなし
React バージョンReact 18React 19
Node.js 最小要件18.17.018.18.0

まとめ

このニュースは、今すぐ全員が対応すべき話か、学習や新規開発で前提を更新しておく話かを分けて読むことが重要です。まずは公式情報で対象バージョンと影響範囲を確認し、自分の環境に関係する部分から見直してください。

参考リソース

← 一覧に戻る
PR
PR
PR
PR