Next.js 15の新機能とApp Router完全移行ガイド
Next.js 15の最新機能とApp Routerへの移行手順を実践的に解説します。
Keywords: Next.js 15, App Router, React フレームワーク
目次
1. Next.js 15 新機能の概要
2. App Routerの進化と重要性
3. パフォーマンス改善とTurbopack統合
4. Pages RouterからApp Routerへの移行戦略
5. 実践的な移行手順
6. Server ComponentsとClient Components活用法
7. データフェッチング最適化
8. 本番環境デプロイのベストプラクティス
INTRODUCTION
Next.js 15がもたらす革新
Next.js 15が2024年末にリリースされ、React開発者コミュニティに大きな衝撃を与えました。2026年現在、多くの企業が本格的にNext.js 15とApp Routerへの移行を進めており、その結果として平均30-40%のパフォーマンス向上を実現しています。
ポイント
Next.js 15では、Turbopackが安定版として統合され、開発サーバーの起動時間が最大90%短縮されました。また、App Routerがより成熟し、Pages Routerからの移行ツールも充実しています。
このガイドでは、Next.js 15の主要な新機能と、実際のプロダクションアプリケーションでApp Routerへ移行する際の具体的な手順を詳しく解説します。実際のコード例と共に、移行時に直面する課題とその解決策も紹介します。

NEW FEATURES
Next.js 15の主要新機能
Turbopack安定版統合
パフォーマンスの劇的改善
開発サーバー起動時間 — 従来の10秒から1-2秒へと90%短縮されました。
Hot Module Replacement — ファイル変更時の反映速度が平均75%向上しました。
バンドルサイズ最適化 — 自動的なCode SplittingとTree Shakingの改善が行われました。
メモリ使用量削減 — 大規模プロジェクトで最大50%のメモリ使用量削減が実現されています。
React 19サポートとServer Actions
コード解説
Next.js 15でServer Actionsを使用したフォーム処理の例です。従来のAPI Routesよりもシンプルで型安全な実装が可能です。
// app/actions/user-actions.ts
'use server'
import { z } from 'zod'
import { redirect } from 'next/navigation'
import { db } from '@/lib/database'
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email()
})
export async function createUser(formData: FormData) {
const validatedFields = userSchema.safeParse({
name: formData.get('name'),
email: formData.get('email')
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors
}
}
const { name, email } = validatedFields.data
try {
await db.user.create({
data: { name, email }
})
} catch (error) {
return {
message: 'Failed to create user'
}
}
redirect('/users')
}
新しいCaching戦略
キャッシュ戦略の改善点
Request Memoization — 同一リクエスト内での重複データフェッチ自動削除が行われます。
Data Cache — サーバーサイドデータの永続的キャッシュが実現されています。
Full Route Cache — 静的レンダリング結果のキャッシュが行われます。
Router Cache — クライアントサイドルーターのキャッシュ改善が行われています。
ポイント
Next.js 15では、キャッシュの無効化とリバリデーション機能が大幅に強化され、動的なコンテンツ更新がより効率的になりました。
APP ROUTER
App Routerの進化と重要性
App Routerは、Next.js 13で実験的機能として導入されましたが、Next.js 15では完全に成熟し、Pages Routerに代わる標準的なアプローチとなりました。GitHubの統計によると、2026年現在、新規Next.jsプロジェクトの85%がApp Routerを採用しています。
App Routerの主要メリット
メリット
✓ Server Componentsによる初期読み込み時間短縮(平均40%改善)
✓ Streaming SSRによる体感速度向上
✓ ファイルベースルーティングの直感性向上
✓ SEOとアクセシビリティの自動最適化
✓ TypeScript統合の改善
App Routerのディレクトリ構造
コード解説
App Routerの推奨ディレクトリ構造です。各ファイルの役割と命名規則を理解することが重要です。
app/
├── layout.tsx // ルートレイアウト
├── page.tsx // ホームページ
├── globals.css // グローバルスタイル
├── loading.tsx // 共通ローディング
├── error.tsx // エラーハンドリング
├── not-found.tsx // 404ページ
├── dashboard/
│ ├── layout.tsx // ダッシュボードレイアウト
│ ├── page.tsx // ダッシュボードページ
│ ├── loading.tsx // ダッシュボードローディング
│ ├── users/
│ │ ├── page.tsx // ユーザー一覧
│ │ └── [id]/
│ │ ├── page.tsx // ユーザー詳細
│ │ └── edit/
│ │ └── page.tsx // ユーザー編集
│ └── settings/
│ ├── page.tsx
│ └── profile/
│ └── page.tsx
├── api/
│ ├── users/
│ │ ├── route.ts // GET/POST /api/users
│ │ └── [id]/
│ │ └── route.ts // GET/PUT/DELETE /api/users/[id]
│ └── auth/
│ └── route.ts
└── components/
├── ui/
└── forms/

PERFORMANCE
パフォーマンス改善とTurbopack統合
Next.js 15の最大の特徴は、Turbopackの本格統合によるパフォーマンス改善です。実際の測定データによると、中規模アプリケーション(1000コンポーネント程度)で以下の改善が確認されています。
90%
開発サーバー起動時間短縮
従来の10秒から1-2秒への大幅改善
Turbopackの設定と最適化
コード解説
Next.js 15でTurbopackを有効にする設定です。開発環境での大幅なパフォーマンス向上が期待できます。
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Turbopackを開発環境で有効化
experimental: {
turbo: {
// CSS処理の最適化
rules: {
'*.css': {
loaders: ['css-loader'],
as: '*.css',
},
},
},
},
// 本番ビルドの最適化
compiler: {
// 未使用のimport自動削除
removeConsole: process.env.NODE_ENV === 'production',
},
// バンドル解析機能
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
}
}
return config
},
}
module.exports = nextConfig
パフォーマンス測定とモニタリング
コード解説
Web Vitalsを測定するカスタムフックの実装例です。Core Web Vitalsの改善状況をリアルタイムで監視できます。
// lib/analytics.ts
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
function sendToAnalytics(metric: any) {
// Google Analytics 4への送信
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', metric.name, {
custom_parameter_1: metric.value,
custom_parameter_2: metric.id,
custom_parameter_3: metric.name,
})
}
// カスタム分析エンドポイントへの送信
fetch('/api/analytics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
url: window.location.href,
timestamp: Date.now(),
}),
}).catch(console.error)
}
export function initWebVitals() {
getCLS(sendToAnalytics) // Cumulative Layout Shift
getFID(sendToAnalytics) // First Input Delay
getFCP(sendToAnalytics) // First Contentful Paint
getLCP(sendToAnalytics) // Largest Contentful Paint
getTTFB(sendToAnalytics) // Time to First Byte
}
ポイント
Turbopackによる改善は開発環境だけでなく、本番ビルド時間も平均50%短縮されています。大規模プロジェクトでは特に効果が顕著です。
MIGRATION STRATEGY
Pages RouterからApp Routerへの移行戦略
既存のNext.jsアプリケーションをPages RouterからApp Routerに移行することは、段階的なアプローチが重要です。一度にすべてを変更するのではなく、機能単位で段階的に移行することで、リスクを最小限に抑えながら新機能の恩恵を受けることができます。
移行前の準備とチェックリスト
移行準備チェックリスト
☑ Next.js 15への更新完了
☑ React 19への依存関係更新
☑ TypeScriptプロジェクトの場合、型定義の更新
☑ 現在のルート構造の分析と文書化
☑ カスタムAPI Routesの棚卸し
☐ Server Componentsとの互換性確認
☐ テストカバレッジの拡充
段階的移行アプローチ
1
新規機能からApp Router導入
新しいページやAPIエンドポイントをApp Routerで実装し、既存の機能への影響を最小限に抑えます。
2
静的ページの移行
About、Contact、利用規約などの静的なページを最初に移行し、App Routerの動作を確認します。
3
API Routesの移行
Server ActionsやRoute Handlersへの移行を進め、より型安全で効率的なAPI実装に切り替えます。
4
動的ルートの移行
ユーザープロファイル、商品詳細などの動的ルートをServer Componentsとして移行します。

移行時の課題と解決策
問題 01
getServerSidePropsの移行
Pages RouterのgetServerSidePropsは直接的な移行方法がなく、データフェッチングロジックの再設計が必要です。
解決策 — Server Components + fetchでの実装
コード解説
Pages RouterのgetServerSidePropsをApp RouterのServer Componentに移行する例です。
// Before (Pages Router)
export async function getServerSideProps(context) {
const { id } = context.params
const user = await fetch(`https://api.example.com/users/${id}`)
return {
props: {
user: await user.json()
}
}
}
// After (App Router)
// app/users/[id]/page.tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
cache: 'no-store' // SSRと同等の動作
})
if (!res.ok) {
throw new Error('Failed to fetch user')
}
return res.json()
}
export default async function UserPage({
params: { id }
}: {
params: { id: string }
}) {
const user = await getUser(id)
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
注意
App Routerでは、コンポーネント内でのasyncな処理が可能ですが、クライアントサイドでの状態管理とは異なるアプローチが必要です。useEffect内でのデータフェッチングは避け、Server ComponentsかClient Components + SWRを使用してください。
HANDS-ON
実践的な移行手順
実際のプロジェクトでPages RouterからApp Routerに移行する具体的な手順を、実例を交えて詳しく解説します。この例では、中規模のECサイトを想定した移行プロセスを示します。
プロジェクト構造の分析と計画
コード解説
既存のPages Routerプロジェクトの構造分析スクリプトです。移行が必要なファイルを自動的に識別します。
// scripts/analyze-pages.js
const fs = require('fs')
const path = require('path')
function analyzePagesDirectory(dirPath) {
const results = {
staticPages: [],
dynamicPages: [],
apiRoutes: [],
customHooks: [],
dependencies: new Set()
}
function scanDirectory(currentPath, relativePath = '') {
const files = fs.readdirSync(currentPath)
files.forEach(file => {
const fullPath = path.join(currentPath, file)
const relativeFilePath = path.join(relativePath, file)
if (fs.statSync(fullPath).isDirectory()) {
scanDirectory(fullPath, relativeFilePath)
} else if (file.endsWith('.tsx') || file.endsWith('.ts')) {
const content = fs.readFileSync(fullPath, 'utf-8')
// API Routes検出
if (relativeFilePath.startsWith('api/')) {
results.apiRoutes.push({
path: relativeFilePath,
hasGetServerSideProps: false,
hasGetStaticProps: false
})
}
// ページファイル検出
else {
const pageInfo = {
path: relativeFilePath,
hasGetServerSideProps: content.includes('getServerSideProps'),
hasGetStaticProps: content.includes('getStaticProps'),
hasUseEffect: content.includes('useEffect'),
isDynamic: file.includes('[') && file.includes(']')
}
if (pageInfo.isDynamic) {
results.dynamicPages.push(pageInfo)
} else {
results.staticPages.push(pageInfo)
}
}
// 依存関係検出
const imports = content.match(/import .+ from ['"']([^'"']+)['"']/g) || []
imports.forEach(imp => {
const match = imp.match(/from ['"']([^'"']+)['"']/)
if (match && !match[1].startsWith('.')) {
results.dependencies.add(match[1])
}
})
}
})
}
scanDirectory(dirPath)
results.dependencies = Array.from(results.dependencies)
return results
}
const analysis = analyzePagesDirectory('./pages')
console.log('Migration Analysis:', JSON.stringify(analysis, null, 2))
共通レイアウトの移行
コード解説
Pages Routerの_app.tsxをApp Routerのlayout.tsxに移行する例です。メタデータの設定も含みます。
// Before: pages/_app.tsx
import type { AppProps } from 'next/app'
import { Provider } from 'react-redux'
import { store } from '../lib/store'
import Header from '../components/Header'
import Footer from '../components/Footer'
import '../styles/globals.css'
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Header />
<main>
<Component {...pageProps} />
</main>
<Footer />
</Provider>
)
}
// After: app/layout.tsx
import { Inter } from 'next/font/google'
import type { Metadata } from 'next'
import { StoreProvider } from './store-provider'
import Header from '@/components/Header'
import Footer from '@/components/Footer'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: {
template: '%s | Your App',
default: 'Your App - Default Title'
},
description: 'Your app description',
keywords: ['next.js', 'react', 'ecommerce'],
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="ja" className={inter.className}>
<body>
<StoreProvider>
<Header />
<main>
{children}
</main>
<Footer />
</StoreProvider>
</body>
</html>
)
}
// app/store-provider.tsx (Client Component)
'use client'
import { Provider } from 'react-redux'
import { store } from '@/lib/store'
export function StoreProvider({
children,
}: {
children: React.ReactNode
}) {
return <Provider store={store}>{children}</Provider>
}
動的ルートとパラメータの移行
コード解説
商品詳細ページの移行例です。getStaticPropsとgetStaticPathsをServer Componentに置き換えます。
// Before: pages/products/[id].tsx
import { GetStaticProps, GetStaticPaths } from 'next'
import { Product } from '@/types'
interface Props {
product: Product
}
export default function ProductPage({ product }: Props) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const products = await fetch('https://api.example.com/products')
const data = await products.json()
const paths = data.map((product: Product) => ({
params: { id: product.id.toString() }
}))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const product = await fetch(`https://api.example.com/products/${params?.id}`)
return {
props: {
product: await product.json()
},
revalidate: 3600 // 1 hour
}
}
// After: app/products/[id]/page.tsx
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { Product } from '@/types'
interface Props {
params: { id: string }
}
async function getProduct(id: string): Promise<Product> {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 3600 } // ISRと同等
})
if (!res.ok) {
notFound()
}
return res.json()
}
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products')
const data = await products.json()
return data.map((product: Product) => ({
id: product.id.toString(),
}))
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const product = await getProduct(params.id)
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
images: [product.imageUrl],
},
}
}
export default async function ProductPage({ params }: Props) {
const product = await getProduct(params.id)
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
)
}

ポイント
App Routerでは、generateMetadata関数を使用してページごとの動的メタデータを生成できます。これにより、SEOの改善と管理の簡素化が同時に実現できます。
COMPONENTS
Server ComponentsとClient Components活用法
Next.js 15のApp Routerでは、Server ComponentsとClient Componentsの使い分けが重要です。適切な選択により、パフォーマンスの最適化とユーザーエクスペリエンスの向上を両立できます。
Server Componentsの最適化パターン
コード解説
Server Componentでのデータフェッチングとキャッシュ戦略の実装例です。複数のデータソースを効率的に扱います。
// app/dashboard/page.tsx - Server Component
import { Suspense } from 'react'
import { getUserStats, getRecentActivity, getNotifications } from '@/lib/api'
import StatsCards from './components/StatsCards'
import ActivityFeed from './components/ActivityFeed'
import NotificationPanel from './components/NotificationPanel'
import LoadingSpinner from '@/components/LoadingSpinner'
// 並列データフェッチング
async function getDashboardData(userId: string) {
const [stats, activity, notifications] = await Promise.all([
getUserStats(userId, {
next: { revalidate: 300 } // 5分キャッシュ
}),
getRecentActivity(userId, {
next: { revalidate: 60 } // 1分キャッシュ
}),
getNotifications(userId, {
cache: 'no-store' // リアルタイム更新
})
])
return { stats, activity, notifications }
}
export default async function DashboardPage({
searchParams
}: {
searchParams: { userId?: string }
}) {
const userId = searchParams.userId || 'default'
const { stats, activity, notifications } = await getDashboardData(userId)
return (
<div className="dashboard-grid">
<div className="stats-section">
<StatsCards data={stats} />
</div>
<div className="activity-section">
<Suspense fallback={<LoadingSpinner />}>
<ActivityFeed data={activity} />
</Suspense>
</div>
<div className="notifications-section">
<NotificationPanel data={notifications} />
</div>
</div>
)
}
Client Componentsでのインタラクション実装
コード解説
Client Componentでの状態管理とServer Actionsとの連携例です。フォーム送信とローディング状態を効率的に管理します。
// app/components/ProductForm.tsx - Client Component
'use client'
import { useState, useTransition } from 'react'
import { useRouter } from 'next/navigation'
import { createProduct, updateProduct } from '@/app/actions/product-actions'
import { Product } from '@/types'
interface ProductFormProps {
product?: Product
mode: 'create' | 'update'
}
export default function ProductForm({ product, mode }: ProductFormProps) {
const [isPending, startTransition] = useTransition()
const [errors, setErrors] = useState<Record<string, string>>({})
const router = useRouter()
async function handleSubmit(formData: FormData) {
startTransition(async () => {
setErrors({})
const action = mode === 'create' ? createProduct : updateProduct
const result = await action(formData)
if (result?.errors) {
setErrors(result.errors)
} else {
// 成功時はリダイレクト(Server Actionで実行される)
router.refresh()
}
})
}
return (
<form action={handleSubmit} className="space-y-6">
{product && (
<input type="hidden" name="id" value={product.id} />
)}
<div>
<label htmlFor="name" className="block text-sm font-medium">
商品名
</label>
<input
type="text"
id="name"
name="name"
defaultValue={product?.name}
className="mt-1 block w-full border rounded-md px-3 py-2"
required
/>
{errors.name && (
<p className="mt-1 text-sm text-red-600">{errors.name}</p>
)}
</div>
<div>
<label htmlFor="description" className="block text-sm font-medium">
説明
</label>
<textarea
id="description"
name="description"
defaultValue={product?.description}
rows={4}
className="mt-1 block w-full border rounded-md px-3 py-2"
required
/>
{errors.description && (
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
)}
</div>
<div>
<label htmlFor="price" className="block text-sm font-medium">
価格
</label>
<input
type="number"
id="price"
name="price"
defaultValue={product?.price}
min="0"
step="0.01"
className="mt-1 block w-full border rounded-md px-3 py-2"
required
/>
{errors.price && (
<p className="mt-1 text-sm text-red-600">{errors.price}</p>
)}
</div>
<button
type="submit"
disabled={isPending}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{isPending ? (
<span className="flex items-center justify-center">
<svg className="animate-spin h-4 w-4 mr-2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path d="M4 12a8 8 0 018-8V0" fill="currentColor" />
</svg>
処理中...
</span>
) : mode === 'create' ? '作成' : '更新'}
</button>
</form>
)
}
最適化のメリット
✓ Server ComponentsによるJavaScriptバンドルサイズ削減(平均25-30%)
✓ 初期ページロード時間の短縮
✓ SEOとアクセシビリティの自動改善
✓ リアルタイムデータ更新の効率化

DATA FETCHING
データフェッチング最適化
Next.js 15では、データフェッチングの戦略が大幅に改善されました。新しいキャッシュシステムと並列処理により、従来比で平均40-50%のパフォーマンス向上を実現できます。
高速化されたキャッシュ戦略
コード解説
異なるキャッシュ戦略を適用したデータフェッチング関数の実装例です。用途に応じて最適な戦略を選択できます。
// lib/data-fetchers.ts
import { unstable_cache } from 'next/cache'
// 1. 静的データ(長期キャッシュ)
export const getStaticContent = unstable_cache(
async (contentId: string) => {
const response = await fetch(`${process.env.API_BASE_URL}/content/${contentId}`)
return response.json()
},
['static-content'],
{
revalidate: 3600 * 24, // 24時間キャッシュ
tags: ['content']
}
)
// 2. ユーザー固有データ(短期キャッシュ)
export const getUserData = unstable_cache(
async (userId: string) => {
const response = await fetch(`${process.env.API_BASE_URL}/users/${userId}`, {
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`
}
})
return response.json()
},
['user-data'],
{
revalidate: 300, // 5分キャッシュ
tags: ['user']
}
)
// 3. リアルタイムデータ(キャッシュなし)
export async function getLiveData(endpoint: string) {
const response = await fetch(`${process.env.API_BASE_URL}/${endpoint}`, {
cache: 'no-store',
headers: {
'Cache-Control': 'no-cache'
}
})
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`)
}
return response.json()
}
// 4. 条件付きフェッチング(ISR)
export async function getProductData(productId: string, forceRefresh = false) {
const cacheOption = forceRefresh
? { cache: 'no-store' as const }
: { next: { revalidate: 1800 } } // 30分
const response = await fetch(
`${process.env.API_BASE_URL}/products/${productId}`,
cacheOption
)
return response.json()
}
// 5. バッチ処理用データフェッチ
export async function getBatchData(ids: string[]) {
const batchSize = 10
const batches = []
for (let i = 0; i < ids.length; i += batchSize) {
const batch = ids.slice(i, i + batchSize)
batches.push(
fetch(`${process.env.API_BASE_URL}/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: batch }),
next: { revalidate: 600 } // 10分キャッシュ
}).then(res => res.json())
)
}
const results = await Promise.all(batches)
return results.flat()
}
Streaming SSRとSuspenseの活用
コード解説
Suspenseを使用したStreaming SSRの実装例です。重いコンポーネントを段階的に読み込み、体感速度を向上させます。
// app/analytics/page.tsx
import { Suspense } from 'react'
import { Metadata } from 'next'
import QuickStats from './components/QuickStats'
import DetailedCharts from './components/DetailedCharts'
import ReportsTable from './components/ReportsTable'
import UserActivity from './components/UserActivity'
export const metadata: Metadata = {
title: '分析ダッシュボード',
description: 'リアルタイム分析データとレポート'
}
// スケルトンローディング用コンポーネント
function ChartsSkeleton() {
return (
<div className="grid grid-cols-2 gap-6">
{[1, 2, 3, 4].map(i => (
<div key={i} className="h-64 bg-gray-200 rounded-lg animate-pulse" />
))}
</div>
)
}
function TableSkeleton() {
return (
<div className="space-y-4">
{[1, 2, 3, 4, 5].map(i => (
<div key={i} className="h-12 bg-gray-200 rounded animate-pulse" />
))}
</div>
)
}
export default function AnalyticsPage() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">分析ダッシュボード</h1>
{/* 即座に表示される軽量なコンポーネント */}
<div className="mb-8">
<QuickStats />
</div>
{/* 重い処理を並列で実行し、準備できた順に表示 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div>
<h2 className="text-xl font-semibold mb-4">詳細チャート</h2>
<Suspense fallback={<ChartsSkeleton />}>
<DetailedCharts />
</Suspense>
</div>
<div>
<h2 className="text-xl font-semibold mb-4">ユーザー活動</h2>
<Suspense fallback={<div className="h-64 bg-gray-100 rounded-lg flex items-center justify-center">読み込み中...</div>}>
<UserActivity />
</Suspense>
</div>
</div>
{/* 最も重いコンポーネントを最後に読み込み */}
<div className="mt-8">
<h2 className="text-xl font-semibold mb-4">詳細レポート</h2>
<Suspense fallback={<TableSkeleton />}>
<ReportsTable />
</Suspense>
</div>
</div>
)
}
// app/analytics/components/DetailedCharts.tsx
import { getChartData } from '@/lib/analytics'
// Server Component - 自動的にStreaming対応
export default async function DetailedCharts() {
// 重い処理をここで実行
const chartData = await getChartData()
return (
<div className="grid grid-cols-2 gap-6">
{chartData.charts.map((chart, index) => (
<div key={index} className="bg-white p-4 rounded-lg shadow">
<h3 className="font-medium mb-2">{chart.title}</h3>
<div className="h-48 bg-blue-50 rounded flex items-center justify-center">
{/* Chart component here */}
Chart {index + 1}
</div>
</div>
))}
</div>
)
}
ポイント
Streaming SSRにより、Time to First Byte (TTFB) が平均60%改善されます。ユーザーはコンテンツの一部を即座に確認でき、残りの部分は段階的に読み込まれるため、体感速度が大幅に向上します。
DEPLOYMENT
本番環境デプロイのベストプラクティス
Next.js 15をプロダクション環境で安全かつ効率的にデプロイするためには、適切な設定とモニタリングが重要です。実際の企業事例では、適切なデプロイ戦略により99.9%以上のアップタイムを維持しています。
本番環境用設定の最適化
コード解説
本番環境向けの Next.js 設定ファイルです。パフォーマンス、セキュリティ、監視の観点から最適化されています。
// next.config.js - 本番環境設定
/** @type {import('next').