Next.js 15完全ガイド2025 - 新機能と実装テクニック徹底解説
Next.js 15の革新的な新機能を完全解説。Turbopack正式版、React 19対応、部分的プリレンダリング、新しいキャッシュ戦略など、実践的なコード例とともに最新機能を網羅します。
React Server Componentsの概念から実装まで、Next.jsのApp Routerを使った実践的な解説。Client ComponentsとServer Componentsの使い分けやパフォーマンス最適化のテクニックを詳しく解説します。
React Server Components(RSC)は、react エコシステムにおける大きなパラダイムシフトです。この記事では、基本概念から実装まで、next.js の App Router を使った実践的な活用方法を詳しく解説します。
React Server Components は、サーバーサイドでレンダリングされる react コンポーネントです。従来の SSR とは異なり、実際にサーバー上でコンポーネントが実行され、結果をクライアントに送信します。
チャートを読み込み中...
React Server Components では、コンポーネントを明確に 2 つのタイプに分ける必要があります。
Server Components(デフォルト)
// app/posts/page.tsx(Server Component)
import { prisma } from '@/lib/prisma'
export default async function PostsPage() {
// サーバーサイドで直接データベースアクセス
const posts = await prisma.post.findMany({
orderBy: { createdAt: 'desc' },
take: 10
})
return (
<div className="posts-container">
<h1>最新の投稿</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
Client Components(‘use client’宣言が必要)
// components/SearchForm.tsx(Client Component)
'use client'
import { useState, useEffect } from 'react'
export default function SearchForm() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const handleSearch = async () => {
const response = await fetch(`/api/search?q=${query}`)
const data = await response.json()
setResults(data)
}
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="検索キーワード"
/>
<button onClick={handleSearch}>検索</button>
</div>
)
}
使い分けの基準
Server Component にする場合:
Client Component にする場合:
React Server Components では、データフェッチングのアプローチが大きく変わります。
// app/dashboard/page.tsx
import { Suspense } from 'react'
// 複数のデータを並列で取得
async function DashboardPage() {
return (
<div>
<h1>ダッシュボード</h1>
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<UserCardSkeleton />}>
<UserCard />
</Suspense>
<Suspense fallback={<StatsCardSkeleton />}>
<StatsCard />
</Suspense>
</div>
</div>
)
}
// 各コンポーネントで独立してデータフェッチ
async function UserCard() {
const user = await getUserData() // 並列実行される
return <div>{user.name}</div>
}
async function StatsCard() {
const stats = await getStatsData() // 並列実行される
return <div>{stats.total}</div>
}
Next.js App Router では、fetch api に強力なキャッシュ機能が内蔵されています。
// デフォルトでは無期限キャッシュ
async function StaticContent() {
const data = await fetch('https://api.example.com/data')
return <div>{data}</div>
}
// リクエスト時に重複排除
async function DeduplicatedFetch() {
// 同じリクエストは自動的にデデュープされる
const [posts, users] = await Promise.all([
fetch('https://api.example.com/posts'),
fetch('https://api.example.com/posts'), // 同じリクエスト
])
}
// 時間ベースの再検証
async function TimedRevalidation() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1時間ごとに再検証
})
return <div>{data}</div>
}
// タグベースの再検証
async function TaggedData() {
const data = await fetch('https://api.example.com/data', {
next: { tags: ['posts'] }
})
return <div>{data}</div>
}
// Server Actionでの手動再検証
async function updatePost() {
'use server'
// データ更新処理
revalidateTag('posts')
}
// app/posts/[id]/page.tsx
import { Suspense } from 'react'
export default function PostPage({ params }: { params: { id: string } }) {
return (
<div>
{/* 即座に表示される部分 */}
<PostHeader id={params.id} />
{/* 段階的に読み込まれる部分 */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={params.id} />
</Suspense>
<Suspense fallback={<RelatedPostsSkeleton />}>
<RelatedPosts postId={params.id} />
</Suspense>
</div>
)
}
async function Comments({ postId }: { postId: string }) {
// 時間のかかるデータフェッチ
const comments = await getComments(postId)
return (
<div>
{comments.map(comment => (
<CommentCard key={comment.id} comment={comment} />
))}
</div>
)
}
実際のブログアプリケーションを例に、RSC の活用方法を見てみましょう。
チャートを読み込み中...
app/
├── layout.tsx # Root Layout (Server Component)
├── page.tsx # Home Page (Server Component)
├── blog/
│ ├── page.tsx # Blog List (Server Component)
│ └── [slug]/
│ └── page.tsx # Blog Post (Server Component)
├── api/
│ ├── posts/
│ └── search/
└── components/
├── server/ # Server Components
│ ├── PostList.tsx
│ ├── PostCard.tsx
│ └── PostContent.tsx
└── client/ # Client Components
├── SearchForm.tsx
├── CommentForm.tsx
└── LikeButton.tsx
// app/blog/[slug]/page.tsx
import { CommentForm } from '@/components/client/CommentForm'
async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
async function addComment(formData: FormData) {
'use server'
const content = formData.get('content') as string
const author = formData.get('author') as string
await prisma.comment.create({
data: {
content,
author,
postId: post.id
}
})
revalidatePath(`/blog/${params.slug}`)
}
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<section>
<h2>コメント</h2>
<CommentForm action={addComment} />
</section>
</article>
)
}
Server Component と Client Component の境界で発生しやすい問題です。
suppressHydrationWarning
の適切な使用useEffect
での条件分岐の活用// 問題のあるコード
function TimeComponent() {
return <div>現在時刻: {new Date().toLocaleString()}</div>
}
// 修正版
'use client'
function TimeComponent() {
const [time, setTime] = useState('')
useEffect(() => {
setTime(new Date().toLocaleString())
}, [])
return <div>現在時刻: {time}</div>
}
// Context は Client Component でのみ使用可能
'use client'
function ClientLayout({ children }: { children: React.ReactNode }) {
return (
<UserContext.Provider value={user}>
{children}
</UserContext.Provider>
)
}
// Server Component では props で渡す
async function ServerLayout({ children }: { children: React.ReactNode }) {
const user = await getCurrentUser()
return (
<ClientLayout user={user}>
{children}
</ClientLayout>
)
}
Server/Client の責務を明確化
並列処理とキャッシュ戦略の実装
Core Web Vitals の確認
実際のユーザー体験の評価
React Server Components は、react アプリケーションのパフォーマンスとユーザー体験を大幅に改善する技術です。
Server Components は、サーバーとクライアントの境界を再定義し、より効率的で高速な web アプリケーションの構築を可能にします。
React Server Components をマスターして、次世代の web アプリケーション開発を始めましょう!