Next.js
Reactベースのフルスタックフレームワーク。SSR、SSG、ISRを標準サポートし、高性能なWebアプリケーション開発を可能にする。企業採用率No.1の地位を確立。
GitHub概要
スター133,753
ウォッチ1,484
フォーク29,050
作成日:2016年10月5日
言語:JavaScript
ライセンス:MIT License
トピックス
blogbrowsercompilercomponentshybridnextjsnodereactserver-renderingssgstaticstatic-site-generatoruniversalvercel
スター履歴
データ取得日時: 2025/8/13 01:43
フレームワーク
Next.js
概要
Next.jsは、ReactベースのフルスタックWebアプリケーション開発フレームワークです。
詳細
Next.js(ネクストJS)は、Vercel(旧Zeit)が開発したReactベースのフルスタックWebアプリケーション開発フレームワークです。2016年にリリースされて以来、Reactアプリケーション開発のデファクトスタンダードとして広く採用されています。静的サイト生成(SSG)、サーバーサイドレンダリング(SSR)、増分静的再生成(ISR)など複数のレンダリング手法をサポートし、パフォーマンス、SEO、開発者体験を大幅に向上させます。App RouterとPages Routerの2つのルーティングシステムを提供し、React Server Componentsなど最新のReact機能にも対応しています。自動コード分割、画像最適化、TypeScript統合、API Routes、ミドルウェアなど豊富な機能を内蔵し、設定なしで本格的なWebアプリケーションを構築できます。現在、Nike、Netflix、Notion、TikTokなど世界規模のサービスで採用されており、Webフロントエンド開発の最前線を牽引しています。
メリット・デメリット
メリット
- ゼロ設定: Webpack、Babel、TypeScript等を自動設定
- 複数レンダリング手法: SSG、SSR、ISRを柔軟に選択可能
- 優秀なパフォーマンス: 自動最適化によるCore Web Vitals向上
- 優れたSEO: サーバーサイドレンダリングによる検索エンジン対応
- 豊富な内蔵機能: 画像最適化、フォント最適化、コード分割等
- 優れた開発体験: Hot Reload、TypeScript統合、ESLint統合
- 強力なエコシステム: Vercel連携とコミュニティサポート
- 最新React対応: Server Components、Suspense等をサポート
デメリット
- 学習コスト: App Router、Server Components等の新概念
- バージョン間の変更: メジャーアップデートでの破壊的変更
- ホスティング制約: フル機能利用にはNode.js環境が必要
- オーバーエンジニアリング: 小規模プロジェクトには過剰な場合も
- デバッグの複雑さ: サーバー・クライアント間のデバッグ
- ベンダーロックイン: Vercel固有機能への依存リスク
主要リンク
書き方の例
Hello World(Pages Router)
// pages/index.js
export default function Home() {
return (
<div>
<h1>Hello, Next.js!</h1>
<p>Pages Routerを使用したシンプルなページです。</p>
</div>
);
}
Hello World(App Router)
// app/page.js
export default function Home() {
return (
<div>
<h1>Hello, Next.js!</h1>
<p>App Routerを使用したシンプルなページです。</p>
</div>
);
}
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body>{children}</body>
</html>
);
}
Static Site Generation(SSG)
Pages Router
// pages/blog/[slug].js
export default function BlogPost({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
// ビルド時にページを生成
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return {
props: { post },
// 60秒後に再生成(ISR)
revalidate: 60,
};
}
// 動的ルートのパスを定義
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return {
paths,
fallback: 'blocking', // 新しいパスをリクエスト時に生成
};
}
App Router
// app/blog/[slug]/page.js
export const revalidate = 60; // ISRの設定
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then((res) =>
res.json()
);
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPost({ params }) {
const { slug } = await params;
// データフェッチ(SSG)
const post = await fetch(`https://api.example.com/posts/${slug}`, {
cache: 'force-cache', // デフォルト(省略可能)
}).then((res) => res.json());
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
Server-Side Rendering(SSR)
Pages Router
// pages/dashboard.js
export default function Dashboard({ user, posts }) {
return (
<div>
<h1>ダッシュボード</h1>
<p>ようこそ、{user.name}さん</p>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
// リクエストごとにサーバーで実行
export async function getServerSideProps(context) {
const { req } = context;
// セッションからユーザー情報を取得
const user = await getUserFromSession(req);
// ユーザー固有のデータを取得
const posts = await fetch(`https://api.example.com/users/${user.id}/posts`)
.then(res => res.json());
return {
props: { user, posts },
};
}
App Router
// app/dashboard/page.js
import { cookies, headers } from 'next/headers';
export default async function Dashboard() {
// 動的APIの使用でSSRに切り替わる
const cookieStore = await cookies();
const headersList = await headers();
// リクエストごとにデータフェッチ
const user = await fetch('https://api.example.com/user', {
cache: 'no-store', // SSRの設定
headers: {
'Authorization': `Bearer ${cookieStore.get('token')?.value}`,
},
}).then(res => res.json());
const posts = await fetch(`https://api.example.com/users/${user.id}/posts`, {
cache: 'no-store',
}).then(res => res.json());
return (
<div>
<h1>ダッシュボード</h1>
<p>ようこそ、{user.name}さん</p>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
API Routes
Pages Router
// pages/api/users/[id].js
export default async function handler(req, res) {
const { id } = req.query;
if (req.method === 'GET') {
try {
const user = await getUserById(id);
res.status(200).json(user);
} catch (error) {
res.status(404).json({ error: 'User not found' });
}
} else if (req.method === 'POST') {
try {
const updatedUser = await updateUser(id, req.body);
res.status(200).json(updatedUser);
} catch (error) {
res.status(500).json({ error: 'Update failed' });
}
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
App Router(Route Handlers)
// app/api/users/[id]/route.js
import { NextResponse } from 'next/server';
export async function GET(request, { params }) {
const { id } = await params;
try {
const user = await getUserById(id);
return NextResponse.json(user);
} catch (error) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
}
export async function POST(request, { params }) {
const { id } = await params;
const body = await request.json();
try {
const updatedUser = await updateUser(id, body);
return NextResponse.json(updatedUser);
} catch (error) {
return NextResponse.json(
{ error: 'Update failed' },
{ status: 500 }
);
}
}
フォーム処理(Server Actions)
// app/contact/page.js
import { redirect } from 'next/navigation';
async function submitContact(formData) {
'use server'; // Server Action
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
// バリデーション
if (!name || !email || !message) {
throw new Error('必須項目を入力してください');
}
// データベースに保存
await saveContact({ name, email, message });
// リダイレクト
redirect('/contact/success');
}
export default function ContactForm() {
return (
<form action={submitContact}>
<div>
<label htmlFor="name">名前:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label htmlFor="email">メール:</label>
<input type="email" id="email" name="email" required />
</div>
<div>
<label htmlFor="message">メッセージ:</label>
<textarea id="message" name="message" rows={4} required />
</div>
<button type="submit">送信</button>
</form>
);
}
データ再検証とキャッシュ管理
// app/admin/posts/page.js
import { revalidatePath, revalidateTag } from 'next/cache';
async function createPost(formData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
// 新しい投稿を作成
await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content }),
next: { tags: ['posts'] }, // タグ付けしてキャッシュ
});
// 特定のパスのキャッシュを無効化
revalidatePath('/blog');
// タグ付きキャッシュを無効化
revalidateTag('posts');
}
export default async function AdminPosts() {
// タグ付きでデータフェッチ
const posts = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
}).then(res => res.json());
return (
<div>
<h1>投稿管理</h1>
<form action={createPost}>
<input name="title" placeholder="タイトル" required />
<textarea name="content" placeholder="内容" required />
<button type="submit">投稿作成</button>
</form>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
ミドルウェア
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
// 認証チェック
const token = request.cookies.get('auth-token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// レスポンスヘッダーの追加
const response = NextResponse.next();
response.headers.set('x-middleware', 'true');
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};