Next.jsとは?
Next.jsはReactベースのフルスタックWebアプリケーションフレームワークです。サーバーサイドレンダリング(SSR)、静的サイト生成(SSG)、API Routes、Server Actionsなど、モダンなWeb開発に必要な機能を統合し、高速で最適化されたアプリケーションを簡単に構築できます。
プロジェクト作成
# create-next-app でプロジェクト作成
npx create-next-app@latest my-app
# 対話的セットアップ
# ✔ TypeScript? → Yes
# ✔ ESLint? → Yes
# ✔ Tailwind CSS? → Yes
# ✔ src/ directory? → Yes
# ✔ App Router? → Yes
# ✔ Import alias? → @/*
cd my-app
npm run dev # http://localhost:3000
● npm run dev - 開発サーバー起動
● npm run build - 本番ビルド
● npm start - 本番サーバー起動
App Router ディレクトリ構造
app/ フォルダ内の構造がURLに対応
app/
├── page.tsx # / (ルート)
├── layout.tsx # 全体レイアウト
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug (動的)
└── api/
└── users/
└── route.ts # /api/users (API Route)
● page.tsx - ページコンポーネント
● layout.tsx - レイアウト(共通UI)
● [param] - 動的ルート
Server Components vs Client Components
Server Component(デフォルト)
// app/page.tsx
// サーバーでレンダリング(デフォルト)
async function getData() {
const res = await fetch('https://api.example.com/data')
return res.json()
}
export default async function Page() {
const data = await getData()
return {data.title}
}
✓ サーバー側で実行
✓ データフェッチング可能
✓ バンドルサイズ削減
✓ async/await 使用可
Client Component
// app/components/Counter.tsx
'use client' // ← クライアントコンポーネント宣言
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
)
}
✓ ブラウザで実行
✓ イベントハンドラ使用可
✓ React Hooks 使用可
✓ ブラウザAPI利用可
データフェッチング & キャッシュ戦略
静的生成 (SSG)
// ビルド時にキャッシュ(デフォルト)
const res = await fetch(
'https://api.example.com/data',
{ cache: 'force-cache' }
)
増分再生成 (ISR)
// 60秒ごとに再検証
const res = await fetch(
'https://api.example.com/data',
{ next: { revalidate: 60 } }
)
動的レンダリング (SSR)
// リクエスト毎に取得
const res = await fetch(
'https://api.example.com/data',
{ cache: 'no-store' }
)
On-Demand Revalidation: revalidatePath('/blog') または revalidateTag('posts') で手動再検証可能
API Routes & Server Actions
API Route (Route Handler)
// app/api/users/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const users = [{ id: 1, name: 'Alice' }]
return NextResponse.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
// DB処理など
return NextResponse.json({ success: true })
}
外部APIとして公開される(/api/users)
Server Action
// app/actions/user.ts
'use server'
export async function createUser(formData: FormData) {
const name = formData.get('name')
// DB処理など
revalidatePath('/users')
return { success: true }
}
// app/components/UserForm.tsx
'use client'
import { createUser } from '@/app/actions/user'
export default function UserForm() {
return (
)
}
フォーム専用。外部公開されない
動的ルート
// app/blog/[slug]/page.tsx
interface PageProps {
params: { slug: string }
searchParams: { [key: string]: string }
}
export default function BlogPost({ params }: PageProps) {
return Post: {params.slug}
}
// 静的生成用パス指定
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
● [slug] - 単一パラメータ
● [...slug] - キャッチオールルート
● [[...slug]] - オプショナルキャッチオール
メタデータ & SEO
// app/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'ページタイトル',
description: 'ページ説明文',
openGraph: {
title: 'OGタイトル',
description: 'OG説明文',
images: ['/og-image.png'],
},
}
// 動的メタデータ
export async function generateMetadata(
{ params }: PageProps
): Promise {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
}
}
画像最適化
import Image from 'next/image'
// 固定サイズ
// レスポンシブ(親要素に合わせる)
// 外部画像
フォント最適化
// app/layout.tsx
import { Inter, Noto_Sans_JP } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
const notoSansJP = Noto_Sans_JP({
weight: ['400', '700'],
subsets: ['latin'],
variable: '--font-noto-sans-jp',
})
export default function RootLayout({ children }) {
return (
{children}
)
}
ミドルウェア
// middleware.ts (プロジェクトルート)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 認証チェック例
const token = request.cookies.get('token')
if (!token && request.nextUrl.pathname.startsWith('/admin')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*', '/dashboard/:path*']
}
認証 (NextAuth.js v5)
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
})
// app/api/auth/[...nextauth]/route.ts
export { handlers as GET, handlers as POST }
// サーバーコンポーネントでセッション取得
import { auth } from "@/auth"
export default async function Page() {
const session = await auth()
if (!session) return Not authenticated
return Hello {session.user?.name}
}
実務で役立つTips
環境変数
.env.local に記載。NEXT_PUBLIC_ プレフィックスでクライアント公開
パフォーマンス
dynamic('...') で動的インポート。loading.tsx でストリーミングUI
エラー処理
error.tsx でエラー境界。not-found.tsx で404ページ
デプロイ
Vercelへpush自動デプロイ。vercel.json で設定カスタマイズ
App Routerの特別なファイル
page.tsx
ページUI
layout.tsx
レイアウト
loading.tsx
ローディング
error.tsx
エラー境界
not-found.tsx
404ページ
template.tsx
再マウント型レイアウト
route.ts
API Route
default.tsx
並列ルートフォールバック
よく使うパターン
検索パラメータ取得
'use client'
import { useSearchParams } from 'next/navigation'
export default function SearchBar() {
const searchParams = useSearchParams()
const query = searchParams.get('q')
return 検索: {query}
}
プログラマティックナビゲーション
'use client'
import { useRouter } from 'next/navigation'
export default function LoginButton() {
const router = useRouter()
const handleLogin = async () => {
// ログイン処理
router.push('/dashboard')
// または router.replace('/dashboard')
}
return
}
サーバーサイドリダイレクト
import { redirect } from 'next/navigation'
export default async function Page() {
const session = await getSession()
if (!session) {
redirect('/login')
}
return Protected Page
}
Suspenseでストリーミング
import { Suspense } from 'react'
export default function Page() {
return (
ダッシュボード
Loading... }>
next.config.js の主要設定
/** @type {import('next').NextConfig} */
const nextConfig = {
// 外部画像ドメイン許可
images: {
domains: ['example.com', 'cdn.example.com'],
// または remotePatterns で細かく制御
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
// リダイレクト設定
async redirects() {
return [
{
source: '/old-path',
destination: '/new-path',
permanent: true,
},
]
},
// リライト設定(プロキシ)
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://api.example.com/:path*',
},
]
},
// 環境変数公開
env: {
CUSTOM_KEY: 'value',
},
// 静的エクスポート(SSG完全版)
output: 'export',
// Strict Mode
reactStrictMode: true,
}
module.exports = nextConfig