はじめに
Next.jsで開発を行う際、様々なエラーに遭遇することがあります。この記事では、よく発生するエラーとその解決方法を、具体的なエラーメッセージとコード例を交えて解説します。
Hydration Error
エラーメッセージ
Error: Hydration failed because the initial UI does not match what was rendered on the server.
原因と解決方法
サーバーとクライアントでレンダリング結果が異なる場合に発生します。
// ❌ 悪い例:サーバーとクライアントで異なる値
function Component() {
return <div>{new Date().toLocaleString()}</div>; // 時刻が異なる!
}
// ✅ 良い例:useEffectでクライアントのみで実行
'use client';
import { useState, useEffect } from 'react';
function Component() {
const [time, setTime] = useState<string>('');
useEffect(() => {
setTime(new Date().toLocaleString());
}, []);
return <div>{time || 'Loading...'}</div>;
}
// ❌ 悪い例:typeof windowのチェック
function Component() {
if (typeof window !== 'undefined') {
return <div>Client</div>;
}
return <div>Server</div>; // Hydrationエラー!
}
// ✅ 良い例:useEffectとstateを使用
'use client';
import { useState, useEffect } from 'react';
function Component() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return <div>{isClient ? 'Client' : 'Loading...'}</div>;
}
Server Component / Client Component関連エラー
エラーメッセージ
Error: useState only works in Client Components. Add the "use client" directive.
解決方法
// ❌ 悪い例:サーバーコンポーネントでuseStateを使用
import { useState } from 'react';
export default function Page() {
const [count, setCount] = useState(0); // エラー!
return <div>{count}</div>;
}
// ✅ 良い例:クライアントコンポーネントに分離
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// app/page.tsx(サーバーコンポーネント)
import { Counter } from '@/components/Counter';
export default function Page() {
return (
<main>
<h1>My Page</h1>
<Counter />
</main>
);
}
Props経由でのシリアライズエラー
Error: Only plain objects can be passed to Client Components from Server Components
// ❌ 悪い例:Dateオブジェクトを直接渡す
// app/page.tsx (Server Component)
export default async function Page() {
const data = await fetchData();
return <ClientComponent date={data.createdAt} />; // Dateオブジェクト
}
// ✅ 良い例:シリアライズ可能な形式に変換
export default async function Page() {
const data = await fetchData();
return <ClientComponent dateString={data.createdAt.toISOString()} />;
}
// ClientComponent.tsx
'use client';
export function ClientComponent({ dateString }: { dateString: string }) {
const date = new Date(dateString);
return <div>{date.toLocaleDateString()}</div>;
}
Module Not Found エラー
エラーメッセージ
Module not found: Can't resolve 'fs'
解決方法
// ❌ 悪い例:クライアントコンポーネントでNode.jsモジュールを使用
'use client';
import fs from 'fs'; // エラー!
// ✅ 良い例:サーバーサイドでのみ使用
// lib/files.ts (サーバーサイドのみで使用)
import fs from 'fs';
import path from 'path';
export function getFiles() {
const dir = path.join(process.cwd(), 'data');
return fs.readdirSync(dir);
}
// app/page.tsx (Server Component)
import { getFiles } from '@/lib/files';
export default async function Page() {
const files = getFiles();
return <ul>{files.map((file) => <li key={file}>{file}</li>)}</ul>;
}
パスエイリアスの問題
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"]
}
}
}
動的ルーティングエラー
エラーメッセージ
Error: getStaticPaths is required for dynamic SSG pages
解決方法
// app/posts/[slug]/page.tsx
// generateStaticParams を定義
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then((res) =>
res.json()
);
return posts.map((post: { slug: string }) => ({
slug: post.slug,
}));
}
// 動的メタデータ
export async function generateMetadata({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}
export default async function PostPage({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
if (!post) {
notFound();
}
return <article>{/* ... */}</article>;
}
環境変数エラー
エラーメッセージ
Error: Environment variable not found: DATABASE_URL
解決方法
// lib/env.ts - 環境変数のバリデーション
const requiredEnvVars = ['DATABASE_URL', 'API_SECRET'] as const;
type EnvVars = (typeof requiredEnvVars)[number];
function getEnv(key: EnvVars): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
export const env = {
databaseUrl: getEnv('DATABASE_URL'),
apiSecret: getEnv('API_SECRET'),
// クライアント用(NEXT_PUBLIC_プレフィックス必須)
publicApiUrl: process.env.NEXT_PUBLIC_API_URL || '',
};
# .env.local
DATABASE_URL=postgresql://localhost:5432/mydb
API_SECRET=secret-key
# クライアントで使用する場合はNEXT_PUBLIC_プレフィックスが必要
NEXT_PUBLIC_API_URL=https://api.example.com
fetch関連エラー
エラーメッセージ
TypeError: fetch failed
解決方法
// lib/api.ts
export async function fetchWithRetry(
url: string,
options?: RequestInit,
retries = 3
): Promise<Response> {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, {
...options,
// タイムアウト設定
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} catch (error) {
if (i === retries - 1) throw error;
// 指数バックオフ
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
throw new Error('Max retries exceeded');
}
// 使用例
export async function getPosts() {
try {
const response = await fetchWithRetry('https://api.example.com/posts');
return response.json();
} catch (error) {
console.error('Failed to fetch posts:', error);
return [];
}
}
CORSエラー
エラーメッセージ
Access to fetch at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy
解決方法
// app/api/proxy/route.ts - プロキシAPIを作成
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const targetUrl = searchParams.get('url');
if (!targetUrl) {
return NextResponse.json({ error: 'URL is required' }, { status: 400 });
}
try {
const response = await fetch(targetUrl);
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch' },
{ status: 500 }
);
}
}
// middleware.ts - CORS設定
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// APIルートにCORSヘッダーを追加
if (request.nextUrl.pathname.startsWith('/api')) {
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, OPTIONS'
);
response.headers.set(
'Access-Control-Allow-Headers',
'Content-Type, Authorization'
);
}
return response;
}
メモリ不足エラー
エラーメッセージ
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
解決方法
// package.json
{
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=4096' next build",
"dev": "NODE_OPTIONS='--max-old-space-size=4096' next dev"
}
}
// next.config.js - バンドル最適化
module.exports = {
experimental: {
optimizePackageImports: ['lodash', 'date-fns', '@mui/material'],
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.optimization.splitChunks = {
chunks: 'all',
maxSize: 200000,
};
}
return config;
},
};
画像最適化エラー
エラーメッセージ
Error: Invalid src prop on `next/image`, hostname "example.com" is not configured
解決方法
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '**.cloudinary.com',
},
],
},
};
デバッグのベストプラクティス
エラーバウンダリの実装
// app/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// エラーをログサービスに送信
console.error('Application error:', error);
}, [error]);
return (
<div className="error-container">
<h2>エラーが発生しました</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>再試行</button>
</div>
);
}
開発時のデバッグ設定
// next.config.js
module.exports = {
// ソースマップを有効化
productionBrowserSourceMaps: true,
// 詳細なエラーメッセージ
onDemandEntries: {
maxInactiveAge: 60 * 1000,
pagesBufferLength: 5,
},
logging: {
fetches: {
fullUrl: true,
},
},
};
まとめ
Next.jsでよく発生するエラーへの対処方法をまとめます。
| エラー種別 | 主な原因 | 対処法 |
|---|---|---|
| Hydration Error | サーバー/クライアントの不一致 | useEffectで動的な値を設定 |
| Client Component Error | use clientディレクティブ不足 | ファイル先頭に’use client’を追加 |
| Module Not Found | Node.jsモジュールのクライアント使用 | サーバーサイドでのみ使用 |
| 動的ルーティング | generateStaticParams未定義 | 静的パスを生成 |
| CORS | クロスオリジンリクエスト | プロキシAPI or ヘッダー設定 |