Next.js API routes 500エラーの原因と解決方法【初心者向け完全ガイド】

React / Next.js

Next.js API routes 500エラーの原因と解決方法【初心者向け完全ガイド】

Next.jsでAPIルートを使用している際に、突然500エラーが発生したことはありませんか?500エラーはサーバー側のエラーを示す最も一般的なHTTPステータスコードで、原因を特定するのが難しいことで知られています。本記事では、Next.js API routesで発生する500エラーの原因から解決方法まで、初心者でもわかりやすく説明します。

Next.js API routes 500エラーとは

500エラーはHTTP 500 Internal Server Errorの略で、サーバー側で予期しないエラーが発生したことを示します。Next.jsのAPIルートで500エラーが返される場合、サーバー実行時に何らかの例外が発生していることを意味します。クライアント側(ブラウザ)からは「何か悪いことが起こった」という情報しか得られないため、デバッグが困難になるケースが多いです。

Next.js API routes 500エラーの主な原因

1. 未処理の例外やエラー

APIルート内でエラーが発生し、それが適切にキャッチされていない場合、500エラーが返されます。例えば、データベース接続エラーやネットワークエラーが発生した際に、try-catchで処理されていないと500エラーになります。

2. 環境変数の不足や誤設定

APIルート内で環境変数を参照していますが、本番環境で環境変数が正しく設定されていない場合、エラーが発生します。特にデータベースのURLやAPIキーなどがundefinedになっていることが原因のケースが多いです。

3. データベース接続エラー

MongoDBやPostgreSQLなどのデータベースに接続できない場合、500エラーが発生します。接続文字列の誤りや、ネットワークの問題が原因となることが一般的です。

4. メモリリークやリソース枯渇

APIルートが無限ループに入ったり、大量のメモリを消費したりすると、Node.jsプロセスがクラッシュし500エラーが返されます。

5. 非同期処理の処理ミス

async/awaitの使い方が誤っていたり、Promiseが正しく返されていない場合、予期しない動作が発生します。

6. JSONレスポンスの形式エラー

res.json()に循環参照を含むオブジェクトを渡したり、BigIntなど直列化できない値を含めたりすると、500エラーが発生することがあります。

500エラーの解決手順

ステップ1: コンソールログで詳細を確認する

まずはサーバー側のコンソールで詳細なエラーメッセージを確認しましょう。ローカル開発環境でnpm run devを実行している場合、ターミナルにエラースタックが出力されます。本番環境ではログサービスを活用してエラーを確認します。

ステップ2: try-catchを使用してエラーハンドリングを実装する

全てのAPIルートに適切なエラーハンドリングを追加してください。これにより、エラーの詳細をログに出力したり、クライアントに有用な情報を返したりできます。

ステップ3: 環境変数を確認する

環境変数が正しく設定されているか、.env.localと本番環境の設定を比較してください。特にAPIキーやデータベース接続文字列は慎重に確認が必要です。

ステップ4: ネットワークと接続性をテストする

APIルートが外部サービスに接続する場合、その接続がサーバーから可能かテストしてください。ファイアウォールやセキュリティグループの設定を確認しましょう。

ステップ5: デバッグモードで詳細をログ出力する

本番環境ではログサービス(例:Sentry、LogRocket)を導入し、500エラーが発生した際の詳細情報を記録してください。

解決策:コード例

基本的なエラーハンドリングの実装

// pages/api/users.js
export default async function handler(req, res) {
  try {
    // リクエストメソッドの確認
    if (req.method !== 'GET') {
      return res.status(405).json({ error: 'Method not allowed' });
    }

    // 環境変数の確認
    if (!process.env.DATABASE_URL) {
      throw new Error('DATABASE_URL is not defined');
    }

    // データベース接続やAPI呼び出し
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }

    const data = await response.json();

    // 成功レスポンス
    return res.status(200).json({ success: true, data });
  } catch (error) {
    // エラーログを出力
    console.error('API error:', error);

    // クライアントにエラーメッセージを返す
    return res.status(500).json({
      error: 'Internal server error',
      message: process.env.NODE_ENV === 'development' ? error.message : undefined
    });
  }
}

データベース接続エラーの解決例

// pages/api/products.js
import { MongoClient } from 'mongodb';

export default async function handler(req, res) {
  const client = new MongoClient(process.env.MONGODB_URI);

  try {
    // 接続タイムアウトを設定
    await client.connect({ serverSelectionTimeoutMS: 5000 });
    
    const db = client.db('myapp');
    const products = await db.collection('products').find({}).toArray();

    res.status(200).json({ success: true, products });
  } catch (error) {
    console.error('Database connection error:', error);
    
    // 接続エラーの種類に応じた処理
    if (error.name === 'MongoServerSelectionError') {
      return res.status(503).json({ 
        error: 'Database connection failed',
        details: 'Service temporarily unavailable'
      });
    }

    res.status(500).json({ error: 'Internal server error' });
  } finally {
    await client.close();
  }
}

非同期処理の正しい実装例

// pages/api/async-data.js
export default async function handler(req, res) {
  try {
    // 複数の非同期処理を並列実行
    const [users, posts] = await Promise.all([
      fetchUsers(),
      fetchPosts()
    ]);

    res.status(200).json({ users, posts });
  } catch (error) {
    console.error('Error fetching data:', error);
    res.status(500).json({ error: 'Failed to fetch data' });
  }
}

// ヘルパー関数
async function fetchUsers() {
  const response = await fetch('https://api.example.com/users');
  if (!response.ok) throw new Error('Failed to fetch users');
  return response.json();
}

async function fetchPosts() {
  const response = await fetch('https://api.example.com/posts');
  if (!response.ok) throw new Error('Failed to fetch posts');
  return response.json();
}

環境変数の検証

// lib/validateEnv.js
export function validateRequiredEnvVars(requiredVars) {
  const missing = requiredVars.filter(
    variable => !process.env[variable]
  );

  if (missing.length > 0) {
    throw new Error(
      `Missing required environment variables: ${missing.join(', ')}`
    );
  }
}

// pages/api/secure-endpoint.js
import { validateRequiredEnvVars } from '@/lib/validateEnv';

export default async function handler(req, res) {
  try {
    validateRequiredEnvVars(['DATABASE_URL', 'API_KEY', 'SECRET']);

    // APIの処理
    res.status(200).json({ message: 'Success' });
  } catch (error) {
    console.error('Configuration error:', error);
    res.status(500).json({ error: error.message });
  }
}

カスタムエラーハンドリング関数

// lib/apiHandler.js
export function createApiHandler(handler) {
  return async (req, res) => {
    try {
      return await handler(req, res);
    } catch (error) {
      console.error('Unhandled API error:', {
        timestamp: new Date().toISOString(),
        path: req.url,
        method: req.method,
        error: error.message,
        stack: error.stack
      });

      // エラータイプに応じた処理
      if (error.statusCode) {
        return res.status(error.statusCode).json({
          error: error.message
        });
      }

      res.status(500).json({
        error: 'Internal server error'
      });
    }
  };
}

// 使用例
// pages/api/example.js
import { createApiHandler } from '@/lib/apiHandler';

const handler = createApiHandler(async (req, res) => {
  const data = await fetchData();
  res.status(200).json(data);
});

export default handler;

よくある間違いと対策

間違い1: エラーハンドリングがない

間違った例:

export default async function handler(req, res) {
  const data = await fetch('https://api.example.com/data');
  const json = await data.json();
  res.json(json); // エラーが発生してもキャッチされない
}

正しい例:

export default async function handler(req, res) {
  try {
    const data = await fetch('https://api.example.com/data');
    if (!data.ok) throw new Error('API failed');
    const json = await data.json();
    res.json(json);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to fetch data' });
  }
}

間違い2: 環境変数をチェックしない

間違った例:

const apiKey = process.env.API_KEY; // undefinedの可能性
const response = await fetch(`https://api.example.com?key=${apiKey}`);

正しい例:

const apiKey = process.env.API_KEY;
if (!apiKey) {
  throw new Error('API_KEY environment variable is not set');
}
const response = await fetch(`https://api.example.com?key=${apiKey}`);

間違い3: Promiseを待たない

間違った例:

export default async function handler(req, res) {
  fetchData(); // awaitがない
  res.json({ message: 'ok' }); // データが取得される前にレスポンスを返す
}

正しい例:

export default async function handler(req, res) {
  const data = await fetchData(); // awaitを忘れない
  res.json({ message: 'ok', data });
}

間違い4: JSONに直列化できない値を含める

間違った例:

const date = new Date();
const bigNumber = BigInt(9007199254740991);
res.json({ date, bigNumber }); // エラーが発生

正しい例:

const date = new Date();
const bigNumber = BigInt(9007199254740991);
res.json({
  date: date.toISOString(),
  bigNumber: bigNumber.toString()
});

間違い5: レスポンスを複数回送信する

間違った例:

export default async function handler(req, res) {
  res.json({ message: 'first' });
  res.json({ message: 'second' }); // エラーが発生
}

正しい例:

export default async function handler(req, res) {
  if (someCondition) {
    return res.json({ message: 'first' }); // returnを使う
  }
  return res.json({ message: 'second' });
}

デバッグのテクニック

1. コンソールログの活用

export default async function handler(req, res) {
  console.log('Received request:', { method: req.method, url: req.url });
  
  try {
    console.log('Environment check:', { 
      hasDbUrl: !!process.env.DATABASE_URL 
    });
    
    const data = await fetchData();
    console.log('Data fetched successfully:', data);
    
    res.status(200).json(data);
  } catch (error) {
    console.error('Full error object:', {
      name: error.name,
      message: error.message,
      code: error.code,
      stack: error.stack
    });
    res.status(500).json({ error: error.message });
  }
}

2. ローカルテストの重要性

本番環境で発生した500エラーは、まずローカル環境で再現させることが重要です。本番環境の環境変数をコピーして、ローカル.env.localに設定し、同じ条件でテストしましょう。

3. Postmanやcurlでのテスト

# cURLでAPIをテスト
curl -X GET http://localhost:3000/api/users \
  -H \"Content-Type: application/json\" \
  -v

# POSTリクエストのテスト
curl -X POST http://localhost:3000/api/users \
  -H \"Content-Type: application/json\" \
  -d '{\"name\": \"John\", \"email\": \"john@example.com\"}' \
  -v

本番環境でのエラーモニタリング

Sentryを使用したエラートラッキング

// pages/api/example.js
import * as Sentry from \"@sentry/nextjs\";

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
});

export default async function handler(req, res) {
  try {
    const data = await fetchData();
    res.status(200).json(data);
  } catch (error) {
    // Sentryに自動的に送信される
    Sentry.captureException(error);
    res.status(500).json({ error: 'Internal server error' });
  }
}

まとめ

Next.js API routesで500エラーが発生した場合、以下のポイントに注意して対応することが重要です:

  • 適切なエラーハンドリング:全てのAPIルートにtry-catchを実装し、エラーを適切に処理する
  • 環境変数の検証:APIキーやデータベース接続文字列が正しく設定されているか確認する
  • 非同期処理の正確性:async/awaitを正しく使用し、Promiseが適切に解決されるまで待つ
  • 詳細なログ出力:本番環境ではSentryなどのロギングサービスを導入し、エラーの詳細を記録する
  • ローカルでのテスト:本番環境で発生したエラーは、まずローカル環境で再現させてから解決する

これらの対策を実施することで、500エラーの大多数は予防・解決することができます。初心者でも、この記事で紹介したコード例を参考にすれば、Next.jsアプリケーションのエラーハンドリングを大幅に改善できるでしょう。

タイトルとURLをコピーしました