😽

登録処理の実装とNext.js 13.2への移行に伴うエラーの解決方法

2024/07/02に公開

Next.js 13.2のバージョンアップに伴い、発生したエラー

今回は、page.tsxからのfetchによるPOSTリクエストをサーバー側のroute.tsで処理する際に発生したエラーについて、その解決方法を説明します。

階層図

app
├── api
│   └── register
│       └── route.ts
└── page.tsx

    

app/page.tsx

import { useState } from 'react';
import axios from 'axios';

export default function Register() {
    const [email, setEmail] = useState('');
    const [name, setName] = useState('');
    const [message, setMessage] = useState('');

    const handleSubmit = async (event) => {
        event.preventDefault();
        setMessage('');

        try {
            const response = await axios.post('/api/register', { email, name });
            setMessage('User registered successfully!');
        } catch (error) {
            if (error.response) {
                setMessage(error.response.data.message);
            } else {
                setMessage('An unexpected error occurred');
            }
        }
    };

    return (
        <div className="register-form">
            <h1>Register</h1>
            <form onSubmit={handleSubmit}>
                <div>
                    <label>Email:</label>
                    <input
                        type="email"
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label>Name:</label>
                    <input
                        type="text"
                        value={name}
                        onChange={(e) => setName(e.target.value)}
                    />
                </div>
                <button type="submit">登録</button>
            </form>
            {message && <p>{message}</p>}
        </div>
    );
}

api/register/route.ts

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

export default async function register(req, res) {
    if (req.method !== 'POST') {
        return res.status(405).json({ message: 'Method not allowed' });
    }

    const { email, name } = req.body;

    // 入力値のバリデーション
    if (!email || typeof email !== 'string') {
        return res.status(400).json({ message: 'Email is required and must be a string' });
    }
    if (name && typeof name !== 'string') {
        return res.status(400).json({ message: 'Name must be a string if provided' });
    }

    try {
        // ユーザーの作成
        const user = await prisma.user.create({
            data: {
                email,
                name,
            },
        });

        return res.status(201).json(user);
    } catch (error) {
        console.error('Error creating user:', error);
        if (error.code === 'P2002' && error.meta.target.includes('email')) {
            return res.status(409).json({ message: 'Email already exists' });
        }
        return res.status(500).json({ message: 'Internal Server Error' });
    }
}

・発生したエラー(登録ボタンを押したとき)

⨯ Detected default export in '/app/app/api/register/route.ts'. Export a named export for each HTTP method instead.
 ⨯ No HTTP methods exported in '/app/app/api/register/route.ts'. Export a named export for each HTTP method

Next.js 13.2への移行ガイドとエラー解決方法

エラー解決方法

Next.js 13.2以降では、export default async function handlerの形式ではなく、HTTPメソッドごとに名前付きエクスポートを使用する必要があります。(GET, POST, PUTなど)今回の例では、POSTを使用します。

export async function POST(req: NextRequest) {
}

その他、Next.js 13.2への移行に伴う変更点

・APIルートを設定するときのルール
Next.jsのAPIルート機能が拡張されました。APIルートはapp/apiディレクトリ内に配置され、各ファイルが独自のエンドポイントとして機能します。
・route.js(ts)ファイルは必ず、apiフォルダに配置します。

app
├── api
│   └── route.js
├── page.js
└── [user]
    └── page.js

・Route Handlersでのレスポンス処理とNextResponseの使用方法
Route Handlersでは、第一引数にNextRequestのみ受け取り、レスポンスは「next/server」からimportしたNextResponseを直接使います。
Route Handlersでは、NextResponse型のオブジェクトを明示的にreturnすることで、レスポンスを返す。例示のNextResponse.json()も、NextResponse型のオブジェクトを返します。

・公式ドキュメント
https://ja.next-community-docs.dev/docs/app-router/building-your-application/routing/route-handlers

修正したソース

・訂正後
app/page.tsx

"use client"

import { useState } from 'react';
import axios from 'axios';

export default function Register() {
    const [email, setEmail] = useState('');
    const [name, setName] = useState('');
    const [message, setMessage] = useState('');

    const handleSubmit = async (event) => {
        event.preventDefault();
        setMessage('');

        try {
            const response = await axios.post('/api/register', { email, name });
            setMessage('User registered successfully!');
        } catch (error) {
            if (error.response) {
                setMessage(error.response.data.message);
            } else {
                setMessage('An unexpected error occurred');
            }
        }
    };

    return (
        <div className="register-form">
            <h1>Register</h1>
            <form onSubmit={handleSubmit}>
                <div>
                    <label>Email:</label>
                    <input
                        type="email"
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label>Name:</label>
                    <input
                        type="text"
                        value={name}
                        onChange={(e) => setName(e.target.value)}
                    />
                </div>
                <button type="submit">Register</button>
            </form>
            {message && <p>{message}</p>}
        </div>
    );
}

api/register/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function POST(req: NextRequest) {
  const { email, name } = await req.json();

  // 入力値のバリデーション
  if (!email || typeof email !== 'string') {
    return NextResponse.json({ message: 'Email is required and must be a string' }, { status: 400 });
  }
  if (name && typeof name !== 'string') {
    return NextResponse.json({ message: 'Name must be a string if provided' }, { status: 400 });
  }

  try {
    // ユーザーの作成
    const user = await prisma.user.create({
      data: {
        email,
        name,
      },
    });

    return NextResponse.json(user, { status: 201 });
  } catch (error) {
    console.error('Error creating user:', error);
    if (error.code === 'P2002' && error.meta.target.includes('email')) {
      return NextResponse.json({ message: 'Email already exists' }, { status: 409 });
    }
    return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
  }
}

まとめ

Next.jsはバージョンアップに伴い、記述方法も変更されることがあります。そのため、最新のバージョンに合わせたコードの書き方を適宜適用する必要があります。バージョンアップに伴う変更点や新しいルールを把握し、コードを適切に修正することが重要です。

Discussion