📖
Next.js と Nodemailer による問い合わせフォームの送信高速化
はじめに
Next.js の問い合わせフォームでメールを送信すると、完了まで 約 3 秒 かかっていました。
今回は SMTP コネクションプール と 非同期レスポンス返却 を取り入れ、1 秒未満 に短縮した流れをまとめます。
前提
- Next.js (App Router)
- Nodemailer + Gmail SMTP
- Vercel Functions でホスティング
変更ポイントの全体像
課題 | 対応 |
---|---|
送信のたびに SMTP 接続を再生成していた | transporter をモジュール外に出し、pool: true でコネクションを使い回します |
await sendMail() で処理をブロックしていた |
await を外し、バックグラウンドで送信します |
応答までの待機時間が長い | API は 202 Accepted を即返却して、ユーザーを待たせません |
コード比較
変更前
// src/app/api/contact/route.ts
import { NextResponse } from "next/server";
import nodemailer from "nodemailer";
// .env.local で設定した環境変数を使います
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT || 587),
secure: Number(process.env.SMTP_PORT) === 465,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
export async function POST(request: Request) {
try {
const { name, email, message } = await request.json();
if (!name || !email || !message) {
return NextResponse.json({ error: "名前・メール・メッセージは必須です。" }, { status: 400 });
}
const mailOptions = { /* 省略 */ };
// メール送信
await transporter.sendMail(mailOptions);
return NextResponse.json({ ok: true });
} catch (err) {
console.error("メール送信エラー:", err);
return NextResponse.json({ error: "送信中にエラーが発生しました。" }, { status: 500 });
}
}
- 送信が完了するまで
await
で待っているため、レスポンスが遅くなります。
変更後
// src/app/api/contact/route.ts
import { NextResponse } from "next/server";
import nodemailer from "nodemailer";
// transporter をファイル外側に定義し、プールを有効化
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: process.env.SMTP_PORT === "465",
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
pool: true,
maxConnections: 5,
maxMessages: Infinity,
});
export async function POST(request: Request) {
const { name, email, message } = await request.json();
if (!name || !email || !message) {
return NextResponse.json({ error: "名前・メール・メッセージは必須です。" }, { status: 400 });
}
const mailOptions = { /* 省略 */ };
// await せずバックグラウンド送信
transporter
.sendMail(mailOptions)
.then(() => console.log("メール送信完了"))
.catch((err) => console.error("メール送信失敗", err));
// 即時応答
return NextResponse.json({ queued: true }, { status: 202 });
}
- コネクションプールにより TLS ハンドシェイクコストを削減しています。
-
sendMail
は非同期で実行し、API はすぐに202
を返します。
手順詳細
1. transporter を共通化する
ファイルの上部で Nodemailer の createTransport
を呼び出し、pool: true
を指定します。
こうすることで、コネクションを再利用でき、毎回のハンドシェイク時間を省けます。
sendMail
の await
を外す
2. メール送信が完了するのを API が待たないようにします。
アプリケーションから見ると「処理をキューに入れただけ」なので速く応答できます。
202 Accepted
に変更
3. HTTP ステータスを キューに受け付けたことを示すため 202
を返却しています。
フロントエンドではこのタイミングで「送信が完了しました」と表示して構いません。
効果測定
指標 | 変更前 | 変更後 |
---|---|---|
/api/contact レスポンス時間 |
約 3000 ms | 約 400 ms |
体感 | 待ち時間を感じる | すぐに完了表示 |
今後の検討ポイント
- Gmail SMTP ではなく Gmail API (OAuth2) を使用し、HTTP ベースの呼び出しに切り替える
- SendGrid や Amazon SES などトランザクショナルメール向けのサービスを利用する
- ワーカーやタスクキューを導入し、完全に非同期で処理する
まとめ
コネクションプールと非同期レスポンスを組み合わせるだけで、問い合わせフォームの応答速度を大きく改善できました。
Discussion