NextAuth集成nodemailer后sendVerificationRequest不触发无报错
NextAuth自定义邮件模板后sendVerificationRequest不触发问题排查
问题描述
- 基于NextAuth实现邮箱魔术链接登录:未接入nodemailer、Handlebars自定义邮件模板时,默认邮件发送逻辑运行正常
- 接入二者替换自定义模板后,点击signIn按钮时程序不会进入
sendVerificationRequest函数执行,全程无任何报错抛出 - 问题对应的
[...nextauth].js配置代码如下:
import NextAuth from "next-auth/next"; import EmailProvider from "next-auth/providers/email"; import nodemailer from 'nodemailer' import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { PrismaClient } from "@prisma/client"; import Handlebars from 'handlebars' import { readFileSync } from 'fs' import path from "path"; const prisma = new PrismaClient() const transporter = nodemailer.createTransport({ host: process.env.EMAIL_SERVER_HOST, port: process.env.EMAIL_SERVER_PORT, auth: { user: process.env.EMAIL_SERVER_USER, pass: process.env.EMAIL_SERVER_PASSWORD, }, secure: true, }); const emailsDir = path.resolve(process.cwd(), 'emails'); const sendVerificationRequest = async ({ identifier, url }) => { console.log('send verification req entered') const emailFile = readFileSync(path.join(emailsDir, 'confirm-email.html'), { encoding: 'utf8', }); const emailTemplate = Handlebars.compile(emailFile); const result = await transporter.sendMail({ from: `"✨ FreiDrops" ${process.env.EMAIL_FROM}`, to: identifier, subject: 'Your sign-in link for FreiDrops', html: emailTemplate({ base_url: process.env.NEXTAUTH_URL, signin_url: url, email: identifier, }), }); const failed = result.rejected.concat(result.pending).filter(Boolean) if (failed.length) { throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`) } }; export default NextAuth({ pages: { signIn: '/', signOut: '/', error: '/', verifyRequest: '/' }, providers: [ EmailProvider({ maxAge: 10 * 60, // Magic links are valid for 10min!! sendVerificationRequest, }) ], adapter: PrismaAdapter(prisma) })
配置错误点与修复方案
你的配置确实存在3个会直接导致该问题的错误,逐一修复即可:
- EmailProvider必填配置缺失
未自定义发送函数时,NextAuth内置邮件逻辑会自动读取环境变量里的邮件服务、发件人配置完成初始化;你传入自定义sendVerificationRequest后,Provider不会再自动读取这些环境变量,缺失server和from必填项时会直接静默初始化失败,根本不会触发你写的发送函数。
修复方式:在EmailProvider配置中补全必填字段:EmailProvider({ maxAge: 10 * 60, // 魔术链接10分钟有效期 server: { host: process.env.EMAIL_SERVER_HOST, port: Number(process.env.EMAIL_SERVER_PORT), auth: { user: process.env.EMAIL_SERVER_USER, pass: process.env.EMAIL_SERVER_PASSWORD, }, secure: Number(process.env.EMAIL_SERVER_PORT) === 465, // 465端口走SSL设为true,587等STARTTLS端口设为false }, from: process.env.EMAIL_FROM, sendVerificationRequest, }) - 顶层同步操作错误被静默吞掉
你把模板目录路径解析、transporter初始化的逻辑都放在了API路由模块的顶层,这部分代码会在NextAuth路由初始化时执行。如果模板文件路径不对、文件不存在,或者nodemailer连接参数不匹配,抛出的同步错误会被Next.js API路由边界捕获后静默吞掉,直接导致整个NextAuth配置加载失败,点击登录不会有任何响应。
修复方式:将非全局必要的逻辑挪到sendVerificationRequest函数内部执行,同时提前确认emails文件夹会被正确打包到生产构建产物中,避免生产环境找不到文件:const sendVerificationRequest = async ({ identifier, url }) => { console.log('send verification req entered') // 把路径解析、模板读取挪到函数内部 const emailsDir = path.resolve(process.cwd(), 'emails'); const emailFile = readFileSync(path.join(emailsDir, 'confirm-email.html'), { encoding: 'utf8', }); const emailTemplate = Handlebars.compile(emailFile); const result = await transporter.sendMail({ from: `"✨ FreiDrops" ${process.env.EMAIL_FROM}`, to: identifier, subject: 'Your sign-in link for FreiDrops', html: emailTemplate({ base_url: process.env.NEXTAUTH_URL, signin_url: url, email: identifier, }), }); const failed = result.rejected.concat(result.pending).filter(Boolean) if (failed.length) { throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`) } }; - 端口参数类型错误
从环境变量读取的EMAIL_SERVER_PORT默认是字符串类型,nodemailer要求port字段为数字类型,类型不匹配会导致transporter初始化失败,错误同样会被静默吞掉。修复方式参考上面的代码,读取端口时用Number()做类型转换即可。
修复完成后重启服务,点击登录按钮就能看到你写的入口日志正常打印,后续再调试模板渲染、邮件发送的逻辑即可。
内容的提问来源于stack exchange,提问作者risky last
相关产品推荐
相关产品推荐

