Javascript
邮件验证码模板生产环境报错
问题现象
获取邮箱验证码时,生产环境报错:
Cannot destructure property 'templateName' of 'precompile(...)' as it is undefined
本地开发环境正常,本地执行 build 后也正常,只有上传到生产环境后触发。
后续查看 PM2 日志,看到更底层的真实错误:
Error: ENOENT: no such file or directory, open '/app/nest/dist/views/mail.hbs'
相关代码
邮件验证码发送位置:
await this.mailerService.sendMail({
to: acount,
subject: Mail.subject,
template: 'mail',
context: {
code,
},
})
邮件模板配置:
template: {
dir: join(process.cwd(), '/views'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
}
这里的模板目录依赖 process.cwd()。
根因
生产环境 PM2 配置中,Nest 服务这样启动:
{
name: "nest",
cwd: "/app/nest/dist/",
script: "main.js"
}
因此生产进程里的 process.cwd() 是:
/app/nest/dist
邮件模块配置 join(process.cwd(), '/views') 后,实际模板目录变成:
/app/nest/dist/views
所以发送 template: 'mail' 时,程序会读取:
/app/nest/dist/views/mail.hbs
但原来的 Nest 构建配置没有把项目根目录的 views 目录复制到 dist/views,导致生产环境缺少这个文件。
为什么表层错误看起来不像文件缺失
@nestjs-modules/mailer 的 Handlebars 适配器内部会先调用 precompile(...) 读取模板文件。
当模板文件不存在时,底层会触发:
ENOENT: no such file or directory
但适配器里 precompile(...) 在读取失败后没有返回正常对象,后续代码继续解构:
const { templateName } = precompile(...)
于是外层看到的错误变成:
Cannot destructure property 'templateName' of 'precompile(...)' as it is undefined
这个错误是二次包装后的表现,真正根因仍然是模板文件不存在。
为什么本地正常
本地运行时通常在项目根目录启动,process.cwd() 是项目根目录:
项目根目录
所以模板目录会解析到:
项目根目录/views
本地根目录本来就有:
views/mail.hbs
因此本地不会报错。
生产环境则是在 dist 目录作为工作目录启动,模板路径变成 dist/views/mail.hbs,而构建产物里原本没有这个文件。
最终修复
在 nest-cli.json 中增加构建资产复制配置:
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": [
{
"include": "../views/**/*",
"outDir": "dist/views"
}
],
"watchAssets": true
}
}
这样执行 nest build 后,会把:
views/mail.hbs
复制到:
dist/views/mail.hbs
与生产环境实际读取路径保持一致。
验证方式
本地构建:
pnpm build
或直接执行:
nest build
确认构建产物存在:
ls dist/views/mail.hbs
服务器部署后确认:
ls /app/nest/dist/views/mail.hbs
如果文件存在,当前这个 ENOENT /app/nest/dist/views/mail.hbs 问题即可消除。
排查经验
遇到模板相关的 precompile(...) is undefined 时,不要只看表层错误。应优先查看 PM2 或 Node 完整日志,找到是否存在更底层的:
ENOENT
然后重点确认:
- 生产环境
process.cwd()是什么 - 模板配置里的
dir最终解析到了哪里 - 构建产物里是否包含模板文件
- PM2 的
cwd和script是否改变了相对路径基准
这类问题本质上通常不是 Handlebars 模板语法错误,而是运行目录和构建产物目录结构不一致。
- 默认
- 回复数量
- 与我相关







