记一次把 Nest.js 部署到 Vercel 的避坑经历

记一次把 Nest.js 部署到 Vercel 的避坑经历

2026-01-30
VercelNest.js
AI 智能概括

将Nest.js后台项目部署到Vercel上,不仅需要编写一个专门的入口文件,还需要在vercel.json中配置路由重写。此外,还需要注意Git提交时的邮箱问题,以确保Vercel能够正确识别并部署代码

Powered by DeepSeek

说实话,用 Vercel 部署前端项目(Next.js/Vue)非常的丝滑,但我最近突发奇想,想把我的 Nest.js 后台项目(Nest-Admin)也放上去。毕竟是 Vercel嘛,都知道的,羊毛不用白不用,Serverless 模式对于个人项目来说既省钱又不用维护服务器。

本以为也就是部署个项目的事,结果……一顿操作猛如虎,一看日志全飘红,关键网站还报404、500。折腾了一下午,总算把这几个坑都填平了。

第一步:不仅是 main.ts,还需要一个"替身"

Nest.js 本地开发通常是用 src/main.ts 启动一个 HTTP 服务监听端口(比如 3000)。但 Vercel 是 Serverless 环境,逻辑有所不同,它不干这种“长期监听”的事,它需要的是一个函数入口。

所以,直接把项目丢上去是跑不起来的。我们需要给 Vercel 专门写一个入口文件,把 Nest 应用“伪装”成一个 Express 函数。

api-index

我在根目录下新建了一个 api 文件夹(和src同级),写了个 index.ts

typescript
import { NestFactory, Reflector } from "@nestjs/core";
import { ExpressAdapter } from "@nestjs/platform-express";
import { AppModule } from "../src/app.module";
import express from "express";
import type { Express, Request, Response } from "express";
 
// 这里引入各种全局拦截器、守卫
import { JwtAuthGuard } from "../src/module/auth/jwt-auth.guard";
import { CustomExceptionFilter } from "../src/common/filters/custom-exception.filter";
import { ResponseInterceptor } from "../src/common/interceptors/response.interceptor";
 
const server = express();
 
export const createNestServer = async (expressInstance: Express) => {
  // 使用 ExpressAdapter 适配器
  const app = await NestFactory.create(
    AppModule,
    new ExpressAdapter(expressInstance),
  );
 
  // 把 main.ts 里的全局配置复刻一份
  app.enableCors({
    origin: [
      "http://localhost:3000",
      "https://www.czhlove.cn",
      // ...
    ],
    methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
    credentials: true,
  });
 
  app.setGlobalPrefix("api");
 
  // 如果不加这几行,你的接口返回格式就是乱的,或者鉴权失效
  const reflector = app.get(Reflector);
  app.useGlobalGuards(new JwtAuthGuard(reflector));
  app.useGlobalFilters(new CustomExceptionFilter());
  app.useGlobalInterceptors(new ResponseInterceptor());
 
  await app.init();
  return app;
};
 
// 防止冷启动重复初始化
const appPromise = createNestServer(server);
 
// 导出 Vercel 需要的 Handler
export default async function handler(req: Request, res: Response) {
  await appPromise;
  server(req, res);
}

第二步:配置 vercel.json

有了入口文件,还得告诉 Vercel 怎么走。之前我不小心抄了个旧版本的配置(带 builds 字段的),结果部署的时候 Build 过程直接被跳过了,根本没编译。

现在的标准写法非常简单,只需要用 rewrites 把流量引过去就行,并且注意version版本一定要大于等于2:

JSON
// ❌ 旧版本-不支持
{
  "version": 2,
  "builds": [
    {
      "src": "src/main.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/main.js",
      "methods": ["GET", "POST", "PUT", "DELETE", "PATCH"]
    }
  ]
}
JSON
// ✅新版本-支持
{
  "version": 2,
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/api/index"
    }
  ]
}

踩坑一:Git 提交后,Vercel没自动部署?

这个坑有点无语。我改好了代码推送到 GitHub,结果 Vercel 那边静悄悄的,根本没触发部署。最开始不得不重新删除项目再构建...

github-error

查了半天才发现,因为我这台电脑是用的公司的,Git 的 user.email 是公司账户的,跟我的 GitHub 账号邮箱不一致。GitHub 虽然收到了代码,但 Vercel 认为“来源不明”或者没有匹配到账号,直接忽略了。

解决: 修改本地配置,并修正上一次提交的作者信息:

Bash
git config user.email "GitHub邮箱@xx.com"
git commit --amend --reset-author --no-edit
git push -f

推上去后,Vercel 终于动了!

踩坑二:Cannot find module 'express'

代码推上去后,构建绿了,但访问接口直接报 500 Server Error。点开 Runtime Logs 一看,人傻了:

图片!:

Cannot find module 'express'

我很纳闷,Nest.js 底层不就是 Express 吗?本地跑得好好的。

原因: Vercel 的运行环境非常纯净。虽然 Nest 内部依赖了 Express,但在 package.json 的 dependencies 里如果没有显式声明 express,Vercel 打包的时候可能就没把它算进去(或者被当做 devDependencies 剔除了)。

解决: 暴力且有效。

Bash
pnpm add express

确保它放在 package.json 的 dependencies 里。

踩坑三:Linux 下的绝对路径"噩梦"

解决了 Express 的问题,日志里又爆出了新错误:

Cannot find module 'src/common/qiniu/qiniu.service'

github-error

这又是一个经典的“本地没问题,上线‘火葬场’”。我在代码里使用 src/common/... 这种绝对路径来引入模块。在 VSCode 里配合 tsconfig 确实很爽,但在 Vercel 的 Linux 环境下,编译后的 JS 找不到这个 src 目录,它会以为这是个第三方包。

解决: 老老实实把报错文件的引用路径改成相对路径,类似的路径都需要修改:

typescript
// ❌ 错误写法
import { QiniuService } from "src/common/qiniu/qiniu.service";
typescript
// ✅ 正确写法
import { QiniuService } from "../../common/qiniu/qiniu.service";

虽然改起来有点烦,但这最稳妥,不建议在 Serverless 环境去折腾路径别名映射。

踩坑四:接口返回数据“裸奔”

好不容易接口通了,但我发现返回的数据不对劲。我本地配置了全局拦截器,返回格式应该是标准的 { status: 1, msg: "Success", data: {...} }

但线上返回的却是直接的 { token: "..." },而且也没校验权限。

原因: 这就回到了第一步提到的 api/index.ts。Vercel 运行的是这个新入口,它完全不知道你在 src/main.ts 里写的那些 app.useGlobalInterceptors(...)。它就是一个崭新的、纯洁的 Nest 实例。

解决: 把 main.ts 里的全局配置(Guard, Filter, Interceptor)全部复制粘贴到 api/index.ts 里。(代码见文章开头)。

以上就是利用 Vercel 的 Serverless 能力免费托管 Nest.js 应用,如果你也在搞 Vercel + Nest.js的话,希望这篇文章对您有所帮助。


发布于 2026-01-30