基于 Express + Vite (React) 的 Node.js 项目部署至 ct8.pl (MyDevil) 踩 坑与实 践指南

基于 Express + Vite (React) 的 Node.js 项目部署至 ct8.pl (MyDevil) 踩 坑与实 践指南

ct8.pl(MyDevil 旗下的免费 Node.js 托管平台)底 层基于 FreeBSD 系统,并结合 Phusion Passenger 来管理和调度 Node.js 应用。由于其环境与常见的 Linux(Ubuntu/Debian) 搭配 Nginx + PM2 的部署模式存在较大差 异,在将使用 Vite 构建的现代 Node.js / React 全 栈应用部署上去时,极易遇到静态资源 挂载失败、模 块加载 崩 溃(ESM vs CommonJS)等问题。

本文总结了从最初的 npm start 报错到最终成功部署并运行完整的 Sing-box 订 阅管理平台的完整调试过程与实战教程。

一、 ct8.pl / MyDevil 托管环境 核心机制

在开始部署前,必须厘清 ct8.pl 的底 层运行 逻辑:

  1. Passenger 接管启动:ct8 并不直接运行 npm start,而是通过 Phusion Passenger 查 找项目根目录下的 app.js 文件并进行引导。
  2. 静态资源目录:Web 网关默认将请求直接映射到项目根目录下的 public/** 文件 夹。
  3. CommonJS 限制:Passenger 的底 层引导程序(node-loader.js)使用 require() 加载 app.js。如果项目配置了 ESM,会导致应用无法启动。
  4. FreeBSD 命令行:系统基于 FreeBSD,使用 sed 等命令行工具时参数与 Linux 存在 细微差 异。

二、 完整部署步 骤教程

步 骤 1:本地编译与打包

确保在本地或服务器项目根目录下完成了 Vite 前端与后端服务端的打包。

执行构建命令:

1
npm run build

执行后,构建系统应在根目录下生成 dist/ 文件 夹,其中包含:

  • dist/index.html 及 dist/assets/(前端静态文件)
  • dist/server.cjs(打包后的单文件 Node.js 服务端入口)

步 骤 2:配置 package.json(关 键)

由于 Passenger 使用 CommonJS 模式的 require() 加载引导文件,若 package.json 中带有 "type": "module",Node.js 会强制将 app.js 解 析为 ES Module, 抛出 ERR_REQUIRE_ESM 错误。

在 FreeBSD 终端中运行以下命令,将项目作用域转换为 commonjs

1
sed -i '' 's/"type": "module",/"type": "commonjs",/g' package.json

注:FreeBSD 的 sed -i 语法必须带有空单引号 ''

验证修改结果:

1
2
grep "type" package.json
#         输出应为: "type": "commonjs",

步 骤 3:配置静态资源目录 public

ct8 默认读取 public/ 目录中的文件作为对外暴 露的静态资源。将编译出的 dist 前端产物 覆 盖/ 链接至 public 文件 夹:

1
2
3
4
5
# 1. 删除默认的 public 占位目录
rm -rf public

# 2. 将编译出来的 dist 目录内容复制到 public
cp -r dist public

步 骤 4:编写 Passenger 引导文件 app.js

在项目根目录下创建 app.js,使 Passenger 能够正确引导并启动 dist/server.cjs 生产环境服务:

1
2
3
4
5
6
cat << 'EOF' > app.js
process.env.NODE_ENV = 'production';

//         引入打包好的 CommonJS 格式服务端文件
require('./dist/server.cjs');
EOF

步 骤 5:完善数据目录权限与环境变量

如果项目 涉及 SQLite 数据库或本地 JSON 数据写入(如 data/ 目录),需确保目录已创建且 赋权:

1
2
mkdir -p data
chmod -R 755 data

步 骤 6:重启 Node.js 应用

在 ct8.pl 环境中,无须通过 devil 命令行工具管理单个进程,只需通过 Passenger 的 tmp/restart.txt 机制 触发平 滑重启:

1
2
3
4
5
#         清空旧日志,便于观察
> ~/domains/sbs.ct8.pl/logs/error.log

#       触发 Passenger 重启进程
mkdir -p tmp && touch tmp/restart.txt

三、 常见报错与排查手册(Troubleshooting)

1. Error: Cannot find module ‘…/dist/server.cjs’

  • 原因:运行了 npm start 但项目未 执行构建,dist/ 目录不存在。
  • 解决:先运行 npm run build 生成构建产物。

2. Error: listen EPERM: operation not permitted 0.0.0.0:24678

  • 原因:server.ts 在生产环境下误启动了 Vite 的 HMR (Hot Module Replacement) 开发服务器或 WebSocket 监听。
  • 解决: 检查服务端入口 逻辑,确保在生产环境下只 挂载 express.static,不启动 createViteServer,并且显式指定 process.env.NODE_ENV = 'production'

3. Error [ERR_REQUIRE_ESM]: require() of ES Module app.js … not supported

  • 原因:Passenger 的 node-loader.js 使用 require() 加载 app.js,但 package.json 中配置了 "type": "module"
  • 解决:修改 package.json 中的 “type” 字段为 "commonjs"

4. 页面返回 500 Internal Server Error 且控制台报 Unexpected token ‘<’

  • 原因:Node.js 服务端 崩 溃,Nginx 返回了 HTML 格式的 500 错误页面,前端 尝试以 JSON 解 析该页面导致语法报错。
  •     排查:查看错误日志以定位具体   崩   溃位置:
    
1
tail -n 50 ~/domains/sbs.ct8.pl/logs/error.log

四、 总结流程图

整个部署排查 链条可总结为:

打包 (npm run build) └── 生成 dist/ (含前端静态文件与 server.cjs) ├── 将静态文件 覆 盖到 public/ (解决 404/默认占位页问题) ├── package.json 修改为 “type”: “commonjs” (解决 ERR_REQUIRE_ESM) ├── 编写 app.js 引导 require(’./dist/server.cjs’) ( 适配 Passenger) └── touch tmp/restart.txt ( 触发应用重启)

按此规范配置后,Vite + Express/Node.js 项目即可在 ct8.pl 虚 拟主机环境 稳定运行。

热爱生活 学无止境
使用 Hugo 构建
主题 StackJimmy 设计