Void 的构建脚本和部署配置得对齐、不然本地能跑线上起不来

文章导读
本地能跑、部署后进程起不来或页面白屏,多数情况不归结于业务代码,而是产物路径、启动命令里的工作目录、静态目录映射这三处中,至少有一处只在某一种工作目录下成立。判断的顺序建议固定下来:先确认构建实际输出到哪,再确认启动命令在线上被解析成哪个绝对路径,最后确认静态资源请求落到磁盘上的哪个目录。这三步都能用配置、ls、日志或状态码验证,不需要猜。
📋 目录
  1. Ⅰ 从构建脚本里读出产物目录的准确路径
  2. Ⅱ 对照部署配置里的启动命令与工作目录
  3. Ⅲ 检查静态资源目录的映射关系
  4. Ⅳ 在干净环境或容器里复现一次启动
A A

本地能跑、部署后进程起不来或页面白屏,多数情况不归结于业务代码,而是产物路径、启动命令里的工作目录、静态目录映射这三处中,至少有一处只在某一种工作目录下成立。判断的顺序建议固定下来:先确认构建实际输出到哪,再确认启动命令在线上被解析成哪个绝对路径,最后确认静态资源请求落到磁盘上的哪个目录。这三步都能用配置、ls、日志或状态码验证,不需要猜。

遇到本地正常、线上异常时,先不要改业务逻辑。把构建产物的输出目录写成显式字符串,用 ls 确认;把启动命令改成绝对路径并核对工作目录;用一次具体的静态文件请求看返回状态码,区分是路由没命中还是文件不存在;最后在干净容器里复现一次启动。适用场景是同一份代码在两种工作目录下运行,验证方式是命令输出和日志行,边界是只对本次部署有效,环境变量与权限问题需要另行确认。

从构建脚本里读出产物目录的准确路径

构建工具的输出目录有默认值,默认值会随工具版本、构建模式甚至配置文件合并顺序变化。可靠的做法是在构建配置里把输出目录写成唯一的显式字符串,再让部署配置引用同一个字符串。以下片段是通用示意,字段名按你项目实际的构建工具替换。

// 构建配置片段(示意)
build: {
  outDir: 'dist/web',   // 相对项目根目录,不要留空依赖默认值
  emptyOutDir: true
}

构建完成后立刻查看实际落盘结果,而不是看配置文件:

$ ls -la dist/web
total 24
drwxr-xr-x  assets
-rw-r`--r--`  index.html
-rw-r`--r--`  favicon.ico

判断标准很直接:部署配置里引用的目录,必须能在这个 ls 结果里逐字对上。如果 ls 出来是 dist 下直接躺着 index.html,而部署配置写的是 dist/web,说明 outDir 被别的配置文件或命令行参数覆盖过,先去仓库里搜同名配置项,再确认构建命令有没有带 `--outDir` 之类的覆盖参数。建议构建脚本在结束时打印一次产物根目录,让日志里留下可核对的字符串。

Void 的构建脚本和部署配置得对齐、不然本地能跑线上起不来

对照部署配置里的启动命令与工作目录

相对路径的解析基准是进程的工作目录,不是配置文件所在目录,也不是脚本文件所在目录。下面这条启动命令在两种工作目录下会指向两个完全不同的文件:

# 部署配置里常见的写法
node server/index.js
  • 工作目录为 /srv/app:解析为 /srv/app/server/index.js,正常启动。
  • 工作目录为 /(systemd 未设置 WorkingDirectory、容器未设 WORKDIR、或在根目录执行命令):解析为 /server/index.js,通常是 MODULE_NOT_FOUND 或 ENOENT。

规避方式有两层:启动命令用绝对路径,例如 node /srv/app/server/index.js 或容器内的 node /app/server/index.js;同时把进程内所有读写文件的路径改成以脚本自身位置为基准拼接,例如 path.resolve(__dirname, '../dist/web'),而不是直接写 './dist/web'。绝对路径的启动命令解决进程起不来的问题,基于 __dirname 的拼接解决进程起来但读不到文件的问题,两件事都要做。

产物路径与启动命令的对照表可以先在本机填一遍,再逐项对到线上配置:

项目          本机常见值              线上需要确认的值
产物目录      dist/web               部署配置引用的目录是否同名
入口文件      server/index.js        部署配置的路径是否为绝对路径
启动命令      node server/index.js   WorkingDirectory / WORKDIR 是什么
静态根目录    dist/web                运行时解析出的绝对路径是什么

线上部署配置(systemd 单元、进程管理工具配置、容器编排文件)里任何一项留空,都会落回默认值,默认值往往和开发时的习惯不一致,这一项需要结合环境逐条确认。

Void 的构建脚本和部署配置得对齐、不然本地能跑线上起不来

检查静态资源目录的映射关系

页面白屏和进程起不来是两类问题。进程已经监听端口但页面空白,通常静态目录映射把请求指到了不存在的目录,返回 404,而 index.html 之外的路由又回落到空白页。静态目录同样建议用基于脚本位置的绝对路径声明:

// 静态目录配置片段(示意,按你的服务框架替换 API)
app.use(express.static(path.resolve(__dirname, '../dist/web')))

验证不要靠刷新首页,首页可能是服务端渲染或缓存返回的。直接请求一个具体的静态文件并只看状态码:

$ curl -o /dev/null -s -w "%{http_code}\n" https://your-host/assets/index-abc123.js
200

把文件名换成构建产物 assets 目录里真实存在的那个。200 说明映射正确;404 而首页仍能打开,说明静态目录指错或构建时资源前缀(base / publicPath)与服务实际路径不一致;403 则要检查目录权限和运行用户。首页返回 200 而 assets 返回 404,是白屏最常见的组合,优先查这一项。

Void 的构建脚本和部署配置得对齐、不然本地能跑线上起不来

在干净环境或容器里复现一次启动

本机成功不代表部署成功,本机的全局依赖、残留的 node_modules、缓存目录都会掩盖问题。最小复现步骤是在一个没有预装依赖的容器里,按部署配置的方式启动:

$ docker run `--rm` -it -v "$PWD":/app -w /app node:20-bookworm-slim bash
# npm ci
# npm run build
# ls -la dist/web
# node /app/server/index.js

启动成功时,日志里应当出现监听地址和静态根目录这两类行,例如 Server listening on 0.0.0.0:3000 和打印出来的静态目录绝对路径。看到静态目录打印成 /dist/web 或 /app/dist 而不是实际存放产物的那个路径,就能当场定位到是工作目录拼接方式的问题。

启动失败时先看日志的第一行错误,而不是最后一行。第一行通常给出模块解析失败的具体路径或权限拒绝的具体目录,最后几行往往只是退出码和框架的包装信息。把第一行里的绝对路径与上一步 ls 得到的路径逐字比对,差别通常就是漏掉的 ../、多写的一层目录名,或者容器内挂载点与镜像内路径不一致。