Express 静态资源在 Railway 返回 404,如何提前发现部署遗漏?
这次问题的直接原因是 public 没有进入部署容器。修正 .gitignore 后,还需要将资源加入 Git、提交,然后重新部署。为了提前发现类似遗漏,可以在 CI 中检查关键文件是否被 Git 跟踪,并在应用启动时验证部署环境中是否存在这些文件。
为什么本地正常,部署后却返回 404?
本地磁盘上有 public,Express 因此能读取其中的文件。被 .gitignore 忽略的未跟踪文件不会通过普通的 Git 提交流程进入仓库。如果部署使用的提交不包含这些文件,容器中的 Express 就找不到它们。
下面的配置将 public 设为静态根目录:
app.use(express.static(path.join(__dirname, 'public')));
文件与 URL 的对应关系如下:
| 文件位置 | 请求 URL |
|---|---|
public/main.css | /main.css |
public/js/home.js | /js/home.js |
public/images/fireplace3.jpg | /images/fireplace3.jpg |
原有的 EJS 引用符合这个映射,无需额外加上 /public。绝对路径只能帮助 Express 定位目录,无法补回部署时缺失的文件;调整 MIME type 或 Helmet CSP 也解决不了文件缺失的问题。
先确认资源确实进入了提交
在项目根目录检查忽略规则:
git check-ignore -v public/main.css public/js/home.js public/images/fireplace3.jpg
如果输出了匹配规则,可以据此修改 .gitignore。没有输出只说明这些路径没有匹配到可报告的忽略规则,不能说明文件已经提交;默认情况下,已跟踪的文件也不会被这条命令报告。
修正规则后,添加并检查资源:
git add public
git diff --cached --name-status
git ls-files --error-unmatch public/main.css public/js/home.js public/images/fireplace3.jpg
确认暂存内容正确后,将资源与 .gitignore 的修改一起提交,再部署包含该提交的版本。git ls-files 检查的是索引中的跟踪状态;如果只执行了 git add 而没有提交,远端部署仍然拿不到这些文件。
用 ES modules 明确静态目录的位置
ES modules 没有直接提供 CommonJS 的 __dirname。如果项目已经自行定义了它,可以继续使用原写法;也可以通过 import.meta.url 直接解析静态目录。
下面的示例假设入口文件 server.js 与 public 位于同一目录。启动时会检查三个关键资源,缺失时直接报错:
import express from 'express';
import { access } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
const app = express();
const publicUrl = new URL('./public/', import.meta.url);
const publicDir = fileURLToPath(publicUrl);
const requiredAssets = [
'main.css',
'js/home.js',
'images/fireplace3.jpg',
];
for (const asset of requiredAssets) {
const assetPath = fileURLToPath(new URL(asset, publicUrl));
try {
await access(assetPath);
} catch (cause) {
throw new Error(`Required static asset is missing: ${assetPath}`, {
cause,
});
}
}
app.use(express.static(publicDir));
// 在这里注册业务路由。
app.use((req, res) => {
res.status(404).send('Not Found');
});
app.listen(process.env.PORT || 3000);
关键资源列表应根据项目实际情况维护。只检查 public 目录是否存在还不够,目录存在时,内部文件仍可能遗漏。
这个路径相对于入口模块的位置解析。如果实际入口是 src/server.js,而 public 位于项目根目录,就需要使用 ../public/。如果构建后入口变成 dist/server.js,也要根据部署产物中的目录结构重新确认路径。
把检查放进部署流程
对于题目中直接维护 CSS、JS 和图片的项目,可以采用以下流程:
- 将
public作为源文件目录纳入 Git,移除错误的忽略规则。 - 在 CI 的仓库检出步骤之后,执行前面的
git ls-files --error-unmatch ...,让未跟踪的关键资源导致检查失败。 - 在最终运行环境中检查文件是否存在,捕获路径错误或资源未被复制的问题。
- 部署后请求
/main.css、/js/home.js和一张关键图片,确认请求成功,且响应内容确实是对应资源。
Git 检查适合放在能够访问仓库元数据的 CI 阶段;启动检查则直接验证容器内的文件。两者检查的对象不同。
如果静态资源由构建工具生成,可以继续忽略生成目录,但部署构建必须生成这些文件,并将产物保留到最终运行环境。使用 Docker 或多阶段构建时,还需要检查 .dockerignore 和 COPY 是否遗漏了资源。
Railway 上需要专门的静态资源写法吗?
针对这次故障,普通的 express.static 配置就足够。可以继续使用相对于模块位置解析的目录,并将静态中间件放在最终的 404 处理器之前。
部署文件的约定需要明确:手工维护的资源随代码提交,构建生成的资源由构建流程产出。不要因为目录名是 public 就照搬其他框架的忽略规则。还要检查文件名大小写,确保 EJS 中的路径与实际文件完全一致。
备注:内容仅供参考。
