express.static 是用来提供某个文件夹中文件的内置中间件。app.use(express.static(path.join(__dirname, 'public'))) 让 public 中的每个文件都能通过 URL 中相同的路径访问:public/style.css 响应 /style.css,public/index.html 响应 /。Express 会根据文件扩展名设置 Content-Type;没有匹配的文件时,它会把请求交给你的路由,而不是返回 404。
GET /style.css 的响应就是这个文件的文本,在“标头”标签页中可以看到 Content-Type: text/css; charset=utf-8。发送 GET / 会得到 index.html,再发送 GET /api/time:没有这个文件,所以由路由来响应。
为什么文件夹路径需要 __dirname
express.static('public') 会相对于你启动 Node 时所在的文件夹解析 'public',而不是 server.js 所在的文件夹。在项目目录里运行 node server.js 时没问题,但如果进程管理器或某个脚本从别的地方启动应用,就会出错:
// project/server.js uses express.static('public')
// cd project && node server.js -> serves project/public
// node project/server.js -> looks for ./public, every file is 404
path.join(__dirname, 'public') 指的是包含这行代码的文件旁边的那个文件夹,无论 Node 从哪里启动。在 ES 模块中,使用 path.join(import.meta.dirname, 'public')(Node 20.11 及以上)。__dirname 和 path.join 的详细内容见 path 模块页面。
在前缀下提供文件
先传入一个挂载路径,就能把文件夹放在某个 URL 前缀下。这个前缀不属于文件在磁盘上的路径:
GET /assets/app.js 以 text/javascript 返回 console.log("hi");,GET /assets/img/dot.svg 则能访问子文件夹。不带前缀发送 GET /app.js:结果是 404,因为这些文件只存在于 /assets 下。用前缀可以避免文件 URL 和你的 API 路由冲突。
用 maxAge 设置缓存头
默认情况下,Express 发送 Cache-Control: public, max-age=0,所以浏览器每次使用文件前都会向服务器确认(借助 ETag 响应头,文件没变时会很快得到 304 Not Modified)。对于每次构建都会改名的文件,比如 app.3f9a1c.js,可以让浏览器把它们缓存下来:
打开“标头”标签页:Cache-Control 现在是 public, max-age=604800,也就是以秒计的七天。index: false 关闭了对文件夹 URL 提供 index.html 的功能。其他有用的选项还有 extensions: ['html'],它让 /about 能找到 about.html;以及 dotfiles,默认值是 'ignore',所以 .env 这样的文件永远不会被提供出去。
单页应用:回退到 index.html
为客户端路由构建的 React 或 Vue 应用,需要每个未知路径都返回 index.html,这样在 /settings 上刷新页面时才不会 404。把这个回退放在 express.static 和你的 API 路由之后:
app.use(express.static(path.join(__dirname, 'dist')));
app.use('/api', apiRouter);
// Express 5 wildcard syntax
app.get('/*splat', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
常见错误
在 URL 中带上文件夹名。 使用 app.use(express.static('public')) 时,URL 是 /style.css,而不是 /public/style.css。如果想让 URL 中出现 /public,就挂载它:app.use('/public', express.static(...))。
在 HTML 中用相对路径链接文件。 在 /blog/post 页面上,<link href="style.css"> 请求的是 /blog/style.css。让路径以斜杠开头:/style.css。
把机密放进静态文件夹。 里面的所有内容都是公开的。不要把配置文件、密钥和源代码放进去。
常见问题
可以多次使用 express.static 吗?
可以。每个文件夹注册一个;Express 会按添加顺序在这些文件夹中查找,并提供它找到的第一个文件。如果几个文件夹里可能有同名文件,就给每个文件夹设置自己的挂载路径。
生产环境中应该让 Express 提供静态文件吗?
小型应用这样做没问题。流量很大时,在前面放一个 CDN 或 Nginx 这样的反向代理,它们提供文件更快,还能在离用户更近的地方缓存文件,而 Express 只负责 API。
如何在路由中发送某个特定文件?
使用 res.sendFile(path.join(__dirname, 'files', 'report.pdf'))。路径必须是绝对路径(或者你传入 root 选项)。如果希望浏览器保存文件而不是显示它,就改用 res.download()。