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 への返事はファイルのテキストで、Headersタブには Content-Type: text/css; charset=utf-8 が表示されます。GET / を送ると index.html が返り、続けて GET /api/time を送ると、そういうファイルはないのでルートが答えます。
フォルダのパスに__dirnameが必要な理由
express.static('public') は、'public' を server.js があるフォルダではなく、Node.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.jsがどこで起動されても、その行を含むファイルの隣のフォルダを指します。ESモジュールでは path.join(import.meta.dirname, 'public') を使います(Node 20.11以降)。__dirname と path.join についてはpathモジュールのページで詳しく扱っています。
プレフィックスの下でファイルを配信する
最初にマウントパスを渡すと、フォルダをURLのプレフィックスの下で配信できます。プレフィックスはディスク上のファイルのパスには含まれません。
GET /assets/app.js は console.log("hi"); を text/javascript として返し、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 のようにビルドのたびに名前が変わるファイルなら、ブラウザに保持させましょう。
Headersタブを開くと、Cache-Control は public, max-age=604800、つまり7日を秒で表した値になっています。index: false は、フォルダのURLに対する index.html の配信をオフにします。ほかに役立つオプションとして、/about で about.html を見つけられるようにする extensions: ['html'] と、デフォルトが 'ignore' で .env のようなファイルを決して配信しない dotfiles があります。
シングルページアプリ:index.htmlへのフォールバック
クライアントサイドのルーティングを使うReactやVueのアプリでは、/settings でリロードしても404にならないように、未知のパスにはすべて index.html を返す必要があります。フォールバックは 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は /public/style.css ではなく /style.css です。URLに /public を入れたいなら、マウントします。app.use('/public', express.static(...)) です。
HTMLで相対パスを使ってファイルを読み込む。 /blog/post のページにある <link href="style.css"> は /blog/style.css を要求します。パスはスラッシュで始めましょう。/style.css です。
静的フォルダに秘密情報を置く。 その中身はすべて公開されます。設定ファイル、キー、ソースコードは入れないでください。
よくある質問
express.staticを複数回使えますか?
使えます。フォルダごとに1つずつ登録すると、Expressは追加した順に探して最初に見つかったファイルを配信します。フォルダに同じ名前のファイルが入る可能性があるなら、それぞれに別のマウントパスを付けましょう。
本番でExpressに静的ファイルを配信させるべきですか?
小さなアプリなら問題ありません。アクセスが多いなら、CDNやNginxのようなリバースプロキシを前に置きましょう。ファイルをより速く配信し、ユーザーの近くでキャッシュしてくれます。ExpressはAPIに専念させます。
ルートから特定のファイルを1つ送るには?
res.sendFile(path.join(__dirname, 'files', 'report.pdf')) を使います。パスは絶対パスでなければなりません(または root オプションを渡します)。ブラウザに表示させずに保存させたいなら、代わりに res.download() を使います。