ExpressJS (TypeScript) – 靜態檔案與模板引擎
單元:Static Files 與 Template Engines
主題:提供靜態檔案
簡介
在 Web 開發中,靜態檔案(HTML、CSS、JavaScript、圖片、字型等)是使用者瀏覽器最先請求的資源。若沒有妥善的靜態檔案服務,前端頁面將無法正確渲染,使用者體驗會大幅下降。
Express 是 Node.js 最流行的 Web 框架之一,提供了簡潔且可擴充的 API 來處理靜態檔案,同時也能與各種 Template Engine(如 Pug、EJS、Handlebars)無縫結合,讓伺服器端渲染變得輕鬆。
本篇文章以 TypeScript 為基礎,說明如何在 Express 中設定靜態目錄、設定快取、保護敏感檔案,以及如何在模板引擎中引用這些資源。適合剛入門的開發者,也能讓已有 Node/Express 經驗的同學快速掌握最佳實踐。
核心概念
1. express.static 中介軟體
express.static 是 Express 內建的中介軟體,用來把指定的資料夾暴露為靜態資源。
import express from 'express';
import path from 'path';
const app = express();
// 把 ./public 目錄設為靜態根目錄
app.use(express.static(path.join(__dirname, 'public')));
- 路徑解析:使用
path.join(__dirname, 'public')可避免不同作業系統的路徑分隔符問題。 - 掛載順序:
express.static必須在其他路由之前掛載,否則請求會先被自訂路由攔截。
2. 自訂靜態路徑前綴
有時候想要讓靜態檔案放在特定的 URL 前綴(例如 /assets),只要在 app.use 時傳入路徑字串即可。
app.use('/assets', express.static(path.join(__dirname, 'public')));
此時,public/css/style.css 會被映射為 https://yourdomain.com/assets/css/style.css。
3. 快取與效能優化
瀏覽器快取可以大幅降低重複請求。Express 允許透過 maxAge 選項設定快取時間(單位毫秒)。
app.use(
'/static',
express.static(path.join(__dirname, 'public'), {
maxAge: '30d', // 30 天快取
immutable: true, // 告訴瀏覽器檔案不會變動
etag: false, // 關閉 ETag(視需求而定)
})
);
maxAge:可以使用字串('1h'、'30d')或毫秒數。immutable:配合長期快取的檔案(如哈希命名的 bundle)使用,可減少 revalidation。
4. 防止敏感檔案外流
express.static 只會提供目錄下的檔案,但如果目錄結構不當,仍有可能洩漏不該公開的檔案。
- 建議:將
public目錄與程式碼、設定檔、環境變數等分離。 - 示範:把
src/secret.txt放在根目錄外,確保express.static不會覆蓋。
5. 結合 Template Engine
在使用模板引擎時,常需要在 HTML 中插入靜態資源的 URL。以下以 EJS 為例:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<!-- 直接使用 /assets 前綴 -->
<link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
<h1><%= heading %></h1>
<script src="/assets/js/app.js"></script>
</body>
</html>
在 Express 設定 EJS:
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
只要 app.use('/assets', express.static(...)) 正確掛載,模板內的路徑即可正常工作。
程式碼範例
下面提供 5 個實用範例,涵蓋從最基本到進階的靜態檔案服務需求。每段程式碼均加上說明註解,方便讀者直接 copy‑paste。
範例 1:最簡靜態目錄
// server.ts
import express from 'express';
import path from 'path';
const app = express();
const PORT = 3000;
// 將 ./public 設為靜態根目錄
app.use(express.static(path.resolve(__dirname, 'public')));
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
說明:只要把
public資料夾放在與server.ts同層,所有檔案即可直接以/檔名訪問。
範例 2:自訂 URL 前綴
// 把 ./static 資料夾掛載在 /assets 前綴
app.use('/assets', express.static(path.resolve(__dirname, 'static')));
使用情境:前端團隊習慣用
/assets/...組織資源,後端只需要一行設定即可。
範例 3:設定長期快取與不可變檔案
app.use(
'/static',
express.static(path.resolve(__dirname, 'public'), {
// 30 天快取,適合使用 webpack hash 命名的 bundle
maxAge: '30d',
immutable: true,
// 關閉 ETag,減少回傳標頭大小(視需求決定)
etag: false,
})
);
最佳化:將
bundle.[hash].js、style.[hash].css放在此目錄,可讓瀏覽器長時間快取,降低 CDN 負載。
範例 4:結合 EJS 模板引擎
// 設定 EJS
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, 'views'));
// 路由範例
app.get('/', (req, res) => {
res.render('index', {
title: 'Express 靜態檔案示例',
heading: '歡迎使用 Express + TypeScript',
});
});
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
<h1><%= heading %></h1>
<script src="/assets/js/app.js"></script>
</body>
</html>
說明:模板內直接使用
/assets前綴,與app.use('/assets', …)完全對應。
範例 5:保護敏感檔案 + 404 錯誤處理
// 防止 .env、*.config 等檔案被直接下載
app.use((req, res, next) => {
const forbidden = ['/secret.txt', '/.env'];
if (forbidden.includes(req.path)) {
return res.status(403).send('Forbidden');
}
next();
});
// 靜態檔案服務
app.use(express.static(path.resolve(__dirname, 'public')));
// 404 處理
app.use((req, res) => {
res.status(404).send('找不到您要的資源');
});
重點:在靜態中介軟體之前插入自訂檢查,可避免意外外流;最後的 404 處理則讓使用者得到友善訊息。
常見陷阱與最佳實踐
| 陷阱 | 為何會發生 | 解決方式 |
|---|---|---|
| 路徑錯誤 | 使用相對路徑 ('./public') 時,執行目錄不同會找不到檔案。 |
使用 path.resolve(__dirname, 'public') 或 path.join。 |
| 靜態檔案被路由攔截 | 自訂路由寫在 express.static 前面,導致請求不會走到靜態中介軟體。 |
確保 app.use(express.static(...)) 放在所有自訂路由之前。 |
| 快取過期 | 部署新版本時,使用者仍看到舊的 CSS/JS。 | 使用 hash 命名(bundle.abc123.js)結合 immutable: true。 |
| 敏感檔案外流 | 把 .env、資料庫備份等放在 public 目錄下。 |
永遠不要 把機密檔案放在靜態根目錄;使用 .gitignore、環境變數管理。 |
| 跨來源資源共享 (CORS) | 前端在不同子域名載入靜態資源會被瀏覽器阻擋。 | 若需要跨域,使用 cors 中介軟體或在 CDN 設定 Access-Control-Allow-Origin。 |
最佳實踐清單
- 統一管理靜態根目錄:在專案根目錄建立
public(或static)資料夾,所有圖片、字型、編譯後的前端資源皆放此處。 - 使用 TypeScript 型別:
express.static回傳express.RequestHandler,在app.use時可直接使用,保持型別安全。 - 設定合理的快取:開發環境使用
noCache,正式環境使用maxAge。可透過NODE_ENV判斷。 - 結合 CDN:靜態檔案放到 CloudFront、Azure CDN、Google Cloud CDN,Express 只負責 API。
- 自動化建構:使用
webpack、Vite等工具在建置時自動產生 hash 檔名,避免手動更改。
實際應用場景
單頁應用(SPA)
- 前端使用 React/Vue/Angular 編譯成
index.html、bundle.js、styles.css。 - Express 只需
app.use(express.static('dist')),再提供一條*路由返回index.html,讓前端路由自行處理。
- 前端使用 React/Vue/Angular 編譯成
多語系網站
- 每個語系的圖片、字體放在
public/zh,public/en等子目錄。 - 透過
app.use('/zh', express.static('public/zh'))、app.use('/en', express.static('public/en'))讓 URL 自然對應。
- 每個語系的圖片、字體放在
檔案下載服務
- 把需要授權的檔案放在
private目錄,使用res.download或res.sendFile搭配驗證中介軟體。 - 靜態檔案只服務公開資源,如說明文件、Logo 圖片等。
- 把需要授權的檔案放在
企業內部儀表板
- 靜態檔案(圖表套件、CSS)放在
public,同時設定strict-transport-security、helmet等安全中介軟體。
- 靜態檔案(圖表套件、CSS)放在
總結
提供靜態檔案是任何 Web 應用的基礎功能。透過 Express 的 express.static 中介軟體,我們可以:
- 快速 把本機目錄映射為 HTTP 資源。
- 彈性 設定 URL 前綴、快取策略與安全檢查。
- 無縫 與 Template Engine(EJS、Pug、Handlebars)結合,讓前端資源在伺服器端渲染時保持一致。
在實務開發中,建議遵循以下要點:
- 使用絕對路徑 (
path.resolve) 避免相對路徑錯誤。 - 先掛載
express.static再定義自訂路由,確保請求正確走到靜態服務。 - 設定適當的快取(
maxAge、immutable)並搭配 hash 命名,減少資源重載。 - 嚴格分離公開與私密檔案,避免機密資訊外流。
- 結合 CDN、自動化建構流程,提升效能與維運效率。
掌握以上概念與實作範例,你就能在 Express + TypeScript 的專案裡,安全、有效率地提供各式靜態資源,為前端開發者打造順暢的開發與使用體驗。祝開發順利!