本文 AI 產出,尚未審核

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].jsstyle.[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

最佳實踐清單

  1. 統一管理靜態根目錄:在專案根目錄建立 public(或 static)資料夾,所有圖片、字型、編譯後的前端資源皆放此處。
  2. 使用 TypeScript 型別express.static 回傳 express.RequestHandler,在 app.use 時可直接使用,保持型別安全。
  3. 設定合理的快取:開發環境使用 noCache,正式環境使用 maxAge。可透過 NODE_ENV 判斷。
  4. 結合 CDN:靜態檔案放到 CloudFront、Azure CDN、Google Cloud CDN,Express 只負責 API。
  5. 自動化建構:使用 webpackVite 等工具在建置時自動產生 hash 檔名,避免手動更改。

實際應用場景

  1. 單頁應用(SPA)

    • 前端使用 React/Vue/Angular 編譯成 index.htmlbundle.jsstyles.css
    • Express 只需 app.use(express.static('dist')),再提供一條 * 路由返回 index.html,讓前端路由自行處理。
  2. 多語系網站

    • 每個語系的圖片、字體放在 public/zh, public/en 等子目錄。
    • 透過 app.use('/zh', express.static('public/zh'))app.use('/en', express.static('public/en')) 讓 URL 自然對應。
  3. 檔案下載服務

    • 把需要授權的檔案放在 private 目錄,使用 res.downloadres.sendFile 搭配驗證中介軟體。
    • 靜態檔案只服務公開資源,如說明文件、Logo 圖片等。
  4. 企業內部儀表板

    • 靜態檔案(圖表套件、CSS)放在 public,同時設定 strict-transport-securityhelmet 等安全中介軟體。

總結

提供靜態檔案是任何 Web 應用的基礎功能。透過 Express 的 express.static 中介軟體,我們可以:

  • 快速 把本機目錄映射為 HTTP 資源。
  • 彈性 設定 URL 前綴、快取策略與安全檢查。
  • 無縫Template Engine(EJS、Pug、Handlebars)結合,讓前端資源在伺服器端渲染時保持一致。

在實務開發中,建議遵循以下要點:

  1. 使用絕對路徑 (path.resolve) 避免相對路徑錯誤。
  2. 先掛載 express.static 再定義自訂路由,確保請求正確走到靜態服務。
  3. 設定適當的快取maxAgeimmutable)並搭配 hash 命名,減少資源重載。
  4. 嚴格分離公開與私密檔案,避免機密資訊外流。
  5. 結合 CDN、自動化建構流程,提升效能與維運效率。

掌握以上概念與實作範例,你就能在 Express + TypeScript 的專案裡,安全、有效率地提供各式靜態資源,為前端開發者打造順暢的開發與使用體驗。祝開發順利!