node.js 的網頁部署架構使用 Nginx 託管靜態檔案

這兩者代表了兩種完全不同的網頁部署架構。簡單來說,一個是讓 Node.js 自己兼任管理員去傳送檔案,另一個則是請專業的 Nginx 管理員來處理。

以下是詳細的差異分析:

1. 使用 Node.js 直接執行 (CMD [“node”, “server.js”])

這種方式通常用於動態網站SSR (伺服器端渲染)

  • 運作原理:Node.js 啟動一個 Web Server(如 Express),當使用者請求網頁時,Node.js 負責讀取硬碟裡的檔案並回傳給瀏覽器。
  • 優點:架構簡單,適合需要處理 API、資料庫連線或動態內容的應用(例如 Next.js 的開發模式)。
  • 缺點:Node.js 在處理「靜態檔案」(如圖片、JS、CSS)的效率不如專業的伺服器。在高併發的情況下,Node.js 的效能會被這些瑣碎的檔案讀取佔用,導致 API 回應變慢。

2. 使用 Nginx 託管靜態檔案 (使用 Nginx 映像檔)

這種方式是 SPA (單頁式應用,如 Vite / React / Vue) 的標準部署做法。

  • 運作原理
    1. 在編譯階段(build-stage),Node.js 把程式碼轉換成純粹的 index.htmljscss(放在 dist 目錄)。
    2. 在執行階段,我們直接把這些靜態檔案丟給 Nginx。
    3. Nginx 是一個極度優化的網頁伺服器,它只負責一件事情:把硬碟裡的靜態檔案快速傳給使用者。
  • 優點
    • 效能極高:Nginx 處理靜態資源的速度遠快於 Node.js。
    • 佔用資源極小alpine 版的 Nginx 映像檔非常輕量。
    • 安全性:Nginx 作為第一線,可以處理 SSL、負載平衡與基礎的安全性過濾。
  • 缺點:它無法執行 JavaScript 後端程式碼(不能直接連資料庫),只能單純傳送檔案。

為什麼你的案例建議用 Nginx?

因為你的專案是由 Vite 產出的 前端 React 專案

當你執行 npm run build 後,你的程式碼已經變成了不需要 Node.js 環境也能執行的「純檔案」。在生產環境(如 Azure)中,使用 Nginx 託管這些檔案會比跑一個完整的 Node.js 伺服器更穩定且更省資源。

總結對比表

特性Node.js (CMD [“node”, …])Nginx (CMD [“nginx”, …])
主要用途動態運算、API 開發、SSR 渲染靜態檔案傳輸 (React/Vue/Vite)
執行內容執行 JS 邏輯並回傳結果尋找檔案並傳送出去
效能一般,適合運算優異,適合高流量下載靜態檔
映像檔大小較大 (包含 Node 執行環境)極小 (僅 Nginx 環境)

由於你的目標是部署到 Azure App Service for Containers,使用 Nginx 的版本會讓你的容器啟動更快,且在高負載時表現更穩。


這是一個非常關鍵的架構問題。簡單來說:Nginx 本身不能執行 Node.js 代碼,它只是一個搬運工,負責把編譯好的 HTML/JS 檔案傳給瀏覽器。

如果你需要存取 Azure SQL Server,這代表你的程式碼包含資料庫連線字串、帳號密碼或受控識別邏輯,這些絕對不能跑在瀏覽器(前端)執行,否則任何人都能看到你的資料庫密碼。

要達成你的需求,通常有以下兩種解決方案:

方案一:前後端分離架構 (最標準)

你會建立兩個容器,或者在一個容器內跑兩個服務:

  1. 前端 (Nginx):負責顯示介面。當使用者點擊按鈕,瀏覽器會發送一個 API 請求(例如 GET /api/data)。
  2. 後端 (Node.js):這是一個獨立的 API 伺服器(使用 Express 或 Fastify)。它接收來自前端的請求,去跟 Azure SQL Server 要資料,再把結果回傳給前端。

方案二:全疊代 (Full-stack) 模式 (最簡單)

如果你不想維護兩套服務,那就不要使用 Nginx

改回使用 CMD ["node", "server.js"],並在 Node.js 中使用像是 Express 的框架:

  • 靜態檔案:讓 Node.js 兼差,用 app.use(express.static('dist')) 來傳送網頁檔案。
  • 資料存取:在同一個專案中撰寫 app.get('/api/data', ...) 邏輯來連線 SQL Server。

我該選哪一個?

比較項目方案一 (Nginx + Node API)方案二 (純 Node.js)
安全程度極高 (後端完全隱藏)高 (需正確配置)
效能優秀 (Nginx 處理靜態資源極快)一般
開發難度稍高 (需處理跨網域 CORS)簡單 (前後端都在同個 Port)
Azure 部署適合 App Service for Containers適合 App Service for Containers

給你的實務建議

由於你是從 AI Studio 產生檔案,且目標是 Azure App Service for Containers,我建議先採用 方案二 (純 Node.js)

Dockerfile 調整方向:

  1. 在編譯階段跑 npm run build
  2. 在執行階段使用 node:20-alpine
  3. 你的 server.js 必須包含以下兩部分:
    • 託管靜態檔app.use(express.static(path.join(__dirname, 'dist')))
    • SQL 連線:使用我第一則回覆提供的 mssql 連線邏輯。

這會產生一個單一的 Docker Image,裡面既有網頁畫面,也能安全地連線資料庫。


既然目前還沒有 server.js 或 API 程式碼,且未來預計會有,我建議你現在先採用 Node.js 基礎的 Docker 打包方式

這樣做的好處是,當你未來寫好資料庫連線邏輯時,不需要更換 Docker 基礎環境,只要在 Node.js 專案裡增加一個 server.js 檔案即可。

以下是針對你目前情況的建議步驟:

1. 準備一個簡單的 Node.js 伺服器

在你的專案根目錄 my-ai-app 下,建立一個名為 server.js 的檔案。這支程式會同時負責「顯示網頁」與「預留 API 空間」:

JavaScript

const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 8080;

// 1. 託管靜態檔案(Vite 編譯後的結果)
app.use(express.static(path.join(__dirname, 'dist')));

// 2. 預留 API 路由空間(未來存取 SQL Server 的地方)
app.get('/api/data', (req, res) => {
    // 未來在這裡寫 mssql 連線邏輯
    res.json({ message: "未來這裡會回傳 SQL 資料" });
});

// 3. 確保前端路由正常(SPA 必備)
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

app.listen(port, () => {
    console.log(`伺服器已啟動:http://localhost:${port}`);
});

備註:請執行 npm install express 安裝必要的套件。

2. 調整後的 Dockerfile

這個版本不再使用 Nginx,而是直接用 Node.js 執行:

Dockerfile

# 第一階段:編譯
FROM node:20-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 第二階段:執行
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
# 只安裝生產環境需要的套件(節省空間)
RUN npm install --production
# 從編譯階段複製 dist 目錄
COPY --from=build-stage /app/dist ./dist
# 複製伺服器執行檔
COPY server.js ./

EXPOSE 8080
CMD ["node", "server.js"]

答案是:它同時包含了後端(server.js)與前端(web UI)

雖然你的執行指令是 node server.js,但這個 Dockerfile 的設計已經將前端網頁完整地整合進去了。讓我們拆解一下為什麼它能運作:

為什麼它包含 Web UI?

關鍵在於這兩行指令的配合:

  1. Dockerfile 中的 COPY --from=build-stage /app/dist ./dist:這行指令從編譯階段將 dist 資料夾複製過來。dist 資料夾裡裝的就是 Vite 把你的 .tsx 檔案編譯後產生的純 HTML、CSS 和 JavaScript(也就是你的前端介面)。
  2. server.js 中的 app.use(express.static(path.join(__dirname, 'dist'))):當 Node.js 啟動時,Express 會去讀取並託管這個 dist 資料夾。當你造訪網頁時,Node.js 會主動把這些靜態檔案「餵」給瀏覽器。

如何驗證 Docker 內確實有 UI?

你可以透過以下兩個方法確認:

方法 A:執行容器並瀏覽

執行你之前的指令:

PowerShell

docker run -d -p 8080:8080 --name test-ui policy-app-node

打開瀏覽器輸入 http://localhost:8080。如果你能看到 AI Studio 產生的畫面,就代表前端 UI 已經成功打包在裡面了。

方法 B:進入容器內部檢查

如果你想親眼看看檔案在哪裡,可以執行:

PowerShell

docker exec -it test-ui sh
ls -R /app/dist

你會看到 index.html 以及 assets 資料夾出現在裡面。


未來的運作邏輯

當你把這個容器放到 Azure 後:

  • 使用者輸入網址:請求會先到達 server.js
  • server.js 的反應
    • 如果請求的是 /api/config-test,它就回傳 JSON 資料
    • 如果請求的是根目錄 /,它就回傳 dist/index.html 給瀏覽器,這時 前端 UI 就跑起來了

3. 在 Localhost 驗證

跟之前一樣,在 PowerShell 執行:

PowerShell

docker build -t policy-app-node .
docker run -d -p 8080:8080 --name my-running-app policy-app-node

接著打開 http://localhost:8080 驗證。

為什麼這樣對未來比較好?

  1. 環境一致性:現在 local 測試過能跑,未來上傳到 Azure App Service 時,環境是一模一樣的。
  2. 受控識別 (Managed Identity):當你未來要在 Azure 上存取 SQL Server,Node.js 程式碼需要使用 Azure 的 SDK。這種打包方式讓你隨時可以引入 npm install @azure/identity 等套件。
  3. 單一 Port 管理:App Service 會自動偵測容器開啟的 Port,將網頁流量導向你的 Node.js Server。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *