SPA 部署到 Nginx 的路由問題與解決方案
問題描述
啟動容器後發生一個錯誤:一開始是正常的導到 http://localhost:4200/login 並顯示畫面,但重新整理後會變成 404 錯誤。

從瀏覽器 console 中可以看到錯誤訊息:

問題原因
這是一個在單頁應用程式 (SPA) 部署到 Nginx 時常見的問題。發生的原因是:
首次載入:當第一次載入頁面時,瀏覽器請求根路徑
/
,Nginx 正確地提供了index.html
前端路由運作:Angular 的路由機制接管了頁面,當你點擊連結導航到
/login
時,Angular 路由器在客戶端處理這個路徑變更,不會向伺服器請求新頁面重新整理問題:但當你在
/login
路徑下重新整理頁面時,瀏覽器會直接向伺服器請求/login
路徑的資源404 錯誤:Nginx 找不到實體的
/login
檔案(因為它不存在),所以回傳 404 錯誤
這個問題發生是因為你的 Angular 應用使用前端路由,但 Nginx 預設設定只會尋找實體檔案,而不會將所有請求都導向 index.html。
解決方案
方案一:修改 Dockerfile 和添加自定義 Nginx 設定檔
修改前端的 Dockerfile,添加自定義 Nginx 設定:
# 建置階段
FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
# 執行階段
FROM nginx:alpine
# 複製自定義 Nginx 設定
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/voting-system/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
在前端專案目錄中建立一個
nginx.conf
檔案:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
關鍵設定說明:
try_files $uri $uri/ /index.html;
這行設定是解決問題的核心它會嘗試尋找請求的 URI,如果找不到就回退到 index.html
這樣 Angular 的路由器就能正常接管,處理客戶端路由
重新建置和啟動容器:
docker-compose down
docker-compose up --build
方案二:使用 Docker Hub 官方的 Nginx 設定(透過 docker-compose.yml)
除了修改 Dockerfile 外,也可以直接在 docker-compose.yml 中設定 Nginx:
frontend:
build:
context: ./voting-system-fe
image: voting-system-fe
container_name: voting_system_fe
restart: always
ports:
- "4200:80"
depends_on:
- server
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
使用這種方式時,需要在整合目錄中(與 docker-compose.yml 相同層級)建立 nginx.conf 檔案,內容與前面相同。
技術說明
SPA 應用使用 HTML5 History API 進行路由,與傳統多頁面應用程式有所不同:
傳統網站:每個 URL 路徑對應到伺服器上的一個實體檔案
SPA:所有路由都由前端 JavaScript 處理,伺服器只需提供唯一的入口檔案(通常是 index.html)
因此,正確設定 Nginx 進行 URL 重寫(URL rewriting)是部署 Angular、React 或 Vue 等 SPA 應用時的必要步驟。
擴展知識
這個 try_files
指令也可以添加其他選項,例如:
location / {
try_files $uri $uri/ /index.html?$query_string;
}
這會將原始的查詢參數傳遞給 index.html,在某些需要處理 URL 參數的應用中很有用。
對於生產環境,還可以添加快取設定,例如:
location /assets/ {
expires 1y;
add_header Cache-Control "public";
}
這會對靜態資源設定較長的快取時間,提高網站性能。
Last updated