我們把網站從 HTML 切到 Nuxt 3,一天踩了 5 個坑(附檢查清單)
前言:一天搞定架構遷移,代價是什麼? 3 月 21 日,SFD 實驗室把官網從純 HTML 靜態站遷移到了 Nuxt 3 SSR。 聽起來很酷對吧?一天搞定架構升級,現代化框架、伺服器端渲染、SEO 友善——履歷上又多一行。 但實際情況是:老闆打開首頁,插畫全部破圖。點進文章,「文章不存在」。手機端漢堡選單看不見…

前言:一天搞定架構遷移,代價是什麼?
3 月 21 日,SFD 實驗室把官網從純 HTML 靜態站遷移到了 Nuxt 3 SSR。
聽起來很酷對吧?一天搞定架構升級,現代化框架、伺服器端渲染、SEO 友善——履歷上又多一行。
但實際情況是:老闆打開首頁,插畫全部破圖。點進文章,「文章不存在」。手機端漢堡選單看不見。curl 一下原始碼,滿眼 not-found。
一天之內,我們踩了 5 個坑,每個都是架構切換時最容易忽略的盲區。今天把這些坑全部攤開聊,省得你再走一遍。
坑 1:Nginx 切了 proxy_pass,圖片全炸了
💥 現象
切換上線後老闆第一個打開首頁,所有插畫——全部破圖。Console 一排排 404,/uploads/ 和 /images/ 下面的圖片一個都載入不出來。
🔍 排查
舊站是純 HTML,Nginx 直接 serve 那個目錄,/uploads/logo.png 這種路徑天然就能存取到檔案。但切了 proxy_pass 給 Nuxt 的 Node 行程之後,請求全部轉發到了 3000 埠。Node 行程裡根本沒有這些靜態檔案——它只管渲染 Vue 元件,哪知道你的圖片在磁碟哪個角落?
🎯 根因
架構切換時只改了主路由的轉發,完全忘了靜態資源還在舊目錄裡。proxy_pass 是個「全攔截」的操作,你沒單獨放行的路徑,全都會被轉發到 Node。
🔧 修復
在 Nginx 設定檔裡加了 3 個 location block:
location /uploads/ {
alias /var/www/old-site/uploads/;
}
location /images/ {
alias /var/www/old-site/images/;
}
location /api/ {
proxy_pass http://127.0.0.1:8370;
}
就這麼幾行,但不寫就全炸。
📝 教訓
切 proxy_pass 之前,先把舊站所有的靜態資源路徑列出來。用 grep 或者 find 掃一遍 HTML/CSS/JS 裡引用的路徑,確保每一條在新架構下都有人「接住」。不要假設「Nuxt 會自動處理」——它不會。
坑 2:文章詳情頁全部「文章不存在」
💥 現象
首頁看著挺好,文章列表也有資料。但只要點進任何一篇文章的詳情頁——「文章不存在」。所有文章,無一例外。
🔍 排查
翻前端程式碼一看,好傢伙。變色龍同學寫了一堆 Mock 假資料:mockSciencePosts、mockTechArticles……列表頁用的是這些假資料,所以首頁和列表看起來正常。但詳情頁需要根據 slug 去查具體文章,Mock 資料裡沒有對應內容,直接返回空。
驗收的時候呢?只看了首頁和列表頁。沒人點進文章詳情。
🎯 根因
兩個問題疊加:一是開發用 Mock 資料做原型沒問題,但上線前沒替換成真實 API 呼叫;二是驗收不夠深,只看了「第一層」。
修的時候還踩了個額外的坑:lang 參數不匹配。前端傳的是「zh-CN」,但 CMS API 只認「zh」。就這兩個字元的差異,查了好一會兒。
🔧 修復
刪掉了 300 多行 Mock 資料程式碼,全部替換成 useAsyncData + $fetch 直接呼叫 CMS 的 REST API。lang 參數做了映射,「zh-CN」→「zh」,「en-US」→「en」。
📝 教訓
**上線前必須 grep 全專案「mock」關鍵詞。**這不是建議,是鐵律。Mock 資料就像鷹架,蓋樓的時候需要,交屋的時候必須拆乾淨。不然住進去的人(使用者)看到的就是毛胚。
驗收也一樣:不能只看首頁。必須點開至少 3 篇文章,看詳情頁能不能正常渲染。
坑 3:SSR 原始碼裡藏著「not-found」
💥 現象
這個坑比較隱蔽。用瀏覽器打開頁面,一切正常。但用 curl 看頁面原始碼——
$ curl -s https://v2.smallfiredragon.com/science/xxx | grep not-found
<div class="not-found">...
原始碼裡有 not-found 標記。搜尋引擎爬蟲看到的就是這個原始碼,這意味著 SEO 直接廢了。
🔍 排查
Nuxt 3 的 SSR 是這麼運作的:Node 伺服器端先渲染一遍 HTML 發給瀏覽器,然後客戶端拿到 HTML 後再「hydrate」(激活),客戶端會重新 fetch 資料。
問題出在伺服器端渲染這一步。Node 行程在伺服器內部 fetch CMS API 時,走的網域是 v2.smallfiredragon.com——但伺服器自己解析不到這個網域(DNS 沒設定內網解析)。所以伺服器端 fetch 失敗,渲染出了 not-found 狀態。
瀏覽器為什麼正常?因為客戶端 hydrate 後重新 fetch,走的是公網 DNS,能解析到,所以又取得了資料,覆蓋了伺服器端的錯誤狀態。使用者看不出來,但爬蟲看到的是伺服器端那份錯誤的 HTML。
🎯 根因
SSR 環境下的網路請求和客戶端完全不同。伺服器在內網,DNS 解析、防火牆規則、hosts 檔案都可能不一樣。這個差異在開發環境裡根本測不出來,因為開發機上什麼都能解析。
🔧 修復
兩種方案:在伺服器的 /etc/hosts 裡加一條網域解析記錄,或者在 Nuxt 的 runtimeConfig 裡把 SSR 端的 API 位址改成內網迴環位址(127.0.0.1:8370)。我們兩個都做了,雙重保險。
📝 教訓
**部署 SSR 應用,一定要用 curl 檢查伺服器端渲染出來的 HTML。**瀏覽器看到的 ≠ 搜尋引擎看到的。curl 才是爬蟲的視角。每次部署後跑一遍 curl 檢查,是 SSR 專案的必修課。
坑 4:漢堡選單「隱身」了
💥 現象
手機端打開網站,右上角應該有個漢堡選單圖示——但什麼都看不見。不是沒渲染,是渲染了但看不見。
🔍 排查
打開 DevTools 一看,元素在那裡,有寬高,位置也對。打開 Computed 看顏色:#94A3B8,一個偏冷的中灰色。背景色?#111318,深藍黑色。
數字上對比度勉強過線,但實際在手機小螢幕上看,細線條圖示配上這個灰色——真的很難注意到。尤其是在戶外強光下或者螢幕亮度偏低的時候,約等於隱身。
🎯 根因
深色主題下的互動元素顏色沒有做專項審查。桌面端大螢幕上可能勉強能看,手機端小螢幕就直接「消失」了。
🔧 修復
把圖示顏色改成品牌橙 #F97316。橙色在深色背景上對比度拉滿,而且和品牌色統一,一舉兩得。
📝 教訓
深色主題不是把背景改黑就完了。每個互動元素都要重新審視顏色對比度。建議上線前用 WebAIM 的 Contrast Checker 跑一遍所有可互動元素,尤其是圖示、按鈕邊框、次級文字這些容易忽略的地方。
坑 5:老闆的提醒被當耳旁風了
💥 現象
下午 17:53,老闆在群裡發了一句:「注意文章路徑是否一樣」。
團隊的反應:檢查了一下 HTTP 狀態碼,200,沒問題!繼續搞別的去了。
然後就出了坑 2 和坑 3。
🔍 排查
回過頭看,老闆說的「注意文章路徑」其實暗含了好幾層意思:路徑能不能存取?存取了返回什麼?內容對不對?SEO 路徑有沒有變?舊連結還能不能跳轉?
但團隊只理解了最表面的一層:「路徑能存取」→ HTTP 200 → 沒問題。
🎯 根因
HTTP 200 只代表「伺服器正常回應了」,不代表「頁面內容是對的」。一個顯示「文章不存在」的頁面,照樣返回 200。
🔧 修復
沒有程式碼修復,這是流程問題。後來團隊定了一條規矩:老闆的提醒 = 風險訊號,必須深入排查到內容層。不是檢查狀態碼就完了,而是要打開頁面看內容、看原始碼、測互動。
📝 教訓
200 OK 是最不可靠的「一切正常」訊號。驗收必須看實際內容,不是看狀態碼。這一條不止適用於架構遷移——任何部署後的檢查都一樣。
SFD 編者註
這 5 個坑,每一個單獨拿出來看都不難修。但疊在一起,再加上「一天搞定」的時間壓力,就變成了一場事故接力賽。
我們實驗室這次遷移最大的收穫不是技術層面的——Nginx 設定、Mock 資料清理、DNS 解析,這些搜一下都能查到。最大的收穫是對「驗收」這件事的重新理解:
你以為驗收是打開首頁看一眼。實際上驗收是模擬使用者的完整操作路徑,包括那些你覺得「肯定沒問題」的頁面。
尤其是架構遷移這種事,改的是地基。地基一換,上面的每一層樓、每一扇窗、每一根水管都可能受影響。不能只檢查大門能不能開,要每個房間都走一遍。
📋 架構遷移檢查清單
最後整理了一份清單,我們內部在用,分享出來:
一、遷移前
- □ 列出舊站所有靜態資源路徑(/uploads/、/images/、/fonts/、/assets/ 等)
- □ 列出舊站所有 API 介面位址
- □ 列出舊站所有文章/頁面 URL,匯出一份完整 URL 清單
- □ grep 全專案「mock」、「fake」、「dummy」、「test」關鍵詞,標記所有待替換的假資料
- □ 確認新架構的伺服器端能否解析所有必需網域(curl 測試,不要用瀏覽器)
- □ 確認 CMS API 的參數格式(lang、slug 等欄位的命名和值)
- □ 備份舊站設定(Nginx conf、DNS 記錄、SSL 憑證路徑)
二、Nginx 設定切換
- □ 為每個靜態資源路徑新增獨立的 location block
- □ API 轉發單獨設定 location(不要混在主 proxy_pass 裡)
- □ 測試每個 location block 是否生效(curl 每個路徑)
- □ 檢查 SSL 憑證是否正確綁定到新設定
- □ 舊 URL 做 301 重新導向到新 URL(如果路徑結構有變化)
三、SSR 專項檢查
- □ 用 curl 檢查首頁原始碼,確認有實際內容(不是空殼或 not-found)
- □ 用 curl 檢查至少 3 個文章詳情頁原始碼
- □ 確認伺服器端 DNS 能解析所有 API 網域(在伺服器上 nslookup/dig 測試)
- □ 檢查 SSR 環境變數是否正確設定(API 位址、語言參數等)
- □ 對比伺服器端渲染結果和客戶端渲染結果,不應有內容差異
四、內容與 UI 驗收
- □ 打開首頁,檢查所有圖片是否載入
- □ 點進至少 3 篇文章詳情頁,確認內容正常顯示
- □ 測試所有語言版本(中/英/日等),確認切換正常
- □ 手機端測試:漢堡選單、導航、文章頁、圖片
- □ 檢查所有互動元素在深色/淺色主題下的可見性
- □ 檢查頁面 title、meta description、OG tags 是否正確
五、上線後監控
- □ 舊 URL 全量測試(用匯出的 URL 清單跑一遍,檢查是否 301/200)
- □ Google Search Console 檢查索引狀態(上線後 1-3 天)
- □ 監控伺服器錯誤日誌(Nginx error log + Node 行程日誌)
- □ 讓團隊外的人(或老闆)隨意瀏覽,發現你漏掉的問題
這份清單不長,但每一條都是我們用真實翻車換來的。建議存下來,下次架構遷移前打開對一遍。
畢竟,踩坑不可怕,可怕的是同一個坑踩兩次。