跳至主要内容

本機正常,上線就壞掉?多半不是鬼,是路徑

有一種 bug 很常見,而且很沒創意。

你在本機開發時看起來一切正常:

  • 頁面會開
  • 圖片會顯示
  • CSS 沒炸
  • 連結也點得動

然後一部署上去,整站開始表演:

  • 圖片 404
  • JS chunk 載不到
  • CSS 路徑錯掉
  • 子頁正常,重新整理就死
  • /docs/intro 可以點進去,但直接開就變成伺服器一臉無辜

這種時候,很多人第一反應是:

奇怪,明明本機是好的。

對。

本機通常都很好。

因為本機最會包庇你。

真正的問題通常不是「程式昨天還可以,今天突然不行」。

而是你把一堆只在本機成立的假設,帶去了不打算配合你的正式環境。

其中最常見的一類,就是:

路徑。

講白一點:不是鬼,是你把檔案放在 A,卻叫瀏覽器去 B 找。

這類問題為什麼特別常見?

因為本機開發環境通常很寬容:

  • dev server 會幫你補路由
  • 網站常常掛在根路徑 /
  • 沒有 CDN、反向代理、子目錄部署
  • 快取比較少,錯誤比較不持久
  • 你習慣用點進去,不習慣直接打 URL 測

所以很多錯誤在本機根本不容易暴露。

一旦到正式環境,條件稍微變一下,問題就出來了。

例如:

  • 站不是掛在 /,而是掛在 /blog//docs/
  • 靜態資源有 CDN 前綴
  • 伺服器不幫你做 SPA fallback
  • 反向代理把前綴路徑吃掉或重寫錯
  • 你以為是「絕對路徑」,其實只是「相對於網域根目錄」

這幾件事一湊在一起,整站就會用 404 跟你溝通。

很直接。

最典型的坑:你以為 /img/a.png 很安全

先看這種寫法:

![cover](/img/cover.png)

或這種:

<img src="/img/cover.png" alt="cover" />

如果你的網站部署在:

https://example.com/

那這通常沒事。

但如果你的網站其實部署在:

https://example.com/docs/

那瀏覽器看到 /img/cover.png,會去找:

https://example.com/img/cover.png

不是:

https://example.com/docs/img/cover.png

也就是說,你心裡想的是「站內圖片」,瀏覽器理解的是「網域根目錄圖片」。

它沒有錯。

是你想太多。

baseUrl 不是裝飾品

如果你用的是 Docusaurus,這個問題通常跟 baseUrl 脫不了關係。

例如:

const config = {
url: 'https://example.com',
baseUrl: '/docs/',
};

這代表網站實際掛在 /docs/ 底下。

此時如果你自己手寫字串路徑:

<img src="/img/cover.png" alt="cover" />

你其實是在繞過框架的部署設定

比較穩妥的方式通常是交給框架處理,例如:

import useBaseUrl from '@docusaurus/useBaseUrl';

export default function Hero() {
const imageUrl = useBaseUrl('/img/cover.png');
return <img src={imageUrl} alt="cover" />;
}

這樣在 //docs/ 下面都比較不容易出事。

如果你明知道網站只會掛在根目錄,那硬寫也不是不行。

但很多 bug 的根源就是:

一開始只打算放根目錄,後來部署方式變了,字串路徑還停留在美好舊時代。

程式沒有懷舊能力,所以它會直接壞給你看。

不只圖片,JS、CSS、字型也會一起陪葬

很多人第一次遇到時,只注意到圖片不見。

其實更麻煩的通常是這些:

  • script chunk 路徑錯
  • lazy load 的 asset 指到舊位置
  • font URL 沒帶對前綴
  • manifest、favicon、social image 指錯

然後畫面看起來就像:

  • 樣式一半有、一半沒有
  • 按鈕能按,但 icons 消失
  • 首頁正常,內頁像被拆過

這種現象很容易讓人誤判成:

  • 打包壞了
  • 快取沒清
  • 某個套件版本爆了

當然,這些也可能是真的。

但先檢查路徑,成本最低。

因為它出事的機率高得很不體面。

另一個老坑:相對路徑在巢狀頁面很會背刺你

例如你寫:

<img src="img/cover.png" alt="cover" />

如果目前頁面在:

https://example.com/posts/hello/

瀏覽器可能會把它解成:

https://example.com/posts/hello/img/cover.png

這跟你以為的:

https://example.com/img/cover.png

完全不是同一回事。

所以你會看到一種很煩的症狀:

  • 首頁正常
  • 某些文章頁正常
  • 深一層的頁面全部掛掉

因為相對路徑不是壞掉。

它只是非常誠實地照規則做事。

而你沒有真的記得那個規則。

還有一種:前端路由正常,直接刷新就死

這也是經典。

例如你有一個 SPA 或靜態站前端路由:

/docs/intro

從首頁點進去時正常。

因為是前端接手導頁。

但你直接開這個 URL,或重新整理頁面,伺服器就回你 404。

原因通常不是前端路由壞掉。

而是伺服器根本不知道:

這個路徑其實應該回 index.html,再交給前端處理。

像 Nginx 常見會需要這種設定:

location /docs/ {
try_files $uri $uri/ /docs/index.html;
}

如果沒有類似 fallback,很多 client-side route 都會在「直接進入」時死亡。

這種 bug 也很會騙人,因為:

  • 用站內連結進去時正常
  • 用書籤、分享連結、重新整理時才壞

於是它可以潛伏很久。

直到有人真的照正常人方式使用網站。

怎麼查比較快?先看 Network,不要先拜神

碰到這種問題,第一件事不是重跑 build。

第一件事是打開瀏覽器 DevTools 的 Network

看這幾件事:

  1. 哪個 URL 真的 404?
  2. 它是少了前綴,還是多了前綴?
  3. 是根目錄 / 被誤用,還是相對路徑跑偏?
  4. 是 HTML 找不到,還是 JS chunk 找不到?
  5. 重新整理內頁時,是不是伺服器沒做 fallback?

很多人 debug 卡很久,是因為只盯著畫面說「怎麼沒出來」。

畫面不會告訴你答案。

404 URL 會。

差很多。

一套比較不容易出事的檢查順序

我通常會這樣查:

1. 先確認實際部署位置

先搞清楚網站到底掛在哪:

  • /
  • /docs/
  • /product/site/
  • CDN path prefix 後面

不要用想像的。

看實際網址。

2. 檢查框架設定

例如 Docusaurus:

  • url
  • baseUrl
  • trailingSlash

例如 Vite / Next / 其他框架,也會有自己的:

  • base
  • assetPrefix
  • publicPath

名字不同,問題差不多。

3. 找出所有硬編碼路徑

特別是這些:

/src="/..."
/href="/..."
url(/...)
fetch('/...')

它們不一定錯。

但很值得懷疑。

4. 測「直接進內頁」

不要只測首頁。

請直接開:

https://example.com/docs/some/page

如果只有站內跳轉能用,那問題通常不在「頁面內容」,而在路由或伺服器設定。

5. 清掉快取再看一次

尤其是:

  • service worker
  • CDN cache
  • hashed assets 與 HTML 不同步

有時候你修好了,但快取還在堅持它的舊世界觀。

也很常見。

一個實際上比較安全的習慣

原則其實不複雜:

不要在需要部署彈性的專案裡,到處手寫你自以為不會變的資源路徑。

能交給框架處理,就交給框架。

能集中封裝,就不要散落全專案。

例如包一層:

import useBaseUrl from '@docusaurus/useBaseUrl';

export function useAsset(path: string) {
return useBaseUrl(path);
}

之後統一用:

const logo = useAsset('/img/logo.svg');

這不會讓世界和平。

但至少未來部署位置改掉時,你不需要在專案裡挖一百個 "/img/..."

最後

「本機正常、上線壞掉」這件事,很多時候不是神祕 bug。

只是正式環境終於停止縱容你。

而在所有原因裡,路徑問題通常是最常見、最便宜、也最值得先懷疑的那一個。

所以如果你下次看到:

  • 首頁正常,內頁怪怪的
  • 部分圖片消失
  • JS/CSS 在 production 失蹤
  • 點進去正常,重新整理就 404

先不要急著怪框架。

也先不要把責任推給瀏覽器快取、Node 版本、月亮位置、或某個你其實沒看懂的 bundler 更新。

先去看路徑。

很多時候,問題沒有很深。

只是你剛好走錯目錄而已。

☕ 一杯咖啡,就是我創作的燃料!

贊助我持續分享 AI 實作、全端架構與開源經驗,讓好文章不斷更新。

cta-button
AI / 全端 / 客製 一次搞定 icon
ALL

AI / 全端 / 客製 一次搞定

從構想到上線,涵蓋顧問、開發與部署,全方位支援你的技術實作。

包含內容
  • 顧問服務 + 系統建置 + 客製開發
  • 長期維運與擴充規劃

🚀 你的專案準備好了嗎?

如果你需要客製服務或長期顧問,歡迎與我聯繫!