開發紀錄
404 頁不只是錯誤訊息:靜態網站的未知網址處理
從實際 HTTP 回應、錯誤頁 HTML 和多語 fallback,分清產生 404.html、回傳 404 與讓讀者繼續瀏覽的不同責任,也記錄本機預覽不能代替正式主機驗證的限制。
- Astro
- HTTP
- 靜態網站
- 多語網站
直接開啟 404.html,看到「頁面不存在」,不能證明網站已經正確處理未知網址。
這次在本機靜態預覽裡,我確認了兩種不同回應:請求一個不存在的網址,得到 HTTP 404;直接請求 404.html,得到的卻是 HTTP 200。兩者都能顯示同一份錯誤畫面,但只有前者驗證了「未知網址如何被處理」。
我會把 404 分成兩件事檢查:伺服器要如實回報這個網址沒有內容,頁面則要讓讀者知道發生什麼事,並提供能繼續瀏覽的入口。漂亮的錯誤插圖、清楚的文案和正確的狀態碼,各自有要完成的工作。
先測不存在的網址,不是錯誤頁檔案
Astro 可以透過 404.astro 或 404.md 產生自訂錯誤頁,靜態建置後會得到 404.html。官方文件也提醒,多數部署服務會找到並使用它;這不等於每個提供 HTML 的伺服器都已經採用相同規則。[1]
這裡有兩層責任:建置工具產生可用的文件,提供網站的伺服器決定什麼請求要使用這份文件,以及回傳什麼狀態碼。
因此,檢查時至少要把以下兩個請求分開:
| 請求 | 想確認的事情 |
|---|---|
直接開啟 404.html |
錯誤頁檔案是否存在,內容與資源能否讀取 |
| 開啟一個確認不存在的網址 | 伺服器是否以錯誤回應提供頁面,而非正常內容或首頁 |
本機預覽中,直接開啟錯誤頁檔案回傳 200,是這次實際觀察到的檔案存取行為,不是用來證明未知網址處理失敗的依據。應該拿來判斷的是第二個請求,而且要同時讀狀態碼與回應內容。
把所有網址送回首頁,會掩蓋什麼
對這個已經產生各頁 HTML 的靜態網站來說,不需要把所有未知路徑改寫到首頁,再期待瀏覽器決定要顯示什麼。現有部署設定指定 Astro 和靜態輸出目錄,沒有加入這種全站 rewrite。
假設有人把文章網址拼錯,伺服器卻仍回傳首頁和 HTTP 200,測試就可能只留下「有回應、可以開啟」的紀錄。讀者原本要找的文章並沒有出現。
另一種情況是保留錯誤文案,回應卻依然是 200。Google 的文件說明,成功狀態碼搭配空頁或錯誤內容,可能被搜尋系統判定為 soft 404;不能靠頁面上的「404」三個數字取代 HTTP 狀態。[2]
不過,也不能把所有舊網址都當成錯誤。本站的 /work 是刻意保留的相容入口,本機預覽回傳 200 和可讀作品內容,canonical 指向 /about/work。這和隨便輸入一段不存在的路徑,是兩種不同的網址契約。
測試前先分清楚要檢查的是正式頁、相容入口還是未知網址,才不會把有意保留的內容誤判成 404 缺失。
noindex 處理索引,不負責改狀態碼
本站錯誤頁保留 title、description 和可讀正文,但不沿用首頁的 canonical、og:url、語言替代 metadata 或結構化資料,也不把錯誤頁列進 sitemap。導覽列仍能讓人離開錯誤頁,卻不會把首頁標成目前所在的正常頁面。
這是頁面身份的選擇:錯誤頁可以使用共用外殼,不必因此宣稱未知網址是首頁的另一個版本。
目前 HTML 另外輸出 noindex。它是索引指示,不會讓 HTTP 200 自動變成 404;Google 對真正回傳 4xx 的網址也有獨立處理規則,兩者不應混成一個判斷。[2][3]
Google 亦說明,要讀到 noindex,爬蟲必須能存取頁面,不能先被 robots.txt 擋住。[3] 因此,檢查 head 裡有沒有一段文字,只能確認網站輸出了指示,不能據此宣稱搜尋結果已經更新。
如果要看正式頁、相容頁與公開 metadata 的整體關係,可以接著讀靜態 HTML、canonical、hreflang 與 sitemap 如何一起成立。這篇把重點留在未知網址真正收到什麼回應。
共用一份錯誤頁,多語就有初始語言的限制
本站建置一份共用錯誤頁,初始 HTML 是繁中。瀏覽器腳本再讀取網址第一段:en 使用英文、ja 使用日文,其餘使用繁中,並更新標題、說明、lang、導覽文案和回首頁連結。
這次實際請求英文與日文的未知網址時,HTTP 回應都是 404,原始 HTML 也都仍是繁中。放進瀏覽器執行腳本後,英文頁的 h1 變成 Page not found,回首頁連結指向 /en;日文頁則顯示 ページが見つかりません,連結指向 /ja。網址沒有被轉走。
所以這裡能說的是「共用錯誤頁以瀏覽器腳本做語言增強」,不是「伺服器一開始就回傳對應語言的錯誤 HTML」。
JavaScript 沒有執行時,繁中的錯誤說明仍在,頁面另外提供三種語言的首頁連結。這能保留離開錯誤頁的選擇,但不是完整的英日無腳本錯誤頁。若之後需要每種語言都有正確的初始 HTML,就必須連同主機如何選擇錯誤文件一起設計;只新增一個語言目錄下的頁面,還不能證明主機會在未知網址使用它。
讓讀者離開錯誤頁,不必先改寫他的網址
主動提供首頁連結,和自動把讀者送回首頁,差別在於是否保留原本的請求。
這次的未知網址回應沒有先 redirect 到一個成功的錯誤頁網址。瀏覽器仍停在原來那段路徑,讀者能檢查拼字、複製出錯連結,或選擇回首頁。頁面上的首頁與導覽則使用原生連結,不必等另一個動作程式決定目的地。
錯誤頁的語言切換也保留缺少內容的邏輯路徑。例如在英文未知網址切成日文時,換的是語言前綴,不是把不存在的內容包裝成某個成功頁面。這是目前腳本的實作責任,不表示換語言後就會找到文章。
我會把恢復入口寫得比錯誤原因更確定:可以請讀者確認網址,也可以提供實際存在的首頁;沒有證據時,不先斷言「文章已刪除」,也不加上尚未提供的搜尋或聯絡功能。
本機預覽還有一個尾端斜線的例外
目前網站使用 trailingSlash: 'never'。這次測試不含尾端斜線的未知路徑時,本機 preview 使用了網站自訂錯誤頁;在同樣路徑後面加上 /,雖然仍回傳 404,回應內容卻是 Astro 的尾端斜線錯誤頁,而不是網站自己的畫面。
這個差異說明,只確認狀態碼還不夠。兩個請求都是 404,但標題、語言與恢復入口不同。
它也限制了驗證結論:這是目前本機 preview 對路徑格式的處理,不能直接推定正式主機也會先走這個分支。我沒有為了讓預覽畫面一致而修改正常網址政策,也沒有把尚未測試的主機設定寫成已完成的修正。
這次確認到哪裡
這次先讀取目前路由、layout、語言腳本、部署設定和 sitemap,再檢查建置產物、實際請求本機 preview,並開啟英日未知網址確認腳本執行後的內容。
| 檢查對象 | 這次的結果 |
|---|---|
/about/work 與相容入口 /work |
都回傳 200,作品內容可讀,canonical 指向主要網址 |
| 三種語言前綴下、不含尾端斜線的未知網址 | 都回傳 404,沒有 redirect,使用共用繁中錯誤 HTML |
| 英日未知網址執行 JavaScript 後 | h1、lang 和回首頁連結切換為對應語言,保留未知路徑 |
| 同一組未知網址加上尾端斜線 | 都回傳 404,但使用 Astro 的路徑格式錯誤頁 |
直接請求 404.html |
回傳 200;只用來檢查錯誤頁檔案,不當成未知網址測試 |
這些結果沒有涵蓋正式部署主機、快取、所有瀏覽器或輔助科技,也沒有證明搜尋引擎已經採取任何索引動作。
部署後仍要在實際網域重新請求正常深層路由、相容入口,以及帶不同語言前綴和尾端斜線的未知網址,讀回狀態碼、轉址、正文和恢復連結。錯誤頁檔案已經產生,和主機確實用對它,是兩個要分開驗證的結果。
Sources
[1] https://docs.astro.build/en/basics/astro-pages [2] https://developers.google.com/crawling/docs/troubleshooting/http-status-codes [3] https://developers.google.com/search/docs/crawling-indexing/block-indexing