應(yīng)用部署后刷新子頁(yè)面 404:從路由回退到靜態(tài)目錄索引的排查方法)
很多前端項(xiàng)目在本地開發(fā)時(shí)訪問(wèn) /about、/faq、/article/xxx 這類地址完全正常但部署到服務(wù)器或?qū)ο蟠鎯?chǔ)后用戶直接打開子路徑、刷新頁(yè)面或者將鏈接發(fā)給別人訪問(wèn)時(shí)卻可能得到 404 頁(yè)面或者意外回到首頁(yè)。這不是前端路由本身失效而是瀏覽器、靜態(tài)服務(wù)器和前端路由對(duì)“路徑”的理解不同。本文從請(qǐng)求流程開始說(shuō)明如何判斷問(wèn)題屬于哪一層并給出一套可復(fù)用的排查與驗(yàn)收方法。一、先理解一次子路徑訪問(wèn)發(fā)生了什么以訪問(wèn) /docs/install 為例。在前端開發(fā)服務(wù)器中開發(fā)工具通常會(huì)自動(dòng)把未知路徑回退到入口 HTML隨后由瀏覽器加載腳本再由前端路由識(shí)別 /docs/install 并渲染對(duì)應(yīng)組件。因此開發(fā)階段很少遇到問(wèn)題。部署后請(qǐng)求會(huì)先到達(dá) CDN、對(duì)象存儲(chǔ)或 Web 服務(wù)器。服務(wù)端并不理解 React、Vue 或其他前端路由它只會(huì)嘗試尋找實(shí)際文件例如/docs/install/docs/install.html/docs/install/index.html如果這些文件都不存在服務(wù)器可能直接返回 404也可能根據(jù)錯(cuò)誤頁(yè)配置返回根目錄的 index.html。因此同一個(gè)地址在瀏覽器地址欄里看起來(lái)相同實(shí)際可能對(duì)應(yīng)兩種完全不同的處理方式第一種是靜態(tài)目錄模式。服務(wù)器找到 /docs/install/index.html 并直接返回該文件。第二種是單頁(yè)應(yīng)用回退模式。服務(wù)器找不到對(duì)應(yīng)文件后統(tǒng)一返回根目錄 index.html瀏覽器再運(yùn)行 JavaScript 完成路由渲染。兩種方式都可以正常展示頁(yè)面但部署要求、首屏內(nèi)容和故障表現(xiàn)并不完全相同。二、最常見(jiàn)的三種故障表現(xiàn)第一種直接訪問(wèn)子路徑得到 404這是最容易識(shí)別的情況。通常說(shuō)明服務(wù)器沒(méi)有把未知路徑回退到入口文件也不存在對(duì)應(yīng)目錄或靜態(tài) HTML 文件。例如用戶打開/docs/install服務(wù)器嘗試讀取/docs/install但找不到文件于是直接返回 404。此時(shí)需要檢查托管平臺(tái)是否支持單頁(yè)應(yīng)用歷史路由回退或確認(rèn)構(gòu)建產(chǎn)物里是否應(yīng)該存在 docs/install/index.html。第二種刷新頁(yè)面回到首頁(yè)這類問(wèn)題往往更隱蔽。頁(yè)面首次通過(guò)站內(nèi)點(diǎn)擊進(jìn)入時(shí)正常但按刷新后會(huì)回到首頁(yè)。常見(jiàn)原因是服務(wù)器雖然返回了 index.html但入口 HTML 中默認(rèn)的頁(yè)面信息、初始數(shù)據(jù)或腳本邏輯沒(méi)有正確識(shí)別當(dāng)前路徑。前端代碼可能在客戶端初始化后又主動(dòng)跳轉(zhuǎn)到了根路徑。排查時(shí)不要只看最終視覺(jué)結(jié)果而應(yīng)確認(rèn)地址欄是否被改寫、瀏覽器控制臺(tái)是否有路由報(bào)錯(cuò)以及初始 HTML 是否與預(yù)期路徑匹配。第三種頁(yè)面能打開但標(biāo)題和頁(yè)面信息不對(duì)這種情況常見(jiàn)于所有路徑都回退到了同一個(gè)入口 HTML。頁(yè)面渲染完成后瀏覽器標(biāo)簽標(biāo)題可能會(huì)通過(guò) JavaScript 更新但首次返回的 HTML 仍然是首頁(yè)模板。如果項(xiàng)目使用靜態(tài)預(yù)渲染每個(gè)路徑應(yīng)優(yōu)先命中各自目錄下的 index.html例如/about 對(duì)應(yīng) /about/index.html/docs/install 對(duì)應(yīng) /docs/install/index.html/article/demo 對(duì)應(yīng) /article/demo/index.html如果所有路徑始終返回根目錄 index.html那么就需要檢查 CDN 回源規(guī)則、重寫規(guī)則和緩存鍵配置。三、先確認(rèn)構(gòu)建產(chǎn)物是否真的存在排查部署前先查看構(gòu)建目錄而不是先修改服務(wù)器規(guī)則。一個(gè)采用靜態(tài)預(yù)渲染的項(xiàng)目構(gòu)建完成后通常會(huì)出現(xiàn)類似結(jié)構(gòu)dist/index.htmlabout/index.htmldocs/install/index.htmlarticle/demo/index.html如果構(gòu)建目錄中根本不存在子路徑對(duì)應(yīng)的 index.html那么服務(wù)器無(wú)論如何配置也無(wú)法直接返回該頁(yè)面的靜態(tài)內(nèi)容??梢灾痦?xiàng)確認(rèn)第一構(gòu)建命令是否真正執(zhí)行成功。第二預(yù)渲染腳本是否包含目標(biāo)路由。第三輸出路徑是否與線上訪問(wèn)路徑一致。第四部署時(shí)是否把整個(gè) dist 目錄完整上傳而不是只上傳了根目錄的 index.html 和靜態(tài)資源目錄。第五托管平臺(tái)是否在發(fā)布過(guò)程中忽略了嵌套目錄中的 HTML 文件。這一階段的原則很簡(jiǎn)單先確認(rèn)文件存在再討論路由規(guī)則。四、兩種部署策略不要混用實(shí)際項(xiàng)目中常見(jiàn)的部署策略有兩種。策略一所有未知路徑回退到入口文件這種方式適用于純客戶端渲染的單頁(yè)應(yīng)用。服務(wù)器規(guī)則的含義是如果請(qǐng)求的文件不存在就返回根目錄 index.html。其優(yōu)點(diǎn)是配置簡(jiǎn)單新增路由后通常不需要調(diào)整服務(wù)器規(guī)則。缺點(diǎn)是服務(wù)端首次響應(yīng)的是統(tǒng)一入口文件路徑頁(yè)面所需內(nèi)容要等腳本加載后才能顯示同時(shí)若項(xiàng)目存在預(yù)渲染頁(yè)面又把所有路徑強(qiáng)制回退到根入口就可能導(dǎo)致預(yù)渲染文件永遠(yuǎn)不會(huì)被使用。策略二優(yōu)先返回目錄下的靜態(tài) HTML找不到時(shí)再回退入口這種方式適用于已經(jīng)生成多個(gè)頁(yè)面靜態(tài)文件的項(xiàng)目。規(guī)則邏輯應(yīng)當(dāng)是先查找請(qǐng)求路徑對(duì)應(yīng)文件。再查找請(qǐng)求路徑對(duì)應(yīng)目錄中的 index.html。只有都不存在時(shí)才回退根目錄 index.html。這個(gè)順序很重要。若把回退入口放在前面所有請(qǐng)求都會(huì)提前命中根入口子路徑中的靜態(tài) HTML 即使已經(jīng)發(fā)布也不會(huì)被返回。因此部署前必須明確項(xiàng)目屬于哪一種模式。不要一邊生成大量預(yù)渲染頁(yè)面一邊用“所有路徑統(tǒng)一回退首頁(yè)”的規(guī)則覆蓋它們。五、如何驗(yàn)證服務(wù)器實(shí)際返回了什么不要只在瀏覽器中看頁(yè)面能不能打開。瀏覽器會(huì)執(zhí)行 JavaScript最終畫面可能掩蓋首次響應(yīng)的問(wèn)題。應(yīng)至少檢查以下內(nèi)容請(qǐng)求狀態(tài)碼首頁(yè)、核心子頁(yè)面和一篇詳情頁(yè)均應(yīng)返回 200。響應(yīng)體大小如果不同頁(yè)面返回的 HTML 大小幾乎完全一致且都很小可能說(shuō)明它們返回的是同一個(gè)入口模板。首次 HTML 中是否包含頁(yè)面標(biāo)題直接查看頁(yè)面源代碼確認(rèn) title 是否屬于當(dāng)前頁(yè)面。首次 HTML 中是否包含當(dāng)前頁(yè)面的主標(biāo)題若頁(yè)面是靜態(tài)輸出應(yīng)能在源代碼中找到對(duì)應(yīng)頁(yè)面的 H1 或正文片段。頁(yè)面規(guī)范地址是否與當(dāng)前路徑一致例如訪問(wèn) /docs/install 時(shí)頁(yè)面中的規(guī)范地址應(yīng)是當(dāng)前路徑而不是根路徑。不同路徑是否返回不同的 HTML 內(nèi)容至少對(duì)首頁(yè)、列表頁(yè)、詳情頁(yè)分別檢查一次。只檢查首頁(yè)無(wú)法證明子路徑部署正常。六、緩存會(huì)讓排查結(jié)果產(chǎn)生誤判即使源站文件已經(jīng)更新CDN 仍可能緩存舊 HTML。典型現(xiàn)象包括本地構(gòu)建結(jié)果正確線上仍顯示舊標(biāo)題。部分地區(qū)訪問(wèn)正常部分地區(qū)仍是舊頁(yè)面。首頁(yè)更新了子頁(yè)面沒(méi)有更新。普通瀏覽器與無(wú)痕窗口返回不同內(nèi)容。此時(shí)應(yīng)區(qū)分靜態(tài)資源緩存和 HTML 緩存。JavaScript、CSS、圖片通常可以長(zhǎng)期緩存HTML 文件更新頻率更高應(yīng)在發(fā)布后重點(diǎn)刷新頁(yè)面路徑的緩存。如果 CDN 使用“忽略查詢參數(shù)”的緩存策略也不應(yīng)只依賴在網(wǎng)址后面添加隨機(jī)參數(shù)來(lái)判斷是否刷新成功。更可靠的做法是確認(rèn) CDN 控制臺(tái)中的刷新任務(wù)完成并重新請(qǐng)求具體 HTML 路徑。七、一個(gè)實(shí)用的發(fā)布驗(yàn)收清單每次部署完成后可以按以下順序檢查第一步確認(rèn)構(gòu)建目錄中存在首頁(yè)和關(guān)鍵子路徑的 index.html。第二步確認(rèn)發(fā)布目錄配置指向構(gòu)建產(chǎn)物目錄而不是源碼目錄。第三步分別直接訪問(wèn)首頁(yè)、列表頁(yè)和詳情頁(yè)。第四步在子頁(yè)面按瀏覽器刷新確認(rèn)沒(méi)有 404 或錯(cuò)誤跳轉(zhuǎn)。第五步查看頁(yè)面源代碼確認(rèn)當(dāng)前頁(yè)面標(biāo)題和主體內(nèi)容已存在。第六步確認(rèn)子頁(yè)面的規(guī)范地址沒(méi)有錯(cuò)誤指向首頁(yè)。第七步刷新 CDN 中首頁(yè)、列表頁(yè)、詳情頁(yè)對(duì)應(yīng)的 HTML 緩存。第八步使用無(wú)痕窗口或另一臺(tái)設(shè)備再次訪問(wèn)避免本地緩存干擾判斷。八、結(jié)語(yǔ)單頁(yè)應(yīng)用部署問(wèn)題的關(guān)鍵不在于“頁(yè)面最終有沒(méi)有顯示出來(lái)”而在于“請(qǐng)求到達(dá)服務(wù)器時(shí)服務(wù)器究竟返回了哪個(gè)文件”。如果項(xiàng)目只依賴客戶端路由就需要保證未知路徑能穩(wěn)定回退到入口 HTML如果項(xiàng)目已經(jīng)生成頁(yè)面級(jí)靜態(tài)文件就需要保證服務(wù)器優(yōu)先返回對(duì)應(yīng)目錄中的 index.html。先厘清構(gòu)建產(chǎn)物再檢查靜態(tài)路由規(guī)則最后處理緩存通常能更快定位刷新 404、跳轉(zhuǎn)首頁(yè)和頁(yè)面信息錯(cuò)亂這幾類問(wèn)題。