Next.js服務(wù)端渲染超時導(dǎo)致生產(chǎn)事故的問題排查和解決辦法
前言
前幾天公司平臺首頁崩潰了 504 Time-out,這里記錄一下問題排查和解決的過程。
排查和解決
排查過程
- step 1:檢查測試環(huán)境、開發(fā)環(huán)境未出現(xiàn)問題;
- step 2:檢查生產(chǎn)環(huán)境流水線,沒有最新部署,排除前端新部署代碼影響;
- step 3:檢查生產(chǎn)環(huán)境其他頁面
/about-us
頁面展示正常,但是部分接口報錯超時。猜測是因為生產(chǎn)環(huán)境接口超時導(dǎo)致首頁的服務(wù)端請求返回超時,導(dǎo)致首頁返回超時; - step 4:本地開發(fā)調(diào)用生產(chǎn)環(huán)境接口,將所有在
getServerSideProps
中發(fā)起的請求全部注釋后,首頁可以正常加載。
問題定位
首頁開啟了SSR
渲染,getServerSideProps
中的請求使用了簡單的fetch
請求,只對普通錯誤做了兜底,但沒有考慮到請求超時的問題,請求超時導(dǎo)致了頁面返回超時,首頁報了504
錯誤。
解決方案
優(yōu)化方案
- 放棄使用
SSR
渲染,全部采用客戶端請求渲染; - 修改現(xiàn)有
fetch
,通過AbortController
、setTimeout
手動添加超時處理; - 將
getServerSideProps
的請求方式從fetch
遷移到axios
,借助axios
本身自帶的超時配置,添加接口超時處理。
方案比較
- 放棄
SSR
不可取,因為客戶端并發(fā)請求返回順序的問題可能會導(dǎo)致首頁的入場動畫混亂,且失去了部分首屏性能優(yōu)化; - 修改
fetch
方法,需要手寫大量代碼,且AbortController
是在Node.js 14.17.0+
開始支持,當然也可以不去放棄掉請求,可以通過setTimeout
和Promise.race
提前reject
,不過需要因為是自行實現(xiàn),風險大。 axios
是十分成熟的請求庫,且項目中已經(jīng)引入,通過axios
為請求添加超時處理十分方便。
實際解決方案
將fetch
遷移到axios
,借助axios
已有的超時處理配置解決當前問題,添加首頁對getServerSideProps
中請求超時的兜底,服務(wù)端請求超時后,立即返回空數(shù)組。
首頁所有的數(shù)據(jù)消費組件繼續(xù)兜底,當從getServerSideProps
中獲取的數(shù)據(jù)為空數(shù)組時,會在客戶端再發(fā)一次請求,嘗試從客戶端獲取數(shù)據(jù)。
import axios from 'axios'; const ssrAxios = axios.create({ timeout: 3000, timeoutErrorMessage: 'Request timed out' }); export default ssrAxios;
export const getServerSideProps = async () => { const addr = process.env.API_ADDR; // 使用 Promise.allSettled 獲取所有請求的結(jié)果 const [ list1Res, // ... , list2Res, ] = await Promise.allSettled([ ssrAxios.get(`${addr}/api/list1`), // ... , ssrAxios.get(`${addr}/api/list2`), ]); // 處理每個請求的結(jié)果 const list1 = handleFetchResult(list1Res, 'list1'); // ... ; const list2 = handleFetchResult(list2Res, 'list2'); return { props: { list1: list1 ?? [], // ... , list2: list2 ?? [] }, }; }; // Helper function to handle fetch results const handleFetchResult = (result: PromiseSettledResult<any>, key: string) => { if (result.status === 'rejected') { console.error(`Failed to fetch ${key}:`, result.reason); return []; } const { data, success } = result.value.data; if (!success) { console.error(`Failed to fetch ${key}`); return []; } switch (key) { case 'list1': return data?.result || []; // ... ; case 'list2': return data?.result || []; default: return []; } };
問題探究
問題猜測
- 接口性能瓶頸:生產(chǎn)環(huán)境接口響應(yīng)時間波動大,
SSR
并發(fā)請求存在短板 - SSR 超時機制缺失:未設(shè)置合理的請求超時閾值,導(dǎo)致慢請求影響頁面的生成和響應(yīng)
- 錯誤處理不完善:前端錯誤處理考慮不全面,未區(qū)分超時錯誤與常規(guī)錯誤,只做了無返回或返回錯誤的處理,沒有對請求超時做兜底。
驗證猜想
本地模擬接口返回超時,首頁會無法成功加載。添加超時處理兜底后,首頁其他內(nèi)容可以正常展示。
詢問deepseek
后,得知Next.js
的getServerSideProps
沒有內(nèi)置超時處理機制,因此長時間運行的getServerSideProps
可能導(dǎo)致頁面無法及時返回,甚至觸發(fā)部署平臺(如Vercel
)的超時錯誤。
結(jié)論
首頁開啟了SSR
渲染,Next.js
必須在完成getServerSideProps
中的所有
操作后才能生成頁面并返回給客戶端。 需要在得到所有需要的props
后,才會返回整個頁面。因為生產(chǎn)環(huán)境某個接口請求超時,并且沒有正確處理,可能會導(dǎo)致整個getServerSideProps
的執(zhí)行時間延長,超過服務(wù)器的響應(yīng)時間限制,從而使得頁面無法及時返回,出現(xiàn)超時錯誤。
思考改進
思考
雖然是因為后端服務(wù)崩了,接口請求超時導(dǎo)致的問題,但是因為部分服務(wù)崩掉導(dǎo)致整個前端首頁無法成功加載,作為前端開發(fā)人員的我還是有很大責任的。開發(fā)時我在對可能出現(xiàn)的錯誤進行兜底時,需要盡可能地考慮全面。
改進
服務(wù)端請求優(yōu)化,錯誤處理添加對請求超時的處理。
① 首次訪問使用SSR獲取最新數(shù)據(jù);
② 當SSR超時時自動降級到 CSR版本 ;
③ 保持首屏加載性能的同時提升可用性。
以上就是Next.js服務(wù)端渲染超時導(dǎo)致生產(chǎn)事故的排查和解決辦法的詳細內(nèi)容,更多關(guān)于Next.js服務(wù)端渲染超時的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
原生JavaScript實現(xiàn)todolist功能
本篇文章給大家介紹了通過原生JavaScript實現(xiàn)todolist功能相關(guān)知識點,對此有需要的朋友可以學習下。2018-03-03js中substr,substring,indexOf,lastIndexOf,split,replace的用法詳解
這篇文章主要介紹了js中substr,substring,indexOf,lastIndexOf,split,replace的用法詳解的相關(guān)資料,需要的朋友可以參考下2015-11-11理解javascript中的Function.prototype.bind的方法
這篇文章主要介紹了理解javascript中的Function.prototype.bind的方法,具有一定參考價值,有興趣的可以了解一下。2017-02-02基于Express框架使用POST傳遞Form數(shù)據(jù)
這篇文章主要為大家詳細介紹了基于Express框架使用POST傳遞Form數(shù)據(jù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08