Next.js服務(wù)端渲染超時(shí)導(dǎo)致生產(chǎn)事故的問(wèn)題排查和解決辦法
前言
前幾天公司平臺(tái)首頁(yè)崩潰了 504 Time-out,這里記錄一下問(wèn)題排查和解決的過(guò)程。
排查和解決
排查過(guò)程
- step 1:檢查測(cè)試環(huán)境、開(kāi)發(fā)環(huán)境未出現(xiàn)問(wèn)題;
- step 2:檢查生產(chǎn)環(huán)境流水線,沒(méi)有最新部署,排除前端新部署代碼影響;
- step 3:檢查生產(chǎn)環(huán)境其他頁(yè)面
/about-us
頁(yè)面展示正常,但是部分接口報(bào)錯(cuò)超時(shí)。猜測(cè)是因?yàn)樯a(chǎn)環(huán)境接口超時(shí)導(dǎo)致首頁(yè)的服務(wù)端請(qǐng)求返回超時(shí),導(dǎo)致首頁(yè)返回超時(shí); - step 4:本地開(kāi)發(fā)調(diào)用生產(chǎn)環(huán)境接口,將所有在
getServerSideProps
中發(fā)起的請(qǐng)求全部注釋后,首頁(yè)可以正常加載。
問(wèn)題定位
首頁(yè)開(kāi)啟了SSR
渲染,getServerSideProps
中的請(qǐng)求使用了簡(jiǎn)單的fetch
請(qǐng)求,只對(duì)普通錯(cuò)誤做了兜底,但沒(méi)有考慮到請(qǐng)求超時(shí)的問(wèn)題,請(qǐng)求超時(shí)導(dǎo)致了頁(yè)面返回超時(shí),首頁(yè)報(bào)了504
錯(cuò)誤。
解決方案
優(yōu)化方案
- 放棄使用
SSR
渲染,全部采用客戶端請(qǐng)求渲染; - 修改現(xiàn)有
fetch
,通過(guò)AbortController
、setTimeout
手動(dòng)添加超時(shí)處理; - 將
getServerSideProps
的請(qǐng)求方式從fetch
遷移到axios
,借助axios
本身自帶的超時(shí)配置,添加接口超時(shí)處理。
方案比較
- 放棄
SSR
不可取,因?yàn)榭蛻舳瞬l(fā)請(qǐng)求返回順序的問(wèn)題可能會(huì)導(dǎo)致首頁(yè)的入場(chǎng)動(dòng)畫混亂,且失去了部分首屏性能優(yōu)化; - 修改
fetch
方法,需要手寫大量代碼,且AbortController
是在Node.js 14.17.0+
開(kāi)始支持,當(dāng)然也可以不去放棄掉請(qǐng)求,可以通過(guò)setTimeout
和Promise.race
提前reject
,不過(guò)需要因?yàn)槭亲孕袑?shí)現(xiàn),風(fēng)險(xiǎn)大。 axios
是十分成熟的請(qǐng)求庫(kù),且項(xiàng)目中已經(jīng)引入,通過(guò)axios
為請(qǐng)求添加超時(shí)處理十分方便。
實(shí)際解決方案
將fetch
遷移到axios
,借助axios
已有的超時(shí)處理配置解決當(dāng)前問(wèn)題,添加首頁(yè)對(duì)getServerSideProps
中請(qǐng)求超時(shí)的兜底,服務(wù)端請(qǐng)求超時(shí)后,立即返回空數(shù)組。
首頁(yè)所有的數(shù)據(jù)消費(fèi)組件繼續(xù)兜底,當(dāng)從getServerSideProps
中獲取的數(shù)據(jù)為空數(shù)組時(shí),會(huì)在客戶端再發(fā)一次請(qǐng)求,嘗試從客戶端獲取數(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 獲取所有請(qǐng)求的結(jié)果 const [ list1Res, // ... , list2Res, ] = await Promise.allSettled([ ssrAxios.get(`${addr}/api/list1`), // ... , ssrAxios.get(`${addr}/api/list2`), ]); // 處理每個(gè)請(qǐng)求的結(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 []; } };
問(wèn)題探究
問(wèn)題猜測(cè)
- 接口性能瓶頸:生產(chǎn)環(huán)境接口響應(yīng)時(shí)間波動(dòng)大,
SSR
并發(fā)請(qǐng)求存在短板 - SSR 超時(shí)機(jī)制缺失:未設(shè)置合理的請(qǐng)求超時(shí)閾值,導(dǎo)致慢請(qǐng)求影響頁(yè)面的生成和響應(yīng)
- 錯(cuò)誤處理不完善:前端錯(cuò)誤處理考慮不全面,未區(qū)分超時(shí)錯(cuò)誤與常規(guī)錯(cuò)誤,只做了無(wú)返回或返回錯(cuò)誤的處理,沒(méi)有對(duì)請(qǐng)求超時(shí)做兜底。
驗(yàn)證猜想
本地模擬接口返回超時(shí),首頁(yè)會(huì)無(wú)法成功加載。添加超時(shí)處理兜底后,首頁(yè)其他內(nèi)容可以正常展示。
詢問(wèn)deepseek
后,得知Next.js
的getServerSideProps
沒(méi)有內(nèi)置超時(shí)處理機(jī)制,因此長(zhǎng)時(shí)間運(yùn)行的getServerSideProps
可能導(dǎo)致頁(yè)面無(wú)法及時(shí)返回,甚至觸發(fā)部署平臺(tái)(如Vercel
)的超時(shí)錯(cuò)誤。
結(jié)論
首頁(yè)開(kāi)啟了SSR
渲染,Next.js
必須在完成getServerSideProps
中的所有
操作后才能生成頁(yè)面并返回給客戶端。 需要在得到所有需要的props
后,才會(huì)返回整個(gè)頁(yè)面。因?yàn)樯a(chǎn)環(huán)境某個(gè)接口請(qǐng)求超時(shí),并且沒(méi)有正確處理,可能會(huì)導(dǎo)致整個(gè)getServerSideProps
的執(zhí)行時(shí)間延長(zhǎng),超過(guò)服務(wù)器的響應(yīng)時(shí)間限制,從而使得頁(yè)面無(wú)法及時(shí)返回,出現(xiàn)超時(shí)錯(cuò)誤。
思考改進(jìn)
思考
雖然是因?yàn)楹蠖朔?wù)崩了,接口請(qǐng)求超時(shí)導(dǎo)致的問(wèn)題,但是因?yàn)椴糠址?wù)崩掉導(dǎo)致整個(gè)前端首頁(yè)無(wú)法成功加載,作為前端開(kāi)發(fā)人員的我還是有很大責(zé)任的。開(kāi)發(fā)時(shí)我在對(duì)可能出現(xiàn)的錯(cuò)誤進(jìn)行兜底時(shí),需要盡可能地考慮全面。
改進(jìn)
服務(wù)端請(qǐng)求優(yōu)化,錯(cuò)誤處理添加對(duì)請(qǐng)求超時(shí)的處理。
① 首次訪問(wèn)使用SSR獲取最新數(shù)據(jù);
② 當(dāng)SSR超時(shí)時(shí)自動(dòng)降級(jí)到 CSR版本 ;
③ 保持首屏加載性能的同時(shí)提升可用性。
以上就是Next.js服務(wù)端渲染超時(shí)導(dǎo)致生產(chǎn)事故的排查和解決辦法的詳細(xì)內(nèi)容,更多關(guān)于Next.js服務(wù)端渲染超時(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
原生JavaScript實(shí)現(xiàn)todolist功能
本篇文章給大家介紹了通過(guò)原生JavaScript實(shí)現(xiàn)todolist功能相關(guān)知識(shí)點(diǎn),對(duì)此有需要的朋友可以學(xué)習(xí)下。2018-03-03前端滾動(dòng)錨點(diǎn)三個(gè)常用方案(點(diǎn)擊后頁(yè)面滾動(dòng)到指定位置)
這篇文章主要給大家介紹了關(guān)于前端滾動(dòng)錨點(diǎn)的三個(gè)常用方案,實(shí)現(xiàn)的效果就是點(diǎn)擊后頁(yè)面滾動(dòng)到指定位置,三種方法分別是scrollIntoView、scrollTo和scrollBy,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01js中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的方法,具有一定參考價(jià)值,有興趣的可以了解一下。2017-02-02基于Express框架使用POST傳遞Form數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了基于Express框架使用POST傳遞Form數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08ES6中module模塊化開(kāi)發(fā)實(shí)例淺析
這篇文章主要介紹了ES6中module模塊化開(kāi)發(fā),結(jié)合實(shí)例形式分析了ES6中模塊化開(kāi)發(fā)的相關(guān)功能、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-04-04jQuery實(shí)現(xiàn)文字自動(dòng)橫移
本文詳細(xì)介紹了通過(guò)jquery尺寸相關(guān)函數(shù)實(shí)現(xiàn)文字自動(dòng)橫移的方法。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01php的派發(fā)機(jī)制實(shí)現(xiàn)方法
PHP是一種動(dòng)態(tài)類型的編程語(yǔ)言,它支持面向?qū)ο缶幊?在PHP中,派發(fā)指在運(yùn)行時(shí)確定要調(diào)用的方法或函數(shù)的過(guò)程,派發(fā)機(jī)制允許根據(jù)實(shí)際對(duì)象的類型來(lái)選擇要執(zhí)行的方法,這種靈活性使得PHP可以實(shí)現(xiàn)多態(tài)性,本文將給大家介紹php的派發(fā)機(jī)制是怎么實(shí)現(xiàn)的,需要的朋友可以參考下2023-10-10