前言
React SSR最成熟的開(kāi)源框架是Next.js涌萤,這么多年保持著強(qiáng)勁的生命力溯街,它的創(chuàng)始團(tuán)隊(duì)vercel(曾用名zeit),如今更關(guān)注于SSR和serverless的結(jié)合膛堤。隨著服務(wù)端的容器化技術(shù)以及serverless技術(shù)不斷完善,在國(guó)外可能SSR的降級(jí)已經(jīng)不是一個(gè)必要命題检激。但是齐莲,考慮到國(guó)內(nèi)的服務(wù)環(huán)境嚣鄙,今天我們還是有必要從前端的技術(shù)點(diǎn)討論一下如何去實(shí)現(xiàn)SSR的優(yōu)雅降級(jí)。
舊版本的Next.js是利用getInitProps
實(shí)現(xiàn)服務(wù)端渲染以及靜態(tài)站點(diǎn)生成鳍怨。在Next.js 9.3版本后呻右,getInitProps
這個(gè)api被替換成為三個(gè)不同的api,分別是:
-
getStaticProps
(靜態(tài)頁(yè)面生成SSG): 構(gòu)建的時(shí)候生成頁(yè)面 -
getStaticPaths
(靜態(tài)頁(yè)面生成SSG): 根據(jù)構(gòu)建內(nèi)容去生成動(dòng)態(tài)路由 -
getServerSideProps
(服務(wù)端渲染SSR): 在每個(gè)請(qǐng)求中在服務(wù)端獲取數(shù)據(jù)渲染頁(yè)面
這三個(gè)api的使用是對(duì)一個(gè)項(xiàng)目中不同頁(yè)面的更細(xì)程度的劃分鞋喇,它可以有效區(qū)分哪些頁(yè)面走SSR声滥、哪些頁(yè)面走CSR和SSG。高效的劃分了這三種不同的渲染模式确徙。
What is JAMstack 醒串?
靜態(tài)頁(yè)面生成SSG這種模式更加符合JAMstack的標(biāo)準(zhǔn),所有的頁(yè)面都是提前預(yù)渲染的鄙皇,靜態(tài)的頁(yè)面可以直接托管在CDN上芜赌,有效降低運(yùn)維成本,有助于你“高效下班”伴逸。Next.js官方建議你優(yōu)先使用靜態(tài)頁(yè)面生成缠沈,不得已才使用服務(wù)端渲染。但是靜態(tài)頁(yè)面不能滿足你的所有case错蝴。只有以下情況才比較適合靜態(tài)頁(yè)面生成:
- 數(shù)據(jù)能通過(guò)CMS接口有效渲染
- 數(shù)據(jù)能夠公開(kāi)緩存洲愤,并且不能用戶特有的
- 頁(yè)面必須預(yù)渲染,并且SEO敏感
Next.js已經(jīng)能夠在一個(gè)項(xiàng)目不同路由支持不同的渲染模式顷锰。
源碼參考 brandonxiang/example-nextjs 柬赐,頁(yè)面的邏輯放在modules文件夾里面,用一個(gè)自定義的函數(shù)getPrerenderProps
來(lái)保證頁(yè)面的預(yù)渲染邏輯官紫。這個(gè)預(yù)渲染邏輯如下肛宋,即獲取數(shù)據(jù)傳遞到組件當(dāng)中與Next.js的預(yù)渲染api類似。
// modules/Home.tsx
export const getPrerenderProps = async (ctx) => {
// SSG讀取環(huán)境變量束世,并作為兜底參數(shù)
const defaultLimits = process.env.limits || 0;
// SSR和CSR動(dòng)態(tài)渲染從URL上獲取參數(shù)
const _limits = (ctx?.query?._limits) || defaultLimits;
// 獲取遠(yuǎn)程動(dòng)態(tài)數(shù)據(jù)
const res = await axios.get(
'https://jsonplaceholder.typicode.com/photos?_limit=' + _limits
)
// 傳遞給各種渲染模式
return { props: { photos: res.data } }
}
自定義頁(yè)面渲染函數(shù)Page
來(lái)保證頁(yè)面dom的渲染酝陈,這里的目標(biāo)是“一份核心代碼,多種渲染模式”毁涉。數(shù)據(jù)photos則會(huì)在頁(yè)面中渲染沉帮。
// modules/Home.tsx
function Home({ photos }) {
let _photos = photos || []
return (
<div className="photos">
{
_photos.map((photo, index) => (
<figure key={index}>
<img src={photo.thumbnailUrl} alt={photo.title} />
<figcaption>{photo.title}</figcaption>
</figure>
))
}
</div>
)
}
export const Page = Home;
然后將它渲染到三種不同的模式當(dāng)中。由于 Next.js 的文件路由設(shè)定,頁(yè)面需要被設(shè)置成為三種:
- index.js SSR模式
- index_ssg.js SSG模式
- index_csr.js CSR模式
Next.js如何實(shí)現(xiàn)SSR
SSR模式需要將自定義的getPrerenderProps
輸出到頁(yè)面級(jí)別Next.js API的getServerSideProps
當(dāng)中穆壕,獲取數(shù)據(jù)的邏輯將會(huì)提前在服務(wù)端完成待牵。此時(shí),服務(wù)端可以實(shí)現(xiàn)頁(yè)面的動(dòng)態(tài)渲染喇勋。Page
則返回給整個(gè)頁(yè)面的渲染函數(shù)洲敢。
// index.js
export {
Page as default,
getPrerenderProps as getServerSideProps
} from '../modules/Home';
Next.js如何實(shí)現(xiàn)CSR
CSR模式則是自定義的getPrerenderProps
在useEffect中渲染,在頁(yè)面加載之后茄蚯,重新對(duì)頁(yè)面進(jìn)行渲染,達(dá)到一個(gè)客戶端渲染的效果睦优。路由參數(shù)發(fā)生變化渗常,頁(yè)面會(huì)重新進(jìn)行渲染,保證的頁(yè)面的動(dòng)態(tài)可用汗盘。這種模式頁(yè)面的渲染會(huì)比較慢皱碘,時(shí)長(zhǎng)主要是請(qǐng)求時(shí)長(zhǎng)。
// index_csr.js
export default () => {
const router = useRouter();
const [extraProps, setExtraProps] = useState({});
useEffect(() => {
getPrerenderProps(router).then(({props}) => {
setExtraProps(props);
})
}, [router]);
return <Page {...extraProps}/>
}
Next.js如何實(shí)現(xiàn)SSG
SSG則是靜態(tài)預(yù)渲染隐孽,參數(shù)不能動(dòng)態(tài)從路由傳入癌椿,只能構(gòu)建的時(shí)候以環(huán)境變量的形式傳入,所以頁(yè)面渲染需要采用特殊的兼容讀取方式菱阵。
將自定義的getPrerenderProps
輸出到頁(yè)面級(jí)別Next.js API的getStaticProps
當(dāng)中踢俄,實(shí)現(xiàn)靜態(tài)渲染。
// index_ssg.js
export {
Page as default,
getPrerenderProps as getStaticProps
} from '../modules/Home';
如何將SSR降級(jí)成為CSR
SSR服務(wù)端渲染由于是依賴服務(wù)器資源晴及,在流量過(guò)大的情況下都办,有可能會(huì)出現(xiàn)服務(wù)不可用的情況,返回特殊的錯(cuò)誤碼例如500等虑稼。這時(shí)候我們可以實(shí)現(xiàn)優(yōu)雅降級(jí)琳钉,利用 nginx 做對(duì)應(yīng)的流量分發(fā),當(dāng)SSR頁(yè)面返回異常錯(cuò)誤的時(shí)候蛛倦,nginx會(huì)將流量導(dǎo)入到CSR頁(yè)面當(dāng)中歌懒。
SSR頁(yè)面和CSR頁(yè)面基于Next.js采用同樣的業(yè)務(wù)邏輯編寫方式,有效保證頁(yè)面邏輯的一致性溯壶,一份代碼兩端復(fù)用及皂。
總結(jié)
Next.js是非常成熟高效的服務(wù)端渲染框架,本文通過(guò)一些取巧的方式來(lái)實(shí)現(xiàn)“一份代碼茸塞,多種渲染方式”躲庄,既能提高頁(yè)面的性能,也能夠保證頁(yè)面的優(yōu)雅降級(jí)钾虐。多種渲染模式采用同一份代碼噪窘,保證了邏輯的一致性,有效地為QA節(jié)省了回歸人力。在“質(zhì)量”和“性能”上找到了一個(gè)很好的平衡點(diǎn)倔监。