前言
React SSR最成熟的開源框架是Next.js,這么多年保持著強勁的生命力,它的創始團隊vercel(曾用名zeit),如今更關注于SSR和serverless的結合。隨著服務端的容器化技術以及serverless技術不斷完善,在國外可能SSR的降級已經不是一個必要命題。但是,考慮到國內的服務環境,今天我們還是有必要從前端的技術點討論一下如何去實現SSR的優雅降級。
舊版本的Next.js是利用getInitProps
實現服務端渲染以及靜態站點生成。在Next.js 9.3版本后,getInitProps
這個api被替換成為三個不同的api,分別是:
-
getStaticProps
(靜態頁面生成SSG): 構建的時候生成頁面 -
getStaticPaths
(靜態頁面生成SSG): 根據構建內容去生成動態路由 -
getServerSideProps
(服務端渲染SSR): 在每個請求中在服務端獲取數據渲染頁面
這三個api的使用是對一個項目中不同頁面的更細程度的劃分,它可以有效區分哪些頁面走SSR、哪些頁面走CSR和SSG。高效的劃分了這三種不同的渲染模式。
What is JAMstack ?
靜態頁面生成SSG這種模式更加符合JAMstack的標準,所有的頁面都是提前預渲染的,靜態的頁面可以直接托管在CDN上,有效降低運維成本,有助于你“高效下班”。Next.js官方建議你優先使用靜態頁面生成,不得已才使用服務端渲染。但是靜態頁面不能滿足你的所有case。只有以下情況才比較適合靜態頁面生成:
- 數據能通過CMS接口有效渲染
- 數據能夠公開緩存,并且不能用戶特有的
- 頁面必須預渲染,并且SEO敏感
Next.js已經能夠在一個項目不同路由支持不同的渲染模式。
源碼參考 brandonxiang/example-nextjs ,頁面的邏輯放在modules文件夾里面,用一個自定義的函數getPrerenderProps
來保證頁面的預渲染邏輯。這個預渲染邏輯如下,即獲取數據傳遞到組件當中與Next.js的預渲染api類似。
// modules/Home.tsx
export const getPrerenderProps = async (ctx) => {
// SSG讀取環境變量,并作為兜底參數
const defaultLimits = process.env.limits || 0;
// SSR和CSR動態渲染從URL上獲取參數
const _limits = (ctx?.query?._limits) || defaultLimits;
// 獲取遠程動態數據
const res = await axios.get(
'https://jsonplaceholder.typicode.com/photos?_limit=' + _limits
)
// 傳遞給各種渲染模式
return { props: { photos: res.data } }
}
自定義頁面渲染函數Page
來保證頁面dom的渲染,這里的目標是“一份核心代碼,多種渲染模式”。數據photos則會在頁面中渲染。
// 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;
然后將它渲染到三種不同的模式當中。由于 Next.js 的文件路由設定,頁面需要被設置成為三種:
- index.js SSR模式
- index_ssg.js SSG模式
- index_csr.js CSR模式
Next.js如何實現SSR
SSR模式需要將自定義的getPrerenderProps
輸出到頁面級別Next.js API的getServerSideProps
當中,獲取數據的邏輯將會提前在服務端完成。此時,服務端可以實現頁面的動態渲染。Page
則返回給整個頁面的渲染函數。
// index.js
export {
Page as default,
getPrerenderProps as getServerSideProps
} from '../modules/Home';
Next.js如何實現CSR
CSR模式則是自定義的getPrerenderProps
在useEffect中渲染,在頁面加載之后,重新對頁面進行渲染,達到一個客戶端渲染的效果。路由參數發生變化,頁面會重新進行渲染,保證的頁面的動態可用。這種模式頁面的渲染會比較慢,時長主要是請求時長。
// 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如何實現SSG
SSG則是靜態預渲染,參數不能動態從路由傳入,只能構建的時候以環境變量的形式傳入,所以頁面渲染需要采用特殊的兼容讀取方式。
將自定義的getPrerenderProps
輸出到頁面級別Next.js API的getStaticProps
當中,實現靜態渲染。
// index_ssg.js
export {
Page as default,
getPrerenderProps as getStaticProps
} from '../modules/Home';
如何將SSR降級成為CSR
SSR服務端渲染由于是依賴服務器資源,在流量過大的情況下,有可能會出現服務不可用的情況,返回特殊的錯誤碼例如500等。這時候我們可以實現優雅降級,利用 nginx 做對應的流量分發,當SSR頁面返回異常錯誤的時候,nginx會將流量導入到CSR頁面當中。
SSR頁面和CSR頁面基于Next.js采用同樣的業務邏輯編寫方式,有效保證頁面邏輯的一致性,一份代碼兩端復用。
總結
Next.js是非常成熟高效的服務端渲染框架,本文通過一些取巧的方式來實現“一份代碼,多種渲染方式”,既能提高頁面的性能,也能夠保證頁面的優雅降級。多種渲染模式采用同一份代碼,保證了邏輯的一致性,有效地為QA節省了回歸人力。在“質量”和“性能”上找到了一個很好的平衡點。