做 react SSR 有一段時(shí)間了, 正好在這里分享一下.
什么是服務(wù)端渲染
用戶第一次請求/刷新頁面時(shí), 由服務(wù)端響應(yīng). 服務(wù)器響應(yīng)的是一個(gè)已經(jīng)插入了組件文本的模板 ( template with pre-populated components ). 由于響應(yīng)的頁面已經(jīng)有組件的 HTML 文本, 可以省去瀏覽器端首次渲染的工作, 加快首屏顯示速度, SEO 友好.
服務(wù)端渲染時(shí)瀏覽器的工作流程
瀏覽器接收到服務(wù)器預(yù)渲染的模板后, 渲染 DOM, 然后開始下載 js 文件(bundle), 下載完成后 react 開始工作, 生成VDOM. 由于使用了 SSR hydrate() API, VDOM 上組件的 mount 行為 (也就是組件從 VDOM 到 真實(shí) DOM 的第一次的 render) 將會(huì)被跳過. Mobx/Redux 和 React Router 開始工作.
余后的頁面內(nèi)的鏈接 ( <Link> ) 跳轉(zhuǎn)皆由 react router 接管.
服務(wù)端渲染時(shí)的工程架構(gòu)
服務(wù)端渲染時(shí), 服務(wù)器和瀏覽器共用一套模板, 一套組件.
兩個(gè)入口文件 (兩個(gè) bundle).
服務(wù)器需要一個(gè) 服務(wù)端 bundle, 對應(yīng)的 webpack 編譯配置需要適配服務(wù)端環(huán)境 (nodejs commonjs 模塊系統(tǒng)).
開發(fā)環(huán)境下的服務(wù)端渲染工作可以和 webpack devServer 協(xié)作, 實(shí)現(xiàn)開發(fā)時(shí)服務(wù)端渲染的熱加載.
流程
服務(wù)器響應(yīng)
在客戶端入口組件應(yīng)用 ReactDOM.hydrate() 替換 ReactDOM.render()
在服務(wù)端使用 ReactDOMServer.renderToString() 將組件轉(zhuǎn)化為字符串, 插入模板中, 響應(yīng)給客戶端.
實(shí)現(xiàn)首次加載時(shí)數(shù)據(jù)同步
- Mobx 的服務(wù)端渲染配置: 參考官方文檔.
- 前后端數(shù)據(jù)傳遞: 通過腳本注入的方法向模板插入一個(gè) js 對象, 以此傳遞數(shù)據(jù)給瀏覽器, 實(shí)現(xiàn) VDOM 和 DOM 上組件/元素的數(shù)據(jù)同步.
window.__INITIAL_STATE__ = <%%-initialState %>
服務(wù)端 React Router 設(shè)置
服務(wù)端需要使用 React Router <StaticRouter> 組件.
- 通過 <StaticRouter> 上 location 自動(dòng)實(shí)現(xiàn)對 <Route> 的匹配 (響應(yīng)).
- 通過 <StaticRouter> 上的 context 屬性手動(dòng)實(shí)現(xiàn)對 <Redirect> 的響應(yīng). 參考 React Router 官方文檔.
前端路由的數(shù)據(jù)獲取
定義接口: 可以在 routes 文件內(nèi)定義用于獲取數(shù)據(jù)的 API, 也可以在組件內(nèi)部提供靜態(tài)方法來獲取數(shù)據(jù)的 API.
調(diào)用接口: 在組件的生命周期函數(shù)中調(diào)用這些 API.
針對 SEO 搜索引擎爬蟲的配置.
動(dòng)態(tài)渲染 <head>
以上, 代碼細(xì)節(jié)還是比較多的. 使用了 Material-UI 的話也需要做相關(guān)的配置.
示例代碼參考:
這個(gè) repo 是一個(gè) React + React Router 4 SSR 的 React & React Router 4 Server Side Rendering Boilerplate