一、背景
會員中心頁面上線后,經過需求的不停迭代,頁面的fsp和秒開率表現的并不理想,還有不少提升的空間。基于此,需要做一些性能優化來進行改造。
針對其中的勛章首頁頁面的特點(偏靜態的內容展示場景),非常適合在該頁使用SSR進行優化改造。同時勛章頁面低端機型的性能表現明顯差于高端機型,通過服務端渲染,可以抹平手機網絡請求和渲染性能造成的差距,有效的提升低端機型的性能表現。
勛章首頁:偏靜態的內容展示場景
機型性能表現差別:
機型網絡請求差別:
二、技術方案
SSR簡介
SSR(Server Side Rendering): 服務端渲染
React的 SSR :
? 也叫同構,是服務端渲染與客戶端渲染的一個整合
? 它是在不改變前端開發方式的情況下實現服務端渲染的
? 用來解決純客戶端渲染帶來的?屏渲染慢、SEO不友好的問題
SSR和CSR的對比
csr模式:
ssr模式:
從圖中可以看出SSR 渲染的大致過程是:請求 Node 服務 -> Node 服務請求后端接口 -> Node 服務獲取數據后渲染首屏HTML -> 瀏覽器解析 HTML -> 加載靜態資源(CSS/JS)-> 渲染首屏。
可以看到 SSR 相比于 CSR,將首屏接口請求與渲染移到計算能力更強的服務端。
從時序上看,SSR 不需要加載 JS 文件,即可完成首屏呈現;
從接口請求的環境來看,SSR 請求頁面的數據和首屏 HTML 的渲染是在服務端進行的,相對于 CSR,在接口請求上,尤其是首屏依賴多個接口時,具有內網/專線的可靠、低時延優勢,可極大降低客戶端網絡環境不確定性和不穩定性所造成的網絡耗時。
SSR使用場景
適?場景
? 需要在瀏覽器上做 SEO
? 已經有?個運行中的 React 應用,需要最佳的性能并愿意為增加的服務?資源付費
? 數據展示型??
不適?場景
? 不需要做SEO
? 資源短缺(服務器or開發?員)
? ??需要大量計算(包含大量圖表)
三、項目中的核心實現
- 通過 getSSRData獲取到數據,并使用store方法將數據直接更改,之后進行服務端渲染。
const getSSRData = async (ctx: Context): Store => {
const { medalStore } = stores
const medalService = new MedalService()
const { token, uuid, userid } = ctx.query
try {
const res = await medalService.getMedals(ctx, {
headers: { token },
params: {
userid,
uuid,
},
})
if (res?.data) {
const {
recentMedals = [],
} = res?.data
medalStore.setRecentMedals(recentMedals)
medalStore.setIsSsrData(true)
}
} catch (e) {
console.log(e)
}
return medalStore
}
const serverEntry =
(App: () => JSX.Element, callback?: Function): TRender =>
async (url: string, ctx?: Context) => {
enableStaticRendering(true)
const stores = callback ? await callback(ctx) : undefined
return {
html: ReactDOMServer.renderToString(
<Provider {...stores}>
<App />
</Provider>
),
data: stores,
}
}
export const render = serverEntry(App, getSSRData)
備注:renderToString
? 將 React Component 轉化為 HTML 字符串
? 生成的 HTML 的 DOM 會帶有額外屬性(最外層 DOM 會有 data-reactroot 屬性)
2. 前端合并store,并進行水合操作
export const clientEntry = (App: () => JSX.Element, Store?: Store): void => {
const initialStores = window.__INITIAL_STATE__ ?? {}
const mergeMedalStore = Object.assign(stores.medalStore, initialStores.medalStore)
const hydrateDOM = (
<Provider medalStore={mergeMedalStore}>
<App />
</Provider>
)
ReactDOM.hydrate(hydrateDOM, document.getElementById('app'))
}
clientEntry(App)
備注: ReactDOM.hydrate
? 將事件監聽器?掛載到現有的 DOM 元素,而不是掛載整個 DOM
? 會修復客戶端渲染與服務端渲染中的部分不一致的場景
標簽不一致,直接使?用客戶端渲染結果
?本內容不?致,替換文本內容
不修復屬性不一致的情況
四、收益
整體
整體來看,勛章頁的秒開率從39%到了52%,提升了13%。首屏時間從2.7s到了2.9s,提升了200ms。
可以看到SSR明顯提升了秒開率,但是對于fsp并沒有相應的提升。
為啥呢?
React SSR 與 CSR ?屏渲染時間對?
Time To First Byte(TTFB): ?字節時間,用于衡量?絡和服務?響應性能
First Meaningful Paint(FMP):?面主要內容出現在屏幕上的時間點
-
Time To Interactive (TTI):布局已趨于穩定、關鍵的?網絡字體可?見、主要線程?以處理?戶輸入的時間點
從圖中可以看出SSR相比CSR的首屏時間是不一定會變好的。
所以整體效果算是符合預期的。
不同機型的表現分析
17和18號的數據
22和23號的數據
從數據中可以看到低端機型的秒開率由于ssr的改造,提升特別明顯。印證了之前說過的通過服務端渲染,可以抹平手機網絡請求和渲染性能造成的差距,有效的提升低端機型的性能表現。
而fsp(tp90)在高端機型上表現的反而差了,對低端機型的表現是提升的。這也可以理解。由于渲染主要放在node層做了,高端機型的網絡請求和渲染性能優勢體現的就不明顯了,而對低端機型來說反而變好了。