Next.js頁面渲染的優化方案

在過去一年的工做中我所使用的js框架是Next.js,儘管這個框架在先後端同構方面有着絕佳的體驗,可是當頁面js文件過大以及preload過多的時候仍是會出現頁面跳轉卡頓和渲染阻塞等比較糟糕的用戶體驗問題。因爲我以前既不知道這個框架的工做原理,天然也就不知道如何去優化它。乘着農曆春節前工地活少因此稍微研究一下。html

第一個問題:宣稱先後臺同構的Next.js爲什麼會出現卡頓現象?

Next.js 中的特有生命週期hook 函數 getInitialProps會在頁面渲染的時候判斷瀏覽器是否爲首次渲染,若是是則是服務端渲染網頁,若是不是則是客戶端渲染。在頁面首次渲染的時候,會加載commons.xxxxx.js文件,這個文件中打包了react.js、next.js 以及相關的框架代碼也就是若是是客戶端渲染打包後的commons.xxxxx.js負載了整個前端的頁面邏輯,這個文件相對比較大通常會在180kb以上。若是僅從文件大小角度來講,這個文件並不算大,就算利用了next.js 的 preload機制把文件大小放到300kb以上,也還行。可是一旦這個文件阻塞了頁面的渲染,頁面的渲染要等到commons.xxxxx.js加載完畢以後才渲染,那問題就來了。前端

在next7中使用的打包工具是webpack4,這在打包和加載過程有一個比較蠢的機制(或許僅僅是我我的觀點),那就是但凡React DOM上綁定了style 這些DOM都不會在服務端渲染出來,而是打包抽離成一個小的js文件,在commons.xxxxx.js加載完畢以後,再加載這個js,將DOM和內聯style渲染到HTML。這就在某種程度上致使了next.js首次渲染是SSR失效了,更爲糟糕的是卡頓感十足。react

可能有人會說,那就不要寫內聯style不就行了。可是事實是在大量的後臺數據動態渲染頁面和用戶自定義頁面的狀況下,不可能作到徹底不寫內聯樣式,而去傻乎乎地寫一堆className。webpack

因此咱們要解決一個問題那就是如何保證,內聯style的react dom在首次渲染頁面的時候是服務器端直接輸出後扔給後臺,而不是讓commons.xxxxx.js卡卡卡卡卡,而後砰的一下蹦出來。git

要解決上一個問題,首先要了解Next.js是如何渲染頁面的?

在Next.js的規則中,全部頁面級的代碼都是寫在pages文件夾中,好比/pages/home:github

export default () => (<div>你瞅啥?這是home頁</div>)
複製代碼

而其框架內置的Document組件中,已經幫開發者配置好傳統的HTML文件的<head>,<body>這些標籤做爲靜態資源的外殼。Document組件中有一個renderPage()方法,若是代碼正常運行,該方法就會將pages文件夾中的代碼和它外部同步渲染到瀏覽器中。若是開發者但願自定義Document組件只需添加/pages/_document.js文件便可。web

renderPage()本質是一個回調函數,它的做用只有一個那就是執行React源碼中渲染邏輯同步加載到Next.js的Document組件中造成DOM節點。後端

import Document, { Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  static getInitialProps ({ renderPage }) {
    // renderPage()位於next.js特有生命週期函數getInitialProps中。 
    return renderPage();
  }

  render () {    
    return (
      <html> <Head> <title>沒見過標題黨嗎?</title> </Head> <body> <Main /> <NextScript /> </body> </html>
    )
  }
}
複製代碼

服務端渲染樣式

爲了能讓服務器端渲染樣式,咱們首先得先作兩件事:瀏覽器

  1. 在頁面首次加載的時候,也就是所謂的SSR.能讓renderPage方法在服務器端就能對React Dom進行解析,讓HTML歸HTML,CSS歸CSS;服務器

  2. 能讓Document組件在頁面切換時,能及時更新<head>,這樣不一樣的頁面就能加載本身所需的script,style。


解決方案的登場

隆重介紹神器styled-components出場,styled-components在github上目前爲止已經超過1萬stars,它的設計初衷在於在服務端渲染的時候,同時渲染出一個ServerStyleSheet,而後把這個ServerStyleSheet送入React DOM樹中。它主要就作兩件事:

  1. 把組件中styles抽離到<style>標籤中;
  2. <style>標籤放到<head>

下面就是一段如何正確使用ServerStyleSheet的姿式步驟:

import { ServerStyleSheet } from "styled-components";

  static getInitialProps ({ renderPage }) {
    const sheet = new ServerStyleSheet()
    const transform = (App) => {
      return sheet.collectStyles(<App />);      
    }
    const styleTags = sheet.getStyleElement()
    const page = renderPage(transform);
    return { ...page, styleTags };
  }
  
  render(){
      return(
       <html lang="zh-Hans">
        <Head>
          <meta name="viewport" content="initial-scale=1.0, width=device-width" />
          <meta name="description" content="Kanseefoil"/>
          <link rel="shortcut icon" href="/static/favicon.ico"></link>
          {this.props.styleTags}
        </Head>
        </html>
       );
  }
複製代碼

上面的代碼已經完美跟你們展現瞭如何將內聯style抽離出dom,而後經過<link style>的方法渲染樣式, 那麼問題來了,如何在打包解析react dom時,給服務器一個"純潔、乾淨、無暇"的DOM呢?

這個時候就須要使用babel-plugin-styled-components包,在babel中進行解析。

代碼以下:

{
    "presets": [
        "next/babel"
    ],
    "plugins": [
        ["styled-components", { "ssr": true, "displayName": true, "preprocess": false } ]
    ]
}
複製代碼

這個時候在去打開next.js頁面就會發現,那傢伙、那場面渲染速度嗖嗖的。至於負責前端邏輯的commons.xxxxx.js,您老人家就安靜地慢慢地加載吧。

相關文章
相關標籤/搜索