详解Next.js页面渲染的优化方案


Posted in Javascript onJanuary 27, 2019

在过去一年的工作中我所使用的js框架是Next.js,尽管这个框架在前后端同构方面有着绝佳的体验,但是当页面js文件过大以及preload过多的时候还是会出现页面跳转卡顿和渲染阻塞等比较糟糕的用户体验问题。由于我之前既不知道这个框架的工作原理,自然也就不知道如何去优化它。乘着农历春节前工地活少所以稍微研究一下。

第一个问题:宣称前后台同构的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失效了,更为糟糕的是卡顿感十足。

可能有人会说,那就不要写内联style不就好了。但是事实是在大量的后台数据动态渲染页面和用户自定义页面的情况下,不可能做到完全不写内联样式,而去傻乎乎地写一堆className。

所以我们要解决一个问题那就是如何保证,内联style的react dom在首次渲染页面的时候是服务器端直接输出后扔给后台,而不是让 commons.xxxxx.js 卡卡卡卡卡,然后砰的一下蹦出来。

要解决上一个问题,首先要了解Next.js是如何渲染页面的?

在Next.js的规则中,所有页面级的代码都是写在pages文件夹中,比如/pages/home:

export default () => (<div>你瞅啥?这是home页</div>)

而其框架内置的Document组件中,已经帮开发者配置好传统的HTML文件的 <head> , <body> 这些标签作为静态资源的外壳。Document组件中有一个 renderPage() 方法,如果代码正常运行,该方法就会将pages文件夹中的代码和它外部同步渲染到浏览器中。如果开发者希望自定义Document组件只需添加 /pages/_document.js 文件即可。

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" rel="external nofollow" ></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 ,您老人家就安静地慢慢地加载吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript + jquery实现定时修改文章标题
Mar 19 Javascript
JQuery实现的购物车功能(可以减少或者添加商品并自动计算价格)
Jan 13 Javascript
js实现精确到秒的倒计时效果
May 29 Javascript
Three.js学习之网格
Aug 10 Javascript
关于微信jssdk实现多图片上传的一点心得分享
Dec 13 Javascript
微信小程序 switch组件详解及简单实例
Jan 10 Javascript
vue2.0实战之使用vue-cli搭建项目(2)
Mar 27 Javascript
Vue在页面右上角实现可悬浮/隐藏的系统菜单
May 04 Javascript
手把手教你vue-cli单页到多页应用的方法
May 31 Javascript
浅谈vux之x-input使用以及源码解读
Nov 04 Javascript
如何在微信小程序中使用骨架屏的步骤
Jun 12 Javascript
vue的hash值原理也是table切换实例代码
Dec 14 Vue.js
在Vue项目中取消ESLint代码检测的步骤讲解
Jan 27 #Javascript
在Vue项目中引入JQuery-ui插件的讲解
Jan 27 #jQuery
在vue项目中使用Jquery-contextmenu插件的步骤讲解
Jan 27 #jQuery
jQuery实现左右两个列表框的内容相互移动功能示例
Jan 27 #jQuery
vue中子组件传递数据给父组件的讲解
Jan 27 #Javascript
jQuery实现模拟搜索引擎的智能提示功能简单示例
Jan 27 #jQuery
JavaScript中的回调函数实例讲解
Jan 27 #Javascript
You might like
php获取URL中带#号等特殊符号参数的解决方法
2014/09/02 PHP
CodeIgniter基于Email类发邮件的方法
2016/03/29 PHP
PHP堆栈调试操作简单示例
2018/06/15 PHP
基于PHP实现用户登录注册功能的详细教程
2020/08/04 PHP
中文字符串截取的js函数代码
2013/04/17 Javascript
javascript使用window.open提示“已经计划系统关机”的原因
2014/08/15 Javascript
JS控制文本域只读或可写属性的方法
2016/06/24 Javascript
详解AngularJS中ng-src指令的使用
2016/09/07 Javascript
javascript实现图片左右滚动效果【可自动滚动,有左右按钮】
2016/09/19 Javascript
判断数组的最佳方法(推荐)
2016/10/11 Javascript
微信小程序 页面传参实例详解
2016/11/16 Javascript
js 作用域和变量详解
2017/02/16 Javascript
JavaScript制作简单的框选图表
2017/05/15 Javascript
JavaScript实现数值自动增加动画
2017/12/28 Javascript
jQuery length 和 size()区别总结
2018/04/26 jQuery
基于jquery实现左右上下移动效果
2018/05/02 jQuery
vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】
2018/08/29 Javascript
vue+axios+promise实际开发用法详解
2018/10/15 Javascript
jQuery 图片查看器插件 Viewer.js用法简单示例
2020/04/04 jQuery
js实现简单的无缝轮播效果
2020/09/05 Javascript
[01:32]2014DOTA2西雅图邀请赛 CIS我们有信心进入正赛
2014/07/08 DOTA
[36:52]DOTA2真视界:基辅特锦赛总决赛
2017/05/21 DOTA
Python装饰器用法实例总结
2018/02/07 Python
对python numpy数组中冒号的使用方法详解
2018/04/17 Python
python使用pipeline批量读写redis的方法
2019/02/18 Python
pyftplib中文乱码问题解决方案
2020/01/11 Python
Django CBV模型源码运行流程详解
2020/08/17 Python
解决python3输入的坑——input()
2020/12/05 Python
html5 css3网站菜单实现代码
2013/12/23 HTML / CSS
ETO男装官方网店:ETO Jeans
2019/02/28 全球购物
在购买印度民族服饰:Soch
2020/09/15 全球购物
员工评语范文
2014/12/31 职场文书
民事上诉状范文
2015/05/22 职场文书
关于环保的宣传稿
2015/07/23 职场文书
tensorflow中的数据类型dtype用法说明
2021/05/26 Python
vue+echarts实现多条折线图
2022/03/21 Vue.js