React SSR 介绍

什么是客户端渲染

CSR:Client Side Rendering

数据和 HTML 的拼接是在客户端(浏览器)使用 JavaScript 完成的,服务端只需要返回 JSON 数据即可。

使用 React 开发的项目是客户端渲染的单页应用(SPA)。

什么是服务器端渲染

SSR:Server Side Rendering

数据和 HTML 的拼接是在服务器端完成的,客户端向服务器端发送请求,服务器端返回拼接好的 HTML,客户端只需将其显示出来。

客户端渲染存在的问题

  1. 首屏等待时间长,用户体验差。
  2. 页面结构为空,不利于 SEO。

CSR 过程

  1. 用户首次访问页面,客户端向服务器发送请求,服务端返回 HTML,但是 HTML 文档只包含一些 CSS 资源链接和 JS 资源链接,等待期间页面没有展示的内容。
  2. 浏览器识别 HTML 文档,向服务器请求资源文件,请求过程中页面上没有展示内容。
  3. 加载完资源文件,就要执行对应的 JS 脚本,在执行 JS 脚本的时候又要向服务器获取当前页面所需要的数据,请求数据的过程中,页面上依然没有展示内容(或者渲染了一些静态内容,但是展示动态数据的地方还是空的)。
  4. 当数据请求完,在客户端又要使用 JavaScript 把请求的 JSON 数据和 HTML 拼接好,将拼接好的结果显示在页面中。到此页面才算加载完成。

在整个过程中,页面是没有内容或内容不完整的。即首屏等待时间长,用户体验差。

搜索引擎爬虫通常只会解析该请求返回的 HTML 内容,除非是针对性的爬虫,否则会忽略其它资源和请求数据的请求。

而用户请求客户端渲染的页面获取的 HTML 文档,页面结构是空的(浏览器查看页面源代码),所以爬虫抓取不到什么内容,也就不利于 SEO。

SSR 过程

  1. 用户首次访问页面,客户端向服务器端发送请求,服务器端将数据和 HTML 拼接好的结果返回给客户端。此时浏览器还是处于等待状态。
  2. 浏览器执行服务器端返回的 HTML 内容,它是包含了服务器端数据的,所以用户可以看到页面内容,只不过现在页面上展示的只有静态的 HTML 内容,没有动效果,浏览器还是要向服务端发送请求,获取资源文件。
  3. 当请求完 JS 文件,浏览器就会执行 JS 脚本,执行完成后页面就会有动态效果。

整个过程,用户早早的就能看到页面内容,只不过没有动态效果,要等待JS文件请求并执行完成后才会有动态效果。

用户可以更快看到页面内容,解决了首屏加载慢的问题。

而服务器页面请求返回的是完整的 HTML 内容,搜索引擎的爬虫工具能抓取相关内容,从而解决了 SEO 的问题。

同构

同构指的是代码复用。

单纯的 SSR 只是展示静态内容的传统技术,我们仍需要 SPA 交互体验。

最好的方案就是 SSR + SPA 相结合,即在实现服务端渲染的时候,还要实现客户端渲染,首次访问页面是服务端渲染,基于首次访问的后续的交互就是 SPA 的效果,这样就保留了两个技术的优点。

两种技术有大量可重用的代码,客户端路由、服务器端路由、客户端 Redux、服务器端 Redux 等,最大程度的复用这些代码,就是同构

现在所说的服务端渲染基本上都是 SSR + SPA 的同构渲染,不是传统上的服务端渲染。

服务端渲染快速开始

项目结构

创建 react-ssr 文件夹,创建项目结构:

└─ src                   # 源代码文件夹├─ client           # 客户端代码├─ server            # 服务器端代码└─ share            # 同构代码

安装依赖

# 自动重新执行node命令的工具
nodemon
# babel
@babel/cli
@babel/core
@babel/preset-env
@babel/preset-react
babel-loader
# Node 服务器
express
# react
react
react-dom
# webpack
webpack
webpack-cli

创建 Node 服务器

// src/server/http.js
import express from 'express'const app = express()app.listen(3000, () => console.log('app is running on 3000 port'))export default app
// src/server/index.js
import app from './http'app.get('/', (req, res) => {// 返回响应内容
})

React SSR 实现

实现步骤:

  1. 引入要渲染的 React 组件
  2. 通过 renderToString 方法将 React 组件转换为 HTML 字符串
  3. 将结果 HTML 字符串响应到客户端

renderToString方法用于将 React 组件转换为 HTML 字符串,通过 react-dom/server 导入。

对于要渲染的 React 组件,属于客户端和服务端同构代码,所以放到 share 文件夹下。

// src\share\pages\Home.js
import React from 'react'function Home() {return <div>Home works</div>
}export default Home
// src/server/index.js
import app from './http'
import React from 'react'
import { renderToString } from 'react-dom/server'
import Home from '../share/pages/Home'app.get('/', (req, res) => {const content = renderToString(<Home />)res.send(`<html><head><title>React SSR</title></head><body><div id="root">${content}</div></body></html>`)
})

服务端程序 webpack 打包配置

Node 环境不支持 ESModule 模块系统,不支持 JSX 语法。

所以需要通过 webpack 调用 Babel 对服务端代码进行打包,然后运行打包后的代码启动项目。

// webpack.server.js
const path = require('path')module.exports = {mode: 'development',target: 'node',entry: './src/server/index.js',output: {path: path.join(__dirname, 'build'),filename: 'bundle.js'},module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env', '@babel/preset-react']}}}]}
}

配置打包命令:

// package.json
"scripts": {// 服务器端打包命令(监听文件变化)"dev:server-build": "webpack --config webpack.server.js --watch",// 服务器端启动命令(监听打包目录文件变化)"dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\""
},

执行打包命令:

npm run dev:server-build

新开命令行窗口运行打包结果:

npm run dev:server-run

访问http://localhost:3000/,查看页面源代码包含了页面展示的内容。

现在就完成了一个简单的服务端渲染。

hydrate 客户端二次渲染

为什么要二次渲染

在 React 组件中为元素添加事件,经过服务端渲染后并没有被保留下来:

// src\share\pages\Home.js
import React from 'react'function Home() {return <div onClick={() => {console.log('click')}}>Home works</div>
}export default Home

渲染结果:

<div id="aa" data-reactroot="">Home works</div>

这是因为 renderToString 渲染的 HTML 是纯静态 HTML,没有任何交互效果。

解决办法:在客户端二次“渲染”。

react-dom 提供的 hydrate 方法类似 render 方法,用于二次渲染。

它在渲染的时候会复用原本已经存在的 DOM 节点,减少重新生成节点以及删除原本 DOM 节点的开销,只进行事件处理绑定。

hydraterender 的区别就是 hydrate 会复用已有节点,render 会重新渲染全部节点。

所以hydrate 主要用于二次渲染服务端渲染的节点,提高首次加载体验。

二次渲染组件

// src\client\index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Home from '../share/pages/Home'ReactDOM.hydrate(<Home />, document.getElementById('root'))

这个文件将作为静态资源文件被客户端请求。

但是当前代码无法在浏览器环境直接运行,需要 webpack 打包编译。

客户端 webpack 打包配置

webpack 打包配置:

  • 打包目的:转换 JSX 语法,转换浏览器不识别的高级 JS 语法。
  • 打包目标位置:public 文件夹(存放客户端请求的静态资源文件)。
// webpack.client.js
const path = require('path')module.exports = {mode: 'development',entry: './src/client/index.js',output: {path: path.join(__dirname, 'public'),filename: 'bundle.js'},module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env', '@babel/preset-react']}}}]}
}

配置打包命令:

// package.json
"scripts": {// 客户端打包"dev:client-build": "webpack --config webpack.client.js --watch","dev:server-build": "webpack --config webpack.server.js --watch","dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\""
},

新开命令行窗口执行打包命令:

npm run dev:client-build

引入静态资源文件

客户端的打包的 JS 文件会被作为静态资源引入,打包文件存放在 public 文件夹,服务器端需要为这个目录配置静态资源访问功能,这样从根目录访问的静态资源文件会去 public 文件夹下寻找。

// src/server/http.js
import express from 'express'const app = express()// 配置静态资源访问
app.use(express.static('public'))app.listen(3000, () => console.log('app is running on 3000 port'))export default app

在服务端返回的 HTML 中添加引入静态资源文件的 script 标签:

// src/server/index.js
import app from './http'
import React from 'react'
import { renderToString } from 'react-dom/server'
import Home from '../share/pages/Home'app.get('/', (req, res) => {const content = renderToString(<Home />)res.send(`<html><head><title>React SSR</title></head><body><div id="root">${content}</div><script src="bundle.js"></script></body></html>`)
})

现在点击事件就可以生效了。

优化

合并 webpack 配置

webpack.server.jswebpack.client.js 配置文件中存在一些相同的配置,可以将重复配置抽象到一个配置文件中。

安装 webpack 配置合并工具:

npm i webpack-merge

公共配置文件:

// webpack.base.js
module.exports = {mode: 'development',module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env', '@babel/preset-react']}}}]}
}

修改配置文件:

// webpack.client.js
const path = require('path')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base')const config = {entry: './src/client/index.js',output: {path: path.join(__dirname, 'public'),filename: 'bundle.js'}
}module.exports = merge(baseConfig, config)
// webpack.server.js
const path = require('path')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base')const config = {target: 'node',entry: './src/server/index.js',output: {path: path.join(__dirname, 'build'),filename: 'bundle.js'}
}module.exports = merge(baseConfig, config)

接着重新运行打包命令即可。

合并项目启动命令

现在启动项目需要运行三个启动命令。

通过 npm-run-all 工具可以合并多个命令的执行,解决多个命令启动的繁琐问题。

安装:

npm i npm-run-all

添加脚本:

"scripts": {"dev:client-build": "webpack --config webpack.client.js --watch","dev:server-build": "webpack --config webpack.server.js --watch","dev:server-run": "nodemon --watch build --exec \"node build/bundle.js\"",// 并行执行 `dev:` 开头的命令"dev": "npm-run-all --parallel dev:*"
},

打断所有命令,重新执行 npm run dev

服务器端打包文件体积优化

使用 webpack 打包服务端文件时,通常不希望打包 node_modules 下的依赖模块。

因为在服务端运行后端代码时应该已经安装了依赖,应用程序应该从 node_modules 目录下引入模块,而不是全部合并到打包文件增加打包文件大小。

解决:通过 webpack-node-externals 配置剔除打包文件中的依赖模块。

当前服务端打包文件大小为 1MB 左右。

配置优化工具:

// webpack.server.js
const path = require('path')
const { merge } = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base')const config = {target: 'node',entry: './src/server/index.js',output: {path: path.join(__dirname, 'build'),filename: 'bundle.js'},externals: [nodeExternals()]
}module.exports = merge(baseConfig, config)

优化之后文件大小为 8KB 左右。

服务器端代码模块化拆分

启动服务器代码渲染代码进行模块化拆分。

优化代码组织方式,渲染 React 组件的代码也是独立功能,所以把它从服务器端入口文件中进行抽离,方便后期维护。

// src\server\renderer.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import Home from '../share/pages/Home'export default () => {const content = renderToString(<Home />)return `<html><head><title>React SSR</title></head><body><div id="root">${content}</div><script src="/bundle.js"></script></body></html>`
}
// src/server/index.js
import app from './http'
import renderer from './renderer'app.get('/', (req, res) => {res.send(renderer())
})

React SSR - 01 SSR 介绍 和 快速开始相关推荐

  1. Jest 学习01 - Jest 介绍、快速体验、vscode 智能提示、配置、监视模式、Babel 配置

    起步 测试到底测什么 提到测试的时候,即使是最简单的一个代码块可能都让初学者不知所措.最常问的问题的是"我怎么知道要测试什么?".如果你正在写一个 Web 应用,那么依次测试每个页 ...

  2. 从零搭建一个基于React+Nextjs的SSR网站(四):如何搭建服务器并部署Nextjs项目

    个人博客源码:https://github.com/shaotianyu/blog-front PS: 如果你有疑惑,可以给我留言,咱们一起解决它. 从零搭建一个基于React+Nextjs的SSR网 ...

  3. 初识 SSR (SSR 入门 对产品经理,运营友好)

    前言 最近在大规模的改造SSR页面,所以对 SSR 技术有了一定的技术认识,想着写一篇文章来总结一下对于 SSR 初步的认识.这是一篇对产品经理友好的介绍性文章,具体的实现性技术文章在后头(主要是我们 ...

  4. React 360 初体验介绍与环境搭建

    React 360 初体验介绍 从这章节内容呢,我们来学习并了解下什么是react 360,并使用它来开发一个360度可旋转大屏的案例项目.接下来,我们就一起来逐步揭开它神秘的面纱吧! 我们本章节将会 ...

  5. React版本的todolist小demo 快速复习React(React)

    demo 介绍 该demo 就是帮你快速复习 react 的 事件传值,子组件传值,父传子,事件绑定,交互逻辑,react 语法,该demo有,显示隐藏列表,增加内容,删除todo,双击内容编辑是in ...

  6. Helm基本介绍及快速入门

    文章目录 Helm基本介绍及快速入门 一.Helm基本介绍 Helm简介 Helm 相关组件及概念 二.Helm部署 Helm客户端安装 校验是否安装成功 三.Helm 使用 使用仓库(helm re ...

  7. vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍

    这是我的vue.js 2.0的学习笔记,采取了将官方文档中的代码集中到一个文件的形式.目的是保存下来,方便自己查阅. !官方文档:https://cn.vuejs.org/v2/guide/ 01. ...

  8. React with Webpack -1: 介绍Helloworld

    React with Webpack -1: 介绍&Helloworld node.js 开发之react 学习1 context:node.js 开发的工具和lib发展的很快,in othe ...

  9. html5学习笔记---01.HTML5介绍,02.HTML5的新特性

    2013/6/10 01.HTML5介绍 a.创梦技术qq交流群:CreDream:251572072 -------------------- a.创梦技术qq交流群:CreDream:251572 ...

最新文章

  1. Yann LeCun专访:我不觉得自己有天分,但是我一直往聪明人堆里钻
  2. Java之基础(1) - 编程中“为了性能”尽量要做到的一些地方
  3. 第一次上计算机课日记500,第一次上网课作文500字
  4. header中Content-Disposition的作用与使用方法
  5. dig下载_DIG的完整形式是什么?
  6. 谈Servlet与JSP
  7. ubuntu中文输入法fcitx的安装以及出现方块的解决方法
  8. MongoDb和LINQ:如何汇总和加入集合
  9. WinForm------如何修改PanelControl控件背景色
  10. 华为Linux笔记本拆机,华为MateBook D怎么拆机?华为MateBook D拆机图文步骤详解
  11. 美国卡内基梅隆大学计算机排名,卡内基梅隆大学,美国卡梅基梅隆大学世界排名?...
  12. 教妹学 Java:晦涩难懂的泛型
  13. 阿里双十一,3分01秒破百亿;乐视网称贾跃亭无力履行承诺;法乐第未来宣布解职CFO和CTO丨价值早报
  14. 武田以3.22亿美元剥离中国大陆非核心业务至海森
  15. Cathy学习Java——反射和类的加载
  16. 网易云发送验证码短信,发送通知短信,java版
  17. 数据中台总体技术架构
  18. 深入理解 WIN32 PE 文件格式
  19. AI顶会ACL发榜,腾讯30篇论文入选
  20. Python爬取百度图片搜索结果

热门文章

  1. 墨天轮国产数据库沙龙 | 四维纵横姚延栋 :MatrixDB,All-in-One高性能时序数据库
  2. 黑客是如何攻破一个网站的?
  3. python表格控件_python表格控件
  4. LED高效恒流驱动电源的设计指导书
  5. 大学计算机类专业按成绩分,那个大学的计算机专业比较好
  6. 指导教师邱栋在2012年CSDN高校研讨会上的演讲
  7. 批量修改照片尺寸大小(适应冲洗的,比如6寸)【技术菜鸟】
  8. 来人!把朕的线性代数呈上来!
  9. Oracle(二)Oracle sql操作
  10. AWS IoT 物联网设备 - 即时部署 JITP 实战