原文发布于咱们前端团队的公众号,建议读者进入公众号看原文,CSDN的排版和配色太难受了,作为一个前端不能忍
随着前端技术栈和工具链的迭代成熟,前端工程化、模块化的趋势也愈发明显,在这波前端技术浪潮中,涌现了诸如React、Vue、Angular等基于客户端渲染的前端框架,这类框架所构建的单页应用(SPA)具有渲染性能好、可维护性高等优点。但也同时带来了两个缺陷:
1.首屏加载时间过长
2.不利于SEO
与传统web项目直接获取服务器端渲染好的HTML不同,单页应用使用JavaScript在客户端生成HTML来呈现内容,用户需要等待JS解析执行完成才能看到页面,这就使得首屏加载时间变长,影响用户体验。此外当搜索引擎爬取网站HTML文件时,单页应用的HTML没有内容,从而影响搜索排名。为了解决这两个缺陷,业界借鉴传统的服务器端直出HTML方案,提出在服务器端执行前端框架(React/Vue/Angular)代码生成HTML,然后将渲染好的HTML返回给客户端,实现CSR前端框架的服务器端渲染。
本文通过一个简单的demo,向读者讲解React服务器端渲染(SSR)的基本原理,在阅读完本文后,读者应该能够掌握:
服务器端渲染的基本概念和原理
在SSR项目中渲染组件
在SSR项目中使用路由
在SSR项目中使用redux
1.使用node进行服务端渲染
我们使用express启动一个Node服务器来进行基本的服务端渲染。首先安装初始化node项目和安装express
npm init
npm install express –save
在根目录中创建文件app.js,监听3000端口的请求,当请求根目录时,返回一些HTML
const express = require('express')
const app = express()
app.get('/', (req,res) => res.send(`
ssr demo
Hello world
`))
app.listen(3000, () => console.log('Exampleapp listening on port 3000!'))
鼠标右键查看网页源代码,这就是服务器端直接返回的HTML,我们已经完成了一个基本的服务端渲染。如果我们打开一个react项目并查看网页源代码,会发现代码中并没有页面内容对应的HTML,这是因为react所构建的SPA单页应用是通过在客户端执行JS动态地生成HTML,初始的HTML文件中并没有对应的内容。
2.在服务器端编写React代码
我们已经启动了一个Node服务器,下一步我们需要在服务器上编写React代码,我们创建一段这样的React代码并在app.js进行引用
import React from 'react'
const Home = () =>{
return home
}
export default Home
然而这段代码并不会运行成功,因为直接在服务器端运行React代码是行不通的,原因有以下几个:
Node不能识别import和export,这二者属于esModule的语法,而Node遵循common.js规范
Node不能识别JSX语法,我们需要使用webpack对项目进行打包转换,使之成为Node能识别的语法
为了使代码能够运行,我们需要安装webpack并进行配置
npm install webpack webpack-cli –save 安装webpack和webpack-cli
根目录下创建配置文件webpack.server.js并进行相关配置
const path = require('path') //node的path模块
const nodeExternals = require('webpack-node-externals')
module.exports = {
target:'node',
mode:'development', //开发模式
entry:'./app.js', //入口
output: { //打包出口
filename:'bundle.js', //打包后的文件名
path:path.resolve(__dirname,'build') //存放到根目录的build文件夹
},
externals: [nodeExternals()], //保持node中require的引用方式
module: {
rules: [{ //打包规则
test: /\.js?$/, //对所有js文件进行打包
loader:'babel-loader', //使用babel-loader进行打包
exclude: /node_modules/,//不打包node_modules中的js文件
options: {
presets: ['react','stage-0',['env', {
//loader时额外的打包规则,对react,JSX,ES6进行转换
targets: {
browsers: ['last 2versions'] //对主流浏览器最近两个版本进行兼容
}
}]]
}
}]
}
}
3.安装对应的babel
npm install babel-loaderbabel-core –save
npm install babel-preset-react –save
npm install babel-preset-stage-0 –save
npm install babel-preset-env –save
npm install webpack-node-externals –save
4.运行webpack --config webpack.server.js
5.启动打包后的文件node ./build/bundle.js
关于webpack的使用,比较陌生的读者可以参考我们公众号这篇:《webpack入门》
https://mp.weixin.qq.com/s/qtw3nKLyo-fSrs8cZ9_zHw
3.使用renderToString渲染组件
经过webpack对JSX和ES6进行打包转化后,我们还是无法正确运行我们的代码,以前在客户端渲染DOM时,我们使用下面的代码,但这段代码无法在服务端运行。
import Home from './src/containers/Home'
import ReactDom from 'react-dom'
ReactDom.render( , document.getElementById('root')) //服务端没有DOM
我们需要使用react-dom提供的renderToString方法,将组件渲染成为字符串再插入返回给客户端的HTML中
import express from 'express'
import React from 'react'//引入React以支持JSX的语法
import { renderToString } from 'react-dom/server'//引入renderToString方法
import Home from'./src/containers/Home'
const app= express()
const content = renderToString( )
app.get('/',(req,res) => res.send(`
ssr demo
${content}
`))
app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))
重新打包并重启服务器,我们就能在页面上看到服务器端渲染的组件
4.webpack自动打包和服务端自动重启
写到这里我们对之前的Node和webpack的启动方式做一个小优化,在这之前,我们每次对项目的改动,都需要重新执行webpack--config webpack.server.js和node ./build/bundle.js来重启项目,现在我们对package.json文件中的script做一些改动,使得服务器能够自动重启和打包
在webpack --config webpack.server.js后加上—watch就能实现webpack的自动监听打包,当需要被打包的文件发生变化时,webpack就会自动重新打包
安装nodemon,nodemon是nodemonitor的缩写,nodemon能够帮我们监听文件的变化并自动重启服务器,我们需要运行 npm install nodemon –g安装nodemon,在package.json的script配置项中添加这两句:
"scripts":{
"dev": "nodemon--watch build --exec node \"./build/bundle.js\"",
"build": "webpack--config webpack.server.js --watch"
},
在进行了以上两条配置后,我们开启两个终端,分别运行npm run dev和npm run build就能完成项目的自动打包和服务器重启
3.安装npm-run-all进一步简化流程:
运行npm install npm-run-all –g安装npm-run-all,并对package.json进行配置
"scripts": {
"dev": "npm-run-all--parallel dev:**",
"dev:start": "nodemon--watch build --exec node \"./build/bundle.js\"",
"dev:build": "webpack--config webpack.server.js --watch"
},
我们在原来的start和build加上dev前缀,表示这是开发环境所使用的命令,在线上环境时我们并不需要执行这两条命令去监听。配置好以后,运行npm run dev,我们就完成了自动打包和服务端启动重启,每次对代码的更改只需要刷新页面就能看到效果,不必像原来那样手动重新打包和重启服务器
5.同构的概念
我们在上面的过程中,已经将组件渲染到了页面上,下面我们为组件绑定一个点击事件。
import React from 'react'
const Home= () =>{
return (
home
)
}
export default Home
运行代码,刷新页面,我们会发现并没有执行对应的点击事件,这是由于renderToString只渲染了组件的内容,而不会绑定事件,为了能够给页面上的组件绑定事件,我们需要将React代码在服务端执行一遍,在客户端再执行一遍,这种服务器端和客户端共用一套代码的方式就称之为同构。
我们通过