SSR已经不是什么新词、新概念了。为了解决React、Vue这类单页面(SPA)带来的首屏过慢以及SEO等问题随之而出。Vue、React官方也一直再跟进相关的支持。
同时社区也诞生了Next.js(React)以及Nuxt(vue)这类已经配置好,开箱即用的SSR解决方案。
之前在研究学习Webpack的时候,折腾过React SSR的配置和使用。从零配置Webpack4.0搭建一个React工程并且在蛮多自己瞎搞的项目中使用过,这几周自己再折腾SSR与Redux的结合。这一全面总结一下,就当温故知新了。
什么是服务端渲染/为什么使用服务端渲染
服务端渲染顾名思义浏览器通过url向服务器发送请求,服务器处理好页面的内容将整个页面返还给浏览器。每当请求一个url,服务器要将该url对应的页面内容发送给浏览器。
而SPA单页面应用则是在一开始一股脑将整个bundle.js加载进来。具体流程如下:
浏览器发送请求 -> 服务器返回HTML(空的骨架) -> 浏览器发送bundle.js -> 服务器返回bundle.js ->浏览器执行React/Vue代码
之后所有的url变成操作都通过这个js来处理,不需要再重新加载整个页面。web应用更具响应性,用户体验更好。但是由于一开始要加载一大坨bundle.js就会带来首屏过慢的问题。由于整个网页的内容都在js中,自然而然html源码什么都没有,不利于SEO。
为了解决痛点SSR就来了。它不是传统意义的服务端渲染,只是首屏渲染
,首屏通过服务器直接生成,之后的由客户端(浏览器)接管。这样既能解决首屏速度、SEO还能同事拥有单页面应用带给用户的流畅性。(这里有一个专有名词叫做:同构–一套代码在服务器执行一遍,在客户端执行一遍) 具体流程如下:
浏览器发送请求 -> 服务器运行React代码生成页面 -> 服务器返回页面 -> 浏览器加载bundle.js -> bundle.js的React代码在浏览器重新执行 -> bundle.js的React重新接管页面操作。
这里主要说说React中的SSR核心由于虚拟DOM的存在(真实DOM的JS对象映射)
在SPA中
1 | ReactDom.render(<Home />, id/root) |
在SSR中虚拟dom的存在让SSR变得很简单
1 | renderToString(<Home />) |
把虚拟dom转化为字符串,返给服务器就是了
Simple Demo
首先可以在分别创建server文件夹及client文件夹。我们这里简单的写写一套相同内容的代码
server/index.js
1 | import express from 'express' |
client/index.js
1 | import React from 'react' |
client端就是我们熟悉的React代码这里就不在废话
sever、client共用了一个路由配置
routes.js
1 |
|
这里着重说一下webpack配置.这里拆成个三个文件。webpack.base.js 、webpack.client.js、webpack.server.js
1 | // webpack.base.js |
1 | // webpack.client.js |
1 | // webpack.server.js |
这样一个最简单的SSR Demo就搞定了
SSR与Redux、React-Router
SSR同构配合Redux使用还蛮复杂的,中间涉及到数据的注水和脱水。首先我们面临的第一个问题是server端怎么根据路由,往store里面加数据。
这里我们通过数组对象的方式设置Route。完整Project: https://github.com/laclys/rengar
比如:
1 | export default [{ |
这么一个路由。用过loadData
进行server端对应数据的拉取(server端没有生命周期)。比如在Home这个组件里我们定义了一个loadData静态方法
1 | Home.loadData = (store) => { |
顺便提一下客户端拉取数据当然
1 | componentDidMount() { |
有了这些准备就可以在server端生成首屏的html。这里import { matchRoutes } from 'react-router-config'
使用matchRoutes来遍历routes
1 | app.get('*', function (req, res) { |
渲染html的render函数
1 | export const render = (store, routes, req, context) => { |
其中
1 | <script> |
通过这种方式将server端已经获取的store数据进行注入。到时客户端js接管之后直接从context中拿
client/index:
1 | const store = getClientStore() |
getClientStore
1 | export const getClientStore = () => { |
thunk.withExtraArgument
是redux-thunk中一个可以传入一个值。在使用redux-thunk时候调用
顺道提一下服务器端获取请求cookie这块。可以通过axios的create方法拿到
1 | const createInstance = (req) => axios.create({ |
以上!
捣鼓之后SSR确实是一个有意思的东西。很多东西不能说是坑,只能说是自己还没了解到。后续还会再看看CSS样式在server端中的处理和更好的SEO处理。