服务端渲染

471 阅读4分钟

服务端渲染


服务端渲染由来

在早期还没有前后端分离的概念时,前端工程师们所使用的只有css和js,而html由后端书写,后端配置路由。这种方式方便搜索引擎优化,毕竟服务端直接吐出HTML(服务端渲染)。以前的服务器渲染,是以“文档”为核心思想。服务器端的逻辑是把HTML, CSS, Javascript当作一个静态文件,对“文档”而言不存在“指令”和“数据”的区别,一切都是数据。后来node和mvc的兴起使得spa应用大量出现(客户端渲染)。但spa同样有个问题首屏渲染太慢。所以ssr服务端渲染就出现了(同构渲染)。 服务端渲染(多页面)=>客户端渲染(SPA)=>同构渲染(今天的服务端渲染)

  1. 服务端渲染
    优点:
    优秀的SEO 首屏加载快
    缺点:
    负载大:由于渲染任务都交由服务端进行,在高并发的情况下,对于服务端负载压力大,同时丧失了浏览器端作为一个天然分布式系统的优势。
    复用性能差:因为返回的是整个页面,对于每个路由都要重新进行页面刷新,复用性能 上不友好。
    前后端耦合严重,前端开发依赖于后端,开发形式上不友好
  2. 前端渲染
    优点:
    天然的浏览器分布式环境
    组件复用度高,还可以通过懒加载等进行性能的进一步优化
    除首屏外性能响应快速
    WEB/MOBILE多端渲染
    前后端分离开发
    缺点:
    首屏性能差:由于页面渲染、三方包等逻辑都置于一个JS文件中,首屏加载会导致一定时间的白屏现象。
    浏览器SEO:由于现阶段大多搜索引擎采用的爬虫算法是直接抓取页面代码分析,而SPA应用只有一个入口文件而没实质内容,SEO性能差。
  3. 同构
    优点:
    兼顾前端渲染的大部分优点和后端渲染SEO和首屏加载的优点
    缺点:
    需要额外的开发构建成本
    对服务器有一定负载

同构JavaScript应用开发

如何进行服务端渲染

  1. HTML文档
  2. 数据
  3. 路由

1. HTML
服务端渲染最关键的一步是在发送响应前渲染初始的 HTML。这就要使用 ReactDOMServer.renderToString()把React实例化的组件渲染成HTML标签

import Router from 'koa-router';
import ejs from 'ejs';
import React from 'react';
import {StaticRouter} from 'react-router-dom';
import {Provider} from 'react-redux';
// 把组件渲染成字符串
const html = renderToString(
    <Provider store={store}>
        <StaticRouter location={location} context={context}>
            {renderRoutes(routes)}
        </StaticRouter>
    </Provider>
)
//使用koa2 ejs模板拼接html
const ejs.render(tmpl, {
    title: utils.getHTMLTitle(location, store.getState()),
    initialState: state,
    html: initView
});

2. 数据
把数据发送到客户端,需要以下步骤:

  • 为每次请求创建全新的 Redux store 实例;
  • 按需 dispatch 一些 action;
  • 从 store 中取出 state;
  • 把 state 一同返回给客户端。

我们请求了ssr服务,服务在给我们吐页面之前,实例化一个createStore()对象,要将原本在客户端初始请求的那几个ajax在这发,这几个请求完成后都dispatch(action),然后store中就有初始状态了

3. 路由
之前

<Route path="/bar" component={Bar} />
<Route path="/baz" component={Baz} />
<Route path="/foo" component={Foo} />
<Route path="/top-list" component={TopList} />

改造过后

const router = [
  {
    path: "/bar",
    component: Bar
  },
  {
    path: "/baz",
    component: Baz
  },
  {
    path: "/foo",
    component: Foo
  },
  {
    path: "/top-list",
    component: TopList,
    exact: true
  }
];

为什么这么写一方面是方便拓展,避免路由嵌套,视图组件就会被分散到不同的组件中被import;另一方面是下一篇后面的函数式组件懒加载

StaticRouter可以根据location的url来指定渲染哪个组件,context.url指定重定向到的那个路由也就是说,要是访问 /StaticRouter会给我们重定向到/home,并且StaticRouter自动给context对象加了url,context.url就是重定向的/home,当不是重定向时,context.urlundefined

我们还可以自己写逻辑 通过context来处理302、404等。但这里不需要

除非是做全栈的同构,只服务端渲染了主页,渲染一个和多个差不多,全都渲染的话就是在服务端要根据当前请求的路由来决定要发那些请求来填充Store

对于访问/、/home这两个路由,代理到ssr服务,来吐首页内容,api代理到后端服务,其他的直接返回(也就是说如果在detail页面或user页面刷新了页面还是之前客户端渲染那套)

前端渲染网上的教程蛮多的,看了各种资料都不如自己瞎搞一搞