服务端渲染基础
服务端渲染概述
随着前端技术发展,涌现了许多优秀的基于客户端渲染CSR的前端框架,例如Angular,React,Vue等,这些框架使我们可以方便的构建SPA单页面应用
SPA单页面应用
- 优点
- 用户体验好
- 开发效率高
- 渲染性能好
- 可维护性好
- 缺点
- 首屏渲染慢
- 不利于SEO
为了解决SPA单页面应用的这两个缺点,开始借鉴传统的服务端渲染
在传统服务端渲染的基础上,完成首屏渲染,并在客户端激活为SPA应用,即为现代化的服务端渲染
现代化 服务端渲染
现代化的服务端渲染,也叫同构渲染
以这种方式构建的应用通常称为服务端渲染应用,也叫同构应用
- 通过服务端渲染首屏直出,解决SPA应用首屏渲染慢以及不利于SEO的问题
- 通过客户端渲染接管页面内容交互来得到更好的用户体验
什么是渲染?
这里的渲染简单的概括为将模板与数据拼接到一起的过程
传统的服务端渲染SSR
早期的动态web应用开发使用传统的服务端渲染SSR (Server Side Render),整体流程如下图
传统服务端渲染的步骤
- 获取页面模板
- 获取数据
- 渲染:将页面模板与数据组装成最终的HTML页面结果
- 把结果HTML返回给客户端
传统服务端渲染的缺点
- 前后端开发耦合在一起,不利于开发维护
- 渲染工作全部由服务端完成,服务端压力大
- 相较于SPA单页面应用,用户体验一般,每个页面切换都需要重新向服务端请求,受网络影响大,加载存在延迟
在前端页面不是太复杂的情况下,传统的服务端渲染也是完全可以接受的
客户端渲染CSR
客户端渲染CSR (Client Side Render) 是指由客户端向服务端请求要展示的动态数据,并在客户端完成页面模板与数据的拼接,并最终完成页面的展示,目前主流的前端框架应用大多都使用客户端渲染的方式,这种方式也很好的实现了开发过程的前后端分离,整体流程如下图
前后端分离
- 后端负责处理数据接口
- 前端负责将接口数据渲染到页面中
客户端渲染的缺点
- 首屏渲染慢
- 不利于SEO
为什么首屏渲染慢?
打开浏览器控制台,选择模拟慢速3G网络,并分别打开首页内容完全相同的客户端渲染页面与服务端渲染页面进行比较
查看客户端渲染页面加载的瀑布流,我们可以发现,客户端渲染页面的首屏渲染,至少需要经过3个http请求周期
- 先请求页面
- 通过分析返回的页面HTML,开始请求加载页面中的JS与CSS文件
- 加载JS文件以后,根据JS中的代码请求页面依赖的数据文件或接口,最终完成首屏渲染展示到浏览器中
而相比较下,服务端渲染的首屏页面只需要经过一个http请求周期,即可以完成渲染展示
通过上述简单页面的比较,可以了解到客户端渲染的SPA应用的首屏渲染通常是要慢于服务端渲染的应用的首屏渲染,这在网络速度慢时尤为明显
除了更多的请求周期,客户端渲染的SPA应用通常需要加载额外的渲染引擎、框架代码等JS文件,只有当这些JS文件加载完成后才能渲染首屏展示,虽然可以通过打包优化的方式,将首屏渲染用到的代码拆分到独立的JS文件中优先加载,但仍会增加网络开销
为什么不利于SEO?
客户端渲染的页面在向服务端请求时,获取的直出页面是一个空页面,仅包含一个用于挂载的根结点,只有当负责渲染的JS文件以及相应的数据请求加载完成之后,才会完成渲染操作,展示出最终页面效果
搜索引擎获取到的HTML字符串内容,无法进行SEO
目前除了google搜索以外,大部分搜索引擎不支持对SPA应用网站的页面内容抓取
现代化的服务端渲染
现代化的服务端渲染也叫同构渲染,结合了传统服务端渲染与客户端渲染各自的优点,使用服务端渲染来完成首屏页面的渲染,以解决首屏加载慢与SEO的问题,在返回客户端之后激活SPA,使后续页面操作获得更好的使用体验
- 基于React、Vue等框架,客户端渲染和服务端渲染相结合
- 服务端执行,实现服务端渲染(首屏直出)
- 客户端执行,接管页面交互
- 核心解决SEO和首屏渲染慢的问题
- 拥有传统服务端渲染的有点,也有客户端渲染的优点
如何实现同构渲染?
- 使用React、Vue等框架的官方解决方案
- 优点:有助于理解原理
- 缺点:需要搭建环境,比较麻烦
- 使用第三方解决方案
- React生态的Next.js
- Vue生态的Nuxt.js
通过Nuxt体验同构渲染
以Vue生态的Nuxt.js为例,简单的示例来体验同构渲染的实现方式
新建项目目录,使用
yarn init -y
初始化,执行yarn add nuxt axios
安装Nuxt及axios为
package.json
添加scripts
选项"dev": "nuxt"
-
创建
pages
目录,并新建index.vue
与about.vue
文件,并添加相应的代码// index.vue <template> <div> <h1>home</h1> <ul> <li v-for="item in data" :key="item.id"> {{ item.title }} </li> </ul> </div> </template> <script> import axios from 'axios' export default { // Nuxt 中提供的特殊钩子函数,专门用于获取服务端渲染的数据 async asyncData () { // const { data } = await axios.get('http://jsonplaceholder.typicode.com/posts') const { data } = await axios.get('http://localhost:3000/data.json') // 这里返回的数据会和 data () {} 中的数据合并供页面使用 return { data } } } </script> <style> </style>
<template> <div> <h1>About</h1> </div> </template> <script> export default { } </script> <style> </style>
-
创建
layouts
目录,添加default.vue
文件,添加相应代码<template> <div> <ul> <li> <nuxt-link to="/">Home</nuxt-link> </li> <li> <nuxt-link to="/about">About</nuxt-link> </li> </ul> <!-- 子页面出口 --> <nuxt /> </div> </template> <script> export default { } </script> <style> </style>
创建
static
目录,放入静态资源文件data.json
(用于测试异步请求的数据)-
执行
yarn dev
运行,通过命令行提示,可以看到Client与Server端渲染的代码编译完成 -
在浏览器中访问
http://localhost:3000/
,并打开浏览器控制台查看页面请求返回的内容,可以看到首屏内容通过服务端渲染完成首屏直出 点击About链接切换路由,通过控制台可以看到,并没有向后台请求about页面,而是在客户端完成了页面切换渲染,即仍是一个SPA单页应用
同构渲染的问题
同构渲染虽然可以解决首屏加载慢与SEO的问题,但在使用上仍旧存在一定的局限性
开发条件有限
- 浏览器特定的代码只能在某些生命周期钩子函数中使用
- 一些外部扩展库可能需要特殊处理才能在服务端渲染应用中运行
- 不能在服务端渲染期间操作DOM
- 某些操作需要区分运行环境
- 等等
涉及构建和部署的要求更多
更多的服务端负载
- 在Node中渲染完整的应用程序,相比提供静态文件的服务器,需要占用大量的CPU资源
- 如果应用在高流量环境下使用,需要准备相应的服务器负载
- 需要更多的服务端渲染优化工作处理
服务端渲染使用建议
是否使用服务端渲染,应当考虑以下两个问题
- 首屏渲染速度是否真的重要?
- 是否真的需要SEO?