服务端渲染(SSR)
SSR 意为 server-side rendering(服务端渲染), 目的是为了解决单页面应用的 SEO 的问题.
服务器端渲染 (SSR) 和客户端渲染(CSR)
服务器端渲染(SSR)
浏览器先请求 html 文档, 服务器端先将 HTML 页面(或页面组件), 生成为 HTML 字符串, 再返回给浏览器, 最后直接渲染到页面上.
客户端渲染(CSR)
浏览器先请求 HTML 文档, 在浏览器端加载 HTML 页面中的JS 脚本. 通过 JS(vue/react)的能力, 将虚拟 DOM 最终渲染填充到页面中.
两者本质的区别是什么?
SSR
服务端生成好的 HTML 页面内容, 直接返回给浏览器渲染.
- server
- const express = require('express')
- const App = express()
- App.get('/ssr', (req, res) => {
- res.send(`
- <HTML>
- <head>
- <meta charset='utf-8'>
- <title>SSR 服务端渲染</title>
- </head>
- <body>
- <h3>SSR 服务端渲染</h3>
- <p>SSR 意为 Server Side Rendering(服务端渲染), 目的是为了解决单页面应用的 SEO 的问题.</p>
- </body>
- </HTML>
- `)
- })
- App.listen(7200)
客户端访问页面 localhost:7200/ssr
- <HTML>
- <head>
- <meta charset='utf-8'>
- <title>SSR 服务端渲染</title>
- </head>
- <body>
- <h3>SSR 服务端渲染</h3>
- <p>SSR 意为 Server Side Rendering(服务端渲染), 目的是为了解决单页面应用的 SEO 的问题.</p>
- </body>
- </HTML>
CSR
页面首先直接输出一个空的 div#root, 再由客户端加载编译打包好的 react 代码(bundle.JS chunk.JS 等JS 脚本), 最终将页面组件渲染到页面中.
- npx create-react-App my-App
- cd my-App
- yarn start
浏览器访问 http://localhost:3000/
- <body>
- <noscript>You need to enable JavaScript to run this App.</noscript>
- <div id="root"></div>
- <script src="/static/js/bundle.js"></script><script src="/static/js/0.chunk.js"></script><script src="/static/js/main.chunk.js"></script></body>
客户端渲染和服务器端渲染的最重要的区别就是究竟是谁来完成 HTML 文件的完整拼接, 如果是在服务器端完成的, 然后返回给客户端, 就是服务器端渲染, 而如果是前端做了更多的工作完成了 HTML 的拼接, 则就是客户端渲染.
前后端拆分核心理念
前后端拆分, 后端专注于数据接口服务, 前端专注接口调用, 页面渲染, 双剑合璧, 相得益彰.
服务器端渲染的优缺点是怎样的?
优点:
更好的 SEO 首屏加载快 由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面.
首屏加载快 快速地看到完整渲染的页面, 从而提高用户体验.
后端生成静态化文件 即解析模板的工作完全交由后端来做, 客户端只要解析标准的 HTML 页面即可.
缺点:
开发条件受限 在服务端渲染中, created 和 beforeCreate 之外的生命周期钩子不可用
占用服务端资源
学习成本相对较高 除了对 webpack,Vue 要熟悉, 还需要掌握 node,Express 相关技术. 相对于客户端渲染, 项目构建, 部署过程更加复杂.
客户端渲染的优缺点是怎样的?
优点:
前后端分离 前端专注于前端 UI, 后端专注于 API 开发, 且前端有更多的选择性, 而不需要遵循后端特定的模板.
体验更好
缺点:
首屏加载缓慢
不利于 SEO 除了 Google 和 Bing 比较完美地实现了对于 SPA(Single-Page Application)的爬虫渲染及内容抓取, 大多数搜索引擎包括百度都没有支持. 因而, 包含丰富内容的产品并需要 SEO 流量的产品也就自然需要 SSR 实现.
是否应该使用服务端渲染
首屏加载慢 针对于首屏加载, 可以做服务端渲染. 但要有觉悟, 一旦这样做, 后期维护是个很痛苦的事情. 相比于做服务端渲染, 更推荐通过应用拆分, code spliting 来完成优化首屏加载的过程(先前做过一次首屏优化, 优化前首屏加载每次都在 5s+,code spliting 之后直接变成 2s+, 性价比高).
SEO 优化 如果是为了主页网站被搜索引擎收录, 可以使用服务端渲染. 但更好的建议新开引导项目, 在该项目上静态资源或服务端渲染显示页面, 作为主要网站的搜索引擎引流作用.
Vue 服务端渲染
这里我们先从 Vue 的 vue-server-renderer 来聊聊服务端渲染,暂先不说那些 ssr 框架(Nuxt.JS Next.JS) vue-server-renderer 是官方提供给我们用来实现服务端渲染的 NPM 包
基本用法
安装
NPM install vue vue-server-renderer --save
渲染一个 Vue 实例
1. 创建一个 Vue 实例
- const Vue = require('vue')
- const App = new Vue({
- template: `<div>hello zhufeng</div>`
- })
2. 创建一个 renderer 对象
const renderer = require('vue-server-renderer').createRenderer()
3. 将 Vue 实例渲染为 HTML
- renderer.renderToString(App, (err, HTML) => {
- if (err) throw err
- console.log(HTML)
- // <div data-server-rendered="true">hello zhufeng</div>
- })
- // 在 2.5.0+, 如果没有传入回调函数, 则会返回 Promise:
- renderer.renderToString(App).then(HTML => {
- console.log(HTML)
- }).catch(err => {
- console.error(err)
- })
Vue 实例渲染 完整示例代码
Node.JS 服务器作为中间层
NPM i express --save
- const Vue = require('vue')
- const server = require('express')()
- // 创建一个 renderer 对象
- const renderer = require('vue-server-renderer').createRenderer()
- // 创建一个后端路由
- server.get('/', (req, res) => {
- // 创建一个 Vue 实例
- const App = new Vue({
- data: {
- title: 'hello zhufeng'
- },
- template: `<h3>{{ title }}</h3>`
- })
- // 通过 renderToString 方法 将 Vue 实例转换成 HTML
- renderer
- .renderToString(App)
- .then(HTML => {
- console.log(HTML)
- // '<h3 data-server-rendered="true">hello zhufeng</h3>'
- // 最终将拼接好的 HTML 页面内容 返回给浏览器
- res.send(`
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <title>
- Hello
- </title>
- </head>
- <body>
- ${HTML}
- </body>
- </HTML>
- `)
- })
- .catch(err => {
- res.status(500).end('Internal Server Error')
- })
- })
- server.listen(7300)
使用 HTML 页面模板
创建一个 HTML 模板页面, 用一个额外的 HTML 页面包裹容器, 来包裹生成的 HTML 标记(markup).
HTML 模板
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <title>
- 服务端渲染 SSR
- </title>
- </head>
- <body>
- <!--vue-ssr-outlet-->
- </body>
- </HTML>
注意 <!--vue-ssr-outlet--> 注释 -- 这里将是应用程序 HTML 标记注入的地方.
server 端
- const Vue = require('vue')
- const fs = require('fs')
- const server = require('express')()
- const { createRenderer } = require('vue-server-renderer')
- // 创建一个 renderer 对象 并指定渲染模板
- const renderer = createRenderer({
- template: fs.readFileSync('./template/index1.html', 'utf-8')
- })
- // 创建一个后端路由
- server.get('/', (req, res) => {
- // 创建一个 Vue 实例
- const App = new Vue({
- data: {
- title: 'hello zhufeng'
- },
- template: `<h3>{{ title }}</h3>`
- })
- // 通过 renderToString 方法 将 Vue 实例转换成 HTML
- renderer
- .renderToString(App)
- .then(HTML => {
- // 最终将拼接好的 HTML 页面内容 返回给浏览器
- res.send(HTML)
- })
- .catch(err => {
- res.status(500).end('Internal Server Error')
- })
- })
- server.listen(7300)
动态注入 title 和 meta 标签
HTML 模板设置插值变量
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- {{{meta}}}
- <title>
- {{title}}
- </title>
- </head>
- <body>
- <!--vue-ssr-outlet-->
- </body>
- </HTML>
我们可以通过传入一个 "渲染上下文对象", 作为 renderToString 函数的第二个参数, 来提供插值数据:
- // 动态注入 title 和 meta 标签
- const context = {
- title: '珠峰前端培训',
- meta: `
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
- <meta name="keywords" content="HTML, CSS, Vue, React, Node, JavaScript" />
- `
- }
- // 通过 renderToString 方法 将 Vue 实例转换成 HTML
- renderer
- .renderToString(App, context)
- .then(HTML => {
- // 最终将拼接好的 HTML 页面内容 返回给浏览器
- res.send(HTML)
- })
- .catch(err => {
- res.status(500).end('Internal Server Error')
- })
参考源码
https://github.com/Lwenli1224/ssr_csr
编写通用代码
"通用" 代码 - 即运行在服务器和客户端的代码. 由于用例和平台 API 的差异, 当运行在不同环境中时, 我们的代码将不会完全相同. 所以这里我们将会阐述你需要理解的关键事项.
SSR 开发需要注意的问题
服务端渲染只会执行 vue 的两个钩子函数 beforeCreate 和 created
服务端渲染无法访问 Windows 和 document 等只有浏览器才有的全局对象.
通用 API 例如, axios 是一个 HTTP 客户端, 可以向服务器和客户端都暴露相同的 API.
webpack 工程构建
服务端和客户端各自都需要提供 Vue 应用程序. 为了做到这一点, 我们需要使用 webpack 来打包我们的 Vue 应用程序. 事实上, 我们可能需要在服务器上使用 webpack 打包 Vue 应用程序, 因为:
通常 Vue 应用程序是由 webpack 和 vue-loader 构建, 并且许多 webpack 特定功能不能直接在 Node.JS 中运行(例如通过 file-loader 导入文件, 通过 CSS-loader 导入 CSS).
尽管 Node.JS 最新版本能够完全支持 ES2015 特性, 我们还是需要转译客户端代码以适应老版浏览器. 这也会涉及到构建步骤
然后我们的服务端代码和客户端代码通过 webpack 分别打包, 生成 Server Bundle 和 Client Bundle
服务器需要「服务器 bundle」然后用于服务器端渲染(SSR)
客户端 bundle」会发送给浏览器, 用于混合静态标记.
从零搭建 Vue 开发环境(SSR)
利用 vue-server-renderer 搭建 Vue SSR 开发环境
Git 仓库源码
比较费劲
服务端渲染应用框架 - Nuxt.JS
Nuxt.JS 是一个基于 vue.js 的服务端渲染应用框架. 你可以基于它初始化新项目的基础结构代码, 或者在已有 Node.JS 项目中使用 Nuxt.JS. Nuxt.JS 预设了利用 Vue.JS 开发服务端渲染的应用所需要的各种配置.
create-nuxt-App
Nuxt.JS 团队创建的脚手架工具
创建一个 nuxt 工程
npx create-nuxt-App nuxt-App
它会让你进行一些集成选择, 如服务器端框架 (express koa) 和 UI 框架.
启动项目
NPM run dev
现在我们的应用运行在 http://localhost:3000 上运行.
注意: Nuxt.JS 会监听 pages 目录中的文件更改, 因此在添加新页面时无需重新启动应用程序.
目录结构
├── README.md # 说明文档
├── assets # 资源目录 用于组织未编译的静态资源如 Less,Sass 或 JavaScript
├── components # 组件目录 用于组织应用的 Vue.JS 组件
├── layouts # 布局目录 用于组织应用的布局组件
├── middleware # 中间件目录 用于存放应用的中间件.
├── nuxt.config.JS # nuxt 配置文件 用于组织 Nuxt.JS 应用的个性化配置, 以便覆盖默认配置.
├── pages # 页面目录 用于组织应用的路由及视图
├── plugins # 插件目录 用于组织那些需要 在根 vue.JS 应用实例化之前需要运行的 JavaScript 插件
├── server # 服务端 用于组织 node 中间层服务代码
├── static # 静态文件目录 用于存放应用的静态文件
├── store # store 目录 用于组织应用的 Vuex 状态树 文件.
目录结构详情说明 https://zh.nuxtjs.org/guide/directory-structure
异步数据
asyncData
asyncData 方法会在组件 (限于页面组件) 每次加载之前被调用. 它可以在服务端或路由更新之前被调用. 你可以利用 asyncData 方法来获取数据, Nuxt.JS 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件.
fetch
fetch 方法用于在渲染页面前填充应用的状态树 (store) 数据, 与 asyncData 方法类似, 不同的是它不会设置组件的数据.
Nuxt.JS 开发实战(打造 CNode 社区)
路由创建
Nuxt.JS 依据 pages 目录结构自动生成 vue-router 模块的路由配置.
Vuex 配置
Nuxt.JS 会尝试找到应用根目录下的 store 目录, 如果该目录存在, 它将做以下的事情:
引用 vuex 模块
将 vuex 模块加到 vendors 构建配置中去
设置 Vue 根实例的 store 配置项
middleware 中间件配置
每一个中间件应放置在 middleware/ 目录. 文件名的名称将成为中间件名称(middleware/auth.JS 将成为 auth 中间件).
node 服务配置
nuxt.config.JS
Nuxt.JS 默认的配置涵盖了大部分使用情形, 可通过 nuxt.config.JS 来覆盖默认的配置.
Git 项目源码(CNode)
https://github.com/Lwenli1224/Nuxt.js-CNode
未完待续 持续更新...
来源: https://juejin.im/post/5c45f13f6fb9a049e12a86fb