Vue SSR

SSR概念:

https://ssr.vuejs.org/zh/

server side render服务端渲染

服务端渲染解释:

将一个Vue组件在服务器渲染为HTML字符串并发送到浏览器,最后再将这些静态标记“激活”为可交互应用程序的过程称为服务端渲染。

百度蜘蛛爬虫的机制

百度蜘蛛是百度搜索引擎的一个自动化程序,它会不断的访问收集互联网上的网页、文章、视频等,通过抓取链接来收录网站,计算网站的权重和排名。纯html等静态化网站对百度蜘蛛比较友好,且百度蜘蛛几乎不会爬取js动态的网站,如vue/react构建的且经webpack/gulp等构建工具压缩处理过的网站。百度蜘蛛爬取网站是从主站开始爬,一次根据网站暴露的内链依次往深层次爬取。meta的设置,以及网站TDK的优化,网站结构优化,外链,文章原创等同样对SEO有很大作用,但本文主要是从技术层面入手,则主要是针对网站内链的处理以及基于vue等现在技术流做ssr处理。

传统服务端渲染

asp.net php jsp

浏览器拿到的是全部的dom结构。

优缺点:

  1. 前后端职责不清
  2. 前后端代码杂揉在一起
  3. 项目难以管理和维护

尽管如此,这种渲染方式还是有一些好处:

  1. 客户端能够快速呈现服务器端渲染好的页面,减少白屏时间,这能够提供很好的用户体验
  2. SEO 友好,服务端渲染从服务器发出的html带有页面内容,可提高搜索排名。

CSR

单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS渲染出来,这种方式称为客户端渲染(Client Side Rendering)。

客户端发起页面请求,服务端把页面(响应的字符串)发送给客户端,客户端从上到下依次解析,解析过程中,发现网络请求,再次向服务器发送网络请求,客服端拿到响应的结果,模板引擎渲染到HTML页面。

优点:灵活,真正的前后端分离,方便于前后台各自更新维护。

缺点: 对SEO不友好,增加了HTTP请求的次数,减缓了页面加载速度。

SSR

在后端看来,页面文件其实就是一个“字符串”,所以服务端完全可以在获取到HTML文件的内容之后经过一些处理再返回给客户端,也就说,服务端可以将数据插入到HTML 字符串中之后再返回给客户端。

优点:对 SEO搜索引擎友好,减少了HTTP请求的次数,加速了页面初次渲染的速度。

缺点:不灵活,前后端耦合太深。

在 SPA 模式下,所有的数据请求和 Dom 渲染都在浏览器端完成,所以当我们第一次访问页面的时候很可能会存在“白屏”等待,而服务端渲染所有数据请求和 html内容已在服务端处理完成,浏览器收到的是完整的 html 内容,可以更快的看到渲染内容,在服务端完成数据请求肯定是要比v在浏览器端效率要高的多。

服务端渲染和客户端渲染结合

最好的方案就是服务端渲染和客户端渲染的结合,第一次访问页面是服务端渲染,基于第一次访问后续的交互就是客户端渲染的效果和体验,同时还不影响SEO效果

实现Vue SSR具体过程

创建工程

vue-cli创建工程即可

安装依赖

vue-server-renderer  express

要确保vue、vue-server-renderer版本一致

启动脚本

创建一个express服务器,将vue ssr集成进来。

./server/index.js

/*
* SSR
* 思考:vue在nodejs中如何展示?
*/const express = require('express')
const vue = require('vue')const app = express()
const page = new vue({data: { title: '沐沐沐 --- 服务端渲染' },template: `<div><h1>{{title}}</h1><div>hello moira !</div></div>`
})// 将vue实例渲染成html
// 使用vue官方渲染器 vue-server-renderer
// createRenderer()工厂函数,返回一个渲染器实例
// 渲染器实例作用:把vue实例作为参数传给它,可以将vue当前页面的内容生成html
const renderer = require('vue-server-renderer').createRenderer()
// 页面可能写得有问题,在render的过程中可能会发生异常
// 考虑健壮性,使用try catch
app.get('/', async (req, res) => {try {// renderToString 异步生成一个html,参数是一个vue实例。 // 异步方法,返回一个Promiseconst html = await renderer.renderToString(page)res.send(html)}catch (error) {res.status(500).send('服务器内部错误!')}})app.listen(5200, () => {console.log('服务器启动成功!')
})

路由

路由支持仍然使用vue-router

创建路由实例

每次请求的url委托给vue-router处理

./server/example_simple_ssr.js

// 创建一个express实例
const express = require('express')const app = express()// 导入vue
const Vue = require('vue')// 创建渲染器
const { createRenderer } = require('vue-server-renderer')const renderer = createRenderer()// 导入路由
const Router = require('vue-router')
Vue.use(Router)app.get('*', async (req, res) => {// 创建一个路由器实例const router = new Router({routes: [{ path: '/', component: { template: '<div>Index</div>' } },{ path: '/detail', component: { template: '<div>detail</div>' } },]})// 构建渲染页面内容const vm = new Vue({router,data() {return {name: '沐沐'}},template: `<div><router-link to="/">index</router-link><router-link to="/detail">detail</router-link><div>{{name}}</div><router-view></router-view></div>`})try {// 路由跳转router.push(req.url)// 渲染: 得到html字符串const html = await renderer.renderToString(vm)// 发送回前端res.send(html)} catch (error) {res.status(500).send('服务器内部错误')}})// 监听端口
app.listen(5200, () => {console.log('服务器启动成功!')
})
问题
  • 同构开发

同构开发SSR应用

对于同构开发,我们依然使用webpack打包,我们要解决两个问题:

服务端首屏渲染和客户端激活。

目标是生成一个「服务器 bundle」用于服务端首屏渲染,和一个「客户端bundle」用于客户端激活。

server bundle:
处理前端请求渲染哪个页面,把首屏渲染成html

client bundle:
打包页面相关代码,作用:将静态html激活成前端spa

代码结构

除了两个不同入口之外,其他结构和之前vue应用完全相同。

app.js 通用入口,用于创建vue实例

entry-client.js # 客户端入口,用于静态内容“激活”

entry-server.js # 服务端入口,用于首屏内容渲染

app.js

// 服务端入口
// 核心作用:创建vue实例
import Vue from "vue";
// 根页面
import App from "./App.vue"
// router工厂函数
import createRouter from "./router";// 每一次用户请求都要全新Vue实例
export default function createApp() {const router = createRouter()const app = new Vue({router,// 渲染函数// 问题: 是否需要 .$mount()挂载 render: h => h(App)});// 要返回app和router实例 router实例之后有用return { app, router }
}

entry-client.js

// 客户端入口只需创建vue实例并执行挂载,这一步称为激活。
import createApp from "./app";const { app, router } = createApp()
// 准备就绪后挂载
router.onReady(() => {// 挂载app.$mount('#app')
})

entry-server.js

// 服务端入口
// 核心作用:渲染首屏
// 处理前端请求渲染哪个页面,把首屏渲染成html// 得到具体vue实例
import createApp from "./app";// 导出
// 还是返回一个工厂函数 将来会被服务器调用 因为只有服务器知道用户请求的是什么 在这里处理首屏并且返回对应实例
export default context => {// 这里返回一个Promise,确保路由或组件准备就绪return new Promise((resolve, reject) => {const { app, router } = createApp();// 跳转到首屏的地址router.push(context.url)// 路由就绪,返回结果router.onReady(()=>{// 返回当前实例 appresolve(app);}, reject)})
}

webpack 打包

安装依赖

npm install webpack-node-externals lodash.merge -D

具体配置 vue.config.js

// webpack 两个插件分别负责打包客户端和服务端
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
// 两个依赖
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");// *** 根据传入环境变量决定入口文件和相应配置项
// 环境变量 WEBPACK_TARGET :决定入口是客户端还是服务端
// node ? server : client
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";module.exports = {css: {extract: false},outputDir: './dist/' + target,configureWebpack: () => ({// *** 将 entry 指向应用程序的 server / client 文件entry: `./src/entry-${target}.js`,// 对 bundle renderer 提供 source map 支持devtool: 'source-map',// *** target设置为node使webpack以Node适用的方式处理动态导入,// 并且还会在编译Vue组件时告知`vue-loader`输出面向服务器代码。target: TARGET_NODE ? "node" : "web",// 是否模拟node全局变量node: TARGET_NODE ? undefined : false,output: {// 此处使用Node风格导出模块libraryTarget: TARGET_NODE ? "commonjs2" : undefined},// https://webpack.js.org/configuration/externals/#function// https://github.com/liady/webpack-node-externals// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的打包文件。// 优化代码// externals: TARGET_NODE//     ? nodeExternals({//         // 不要外置化webpack需要处理的依赖模块。//         // 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,//         // 还应该将修改`global`(例如polyfill)的依赖模块列入白名单//         whitelist: [/\.css$/]//     })//     : undefined,// optimization: {//     splitChunks: undefined// },// *** 这是将服务器的整个输出构建为单个 JSON 文件的插件。// 服务端默认文件名为 `vue-ssr-server-bundle.json`// 客户端默认文件名为 `vue-ssr-client-manifest.json`。plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]}),// 优化代码 不太重要chainWebpack: config => {// cli4项目添加if (TARGET_NODE) {config.optimization.delete('splitChunks')}config.module.rule("vue").use("vue-loader").tap(options => {merge(options, {optimizeSSR: false});});}
};

打包脚本

"build": "npm run build:server & npm run build:client",
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"

宿主文件

./public/index.template.html

<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width,initial-scale=1.0">    <title>vue ssr moira</title>  </head>  <body>    <!--vue-ssr-outlet-->  </body></html>

服务器启动文件

修改服务器启动文件,现在需要处理所有路由,./server/index_new.js

const express = require('express')const Vue = require('vue')const fs = require('fs') //文件系统const path = require('path')// 创建express实例和vue实例const app = express()// const renderer = require('vue-server-renderer').createRenderer()const { createBundleRenderer } = require('vue-server-renderer')// 服务端的包const serverBundle = require('../dist/server/vue-ssr-server-bundle.json')// 客户端清单const clientManifest = require('../dist/client/vue-ssr-client-manifest.json')// 参数1:服务端的包// 将来生成的页面要把清单解析出来附加到页面中// 最后生成的html页面会有一些js需要附加,客户端清单告诉了js附加到哪里const renderer = createBundleRenderer(serverBundle, {    // 上下文    runInNewContext: false,    // 这里需要用path转换为绝对地址    template: fs.readFileSync(path.join(__dirname, '../public/index.template.html'), 'utf-8'), //宿主模板文件,使用文件系统的方式导入,使用fs     clientManifest})// 中间件处理静态文件请求// 需要把dist client js暴露给客户端,前端是可以访问的// express.static()  静态文件私服app.use(express.static('../dist/client', { index: false }))// 路由请求// * 将来前端请求什么我都不关心,所有路由都让renderer接管// 路由的权限由vue接管// 路由的处理交给vueapp.get('*', async (req, res) => {    try {        // 创建上下文        const context = {            url: req.url,            title: 'ssr moira title'        }        // renderer此时是BundleRenderer,接收上下文,而不是vue实例 约定        const html = await renderer.renderToString(context);        res.send(html)    } catch (error) {        // 返回一个状态码,500,服务器内部错误        res.status(500).send('服务器内部错误')    }});app.listen(5200, () => {    console.log('渲染服务器启动成功!')})

打包后的server端:

json文件,能够告诉渲染器必要的渲染信息。打包信息。

打包后的client端:

完整。index.html 宿主文件。js文件 动态从前端加载。

client目录需要成为服务器的静态目录。

vue-ssr-client-manifest.json 前端挂载

defer 不能阻塞程序 延迟加载

SSR优缺点

优点

与传统 SPA (单页应用程序 (Single-Page Application)) 相比,服务器端渲染 (SSR) 的优势主要在于:

  • 更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

    请注意,截至目前,Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里,同步是关键。如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。

  • 更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。通常可以产生更好的用户体验,并且对于那些「内容到达时间(time-to-content) 与转化率直接相关」的应用程序而言,服务器端渲染 (SSR) 至关重要。

缺点

使用服务器端渲染 (SSR) 时还需要有一些权衡之处:

  • 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用;一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。
  • 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
  • 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 (high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

在对你的应用程序使用服务器端渲染 (SSR) 之前,你应该问的第一个问题是,是否真的需要它。这主要取决于内容到达时间 (time-to-content) 对应用程序的重要程度。例如,如果你正在构建一个内部仪表盘,初始加载时的额外几百毫秒并不重要,这种情况下去使用服务器端渲染 (SSR) 将是一个小题大作之举。然而,内容到达时间 (time-to-content) 要求是绝对关键的指标,在这种情况下,服务器端渲染 (SSR) 可以帮助你实现最佳的初始加载性能。

整合Vuex

数据预取{ 组件中的数据预取. 服务端数据预取. 客户端数据预取处理}

优化:

  1. 页面缓存
  2. pm2守护node进程配置,用它来做自动重启、性能监控以及负载均衡。

Vue + vue-router + express SSR实践相关推荐

  1. vue ssr 实践

    vue ssr 实践 技术栈 初始化项目并安装相关依赖 编写webpack相关配置 编写客户端,服务端通用代码 组件异步获取数据 编写客户端入口代码 编写服务端入口代码 后台代码 总结 技术栈 后台使 ...

  2. vue服务端渲染ssr

    vue服务端渲染ssr 一.SSR概念 传统web渲染技术 SPA SSR 二.webpack+vue2的实现方式 1.创建工程 2.安装依赖 3.编写一个简单的SSR 4.完整的ssr 5.代码链接 ...

  3. 15分钟学会vue项目改造成SSR

    15分钟学会vue项目改造成SSR Ps:网上看了好多服务器渲染的例子,基本都是从0开始的,用Nuxt或者vue官网推荐的ssr方案(vue-server-renderer),但是我们在开发过程中基本 ...

  4. 四 Vue学习 router学习

    index.js: 按需加载组件: const login = r => require.ensure([], () => r(require('@/page/login')), 'log ...

  5. Vue.js组件化开发实践

    Vue.js组件化开发实践 前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了一下的内容.首先会对使用Vue进行开发的一 ...

  6. 四十三、在Vue使用router,路由的管理

    @Author:Runsen @Date:2020/7/17 管理路由是一项必不可少的功能.今天,Runsen学习Vue Router. 文章目录 安装Vue Router Vue Router使用 ...

  7. Vue 路由router的两种模式

    两种模式 vue中router可以设两种模式:hash和history.设置方式就是代码中注释的部分. import Vue from 'vue' import Router from 'vue-ro ...

  8. vue require css html,requirejs vue vue.router简单框架

    index.html 入口页面html> vue `menu`.`name` base.js requirejs 配置文件(function(){    requirejs.config({   ...

  9. [vue] 在移动端使用vue,你觉得最佳实践有哪些?

    [vue] 在移动端使用vue,你觉得最佳实践有哪些? vant,mint,uniapp 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主目录 与歌 ...

最新文章

  1. 剑指 offer set 22 数组中的逆序数
  2. 人工智能名人堂第54期 | 深度学习鼻祖:Geoffrey Hinton
  3. epub阅读器_推荐一款Epub(windows版)阅读软件,附下载安装教程
  4. 后端python基础
  5. 阿里云前端周刊 - 第 14 期
  6. u-boot新增命令后出现data abort
  7. UIAutomator输入中文
  8. java窗口小程序atm_写一个ATM机小程序(JAVA), 目前老师 讲到了 静态工厂
  9. Java程序如何获得自己的进程ID?
  10. python 返回函数
  11. 下载 Chrome插件 crx的教程
  12. 真正会沟通的项目经理,不会告诉你的4件事
  13. arduino通过串口监视器读取一行字符
  14. 学生请假管理系统(需求说明+项目(部分代码))
  15. 信捷pLC C语言错误格式,信捷PLC常见问题及解决方法经验分享
  16. 麒麟开源堡垒机银行行业设计方案
  17. [COPY]《京东技术解密》——海量订单处理
  18. shiro的基本认识
  19. expected scalar type Long but found Int
  20. docker出现Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use的解决方法

热门文章

  1. android凯立德,定制安卓,正版凯立德导航_凯立德行车一体机_GPS评测-中关村在线...
  2. PHP利用CURL_MULTI实现多线程爆破
  3. 基于图像的人数统计方法
  4. 送客户的祝福语_送给客户的事业祝福语大全(含贺词、寄语等)
  5. velocity制作pdf
  6. Mastering Microsoft Teams 免积分下载
  7. 文件夹内相同类型文件批量重命名
  8. TortoiseGit三板斧维护Gitee
  9. 因为计算机中丢失msvcp100 dll,计算机中丢失MSVCP100.dll怎么解决在线等  爱问知识人...
  10. hd disk / disk raid / disk io / iops / iostat / iowait / iotop / iometer