改造@vue/cli项目为服务端渲染-ServerSideRender
VUE SEO方案二 - SSR服务端渲染
在上一章中,我们分享了预渲染的方案来解决SEO问题,个人还是很中意此方案的,既简单又能解决大部分问题。但是也有着一定的缺陷,所以我们继续来看下一个方案--服务端渲染。
1.概述
服务端渲染的配置相比预渲染就复杂多了,要做到同构,还要保证服务端和客服端的组件状态一致,我们需要对整个项目进行改造。大部分的内容官方文档中都说明的比较清楚,这里就不重复讲述了,需要各位花费一些时间照着文档一步步改造项目。
本人一开始也是这样照着文档做的,但是改造到最后,发现文档一点不走心啊,本人用官方的@vue/cli创建的项目,一步步走到最后根本跑不起来,要么服务端各种报错,要么客服端各种渲染失败。
所以这边主要讲述在按照文档改造之后还要做的一些配置和其他问题。若有不实之处请大家指正。
2.简单原理
原理很简单,同一套代码,根据服务端和客户端的需求不同,分为两个入口,分别打包出两套逻辑一样的代码包。分别在服务器与客户端运行。
本例中,服务端使用node的express框架搭建。
3.开始配置
首先,需要保证服务端同样安装和开发环境同样的node_modules,因为服务端打包,并没有将所有的第三方包与组件打包到一起,也没有那个必要。在服务器端安装好就行了,服务端会自行调用,否则服务端会报找不到第三方插件的错误,如:
1Error: Cannot find module 'vuex'
服务端渲染的核心插件vue-server-renderer,安装:
1npm i -D vue-server-renderer
2npm i -g cross-env
全局安装cross-env使得我们可以在package.json中添加服务端入口打包命令
1"scripts": {
2 "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build"
3}
这样我们可以将参数WEBPACK_TARGET=node代入打包进程中,用以识别打包目标,也可以将客户端打包与此命令使用&&连接,一起执行。
打包配置大致如下
1// vue.config.js
2const path = require('path')
3const resolve = dir => path.join(__dirname, dir)
4const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
5const target = TARGET_NODE ? 'server' : 'client'
6const nodeExternals = require('webpack-node-externals')
7const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
8const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
9
10module.exports = {
11 // 将server-bundle.json与模板一起放在服务器,静态资源则打包到cdn中
12 outputDir: TARGET_NODE? resolve('dist') : resolve('../../www/statics/m/first-aid/'),
13 ...
14 chainWebpack: config => {
15 ...
16 // 两个入口
17 config.entry('app')
18 .clear()
19 .add(`./src/entry-${target}.js`)
20 config.target(TARGET_NODE ? 'node' : 'web')
21 config.output
22 .libraryTarget(TARGET_NODE ? 'commonjs2' : undefined)
23 if(TARGET_NODE){
24 config.externals(nodeExternals({
25 allowlist: [/\.css$/]
26 }))
27 config.optimization
28 .splitChunks(false)
29 config.module
30 .rule('vue')
31 .use('vue-loader')
32 .tap(options => {
33 options.optimizeSSR = false
34 return options
35 })
36 }
37 config.plugin('vue-server-renderer')
38 .use(TARGET_NODE ? VueSSRServerPlugin : VueSSRClientPlugin)
39 ...
40 },
41 css: {
42 sourceMap: true
43 }
44 ...
45}
其中与官方文档不同的几处:
- nodeExternals配置的whitelist属性是旧版的,新版要用allowlist
- 我们需要关闭vue-loader的optimizeSSR属性,否则你将遇到下面这样的错误,具体原因可查询 vue-loader文档 的相关部分

- 服务端打包的时候,你可能也会遇到这样的错误:
1(node:18696) UnhandledPromiseRejectionWarning: Error: Server-side bundle should have one single entry file.
2Avoid using CommonsChunkPlugin in the server config.
这时还需要关闭config.optimization.splitChunks,看来服务端渲染的时候对分包不是很友好,不过这也没有必要,所有的包都在服务器本地,当然不需要分包来优化下载。
- 关于vue-server-renderer/client-plugin插件还有一个bug, 查看gitHub issues,需要设置css: {sourceMap: true},否则服务端将会报以下错误导致不能渲染成功。

4. 服务器配置
1// server.js
2const express = require("express")
3const path = require("path")
4const resolve = dir => path.join(__dirname, dir)
5const fs = require("fs")
6const jsdom = require('jsdom')
7const { JSDOM } = jsdom
8
9const { createBundleRenderer } = require('vue-server-renderer')
10const serverBundle = require(resolve('dist/vue-ssr-server-bundle.json'))
11const clientManifest = require('../../www/statics/m/first-aid/vue-ssr-client-manifest.json')
12const renderer = createBundleRenderer(serverBundle, {
13 runInNewContext: false,
14 template: fs.readFileSync(resolve('template.html'), 'utf-8'),
15 clientManifest
16})
17
18const app = express()
19// 静态资源
20app.use(express.static(resolve('../../www')))
21
22app.use((req, res) => {
23 const context = { url: req.url }
24 const resourceLoader = new jsdom.ResourceLoader({
25 userAgent: req.headers['user-agent'],
26 });
27 const dom = new JSDOM('', {
28 url: req.protocol+'://'+req.hostname,
29 resources: resourceLoader
30 });
31 global.window = dom.window
32 global.document = window.document
33 global.navigator = window.navigator
34 window.nodeis = true
35 window.scrollTo = (x, y) => {
36 document.documentElement.scrollTop = y;
37 }
38 renderer.renderToString(context, (err, html) => {
39 if (err) {
40 console.log(err)
41 if (err.code === 404) res.status(404).end('Page not found')
42 else res.status(500).end('Internal Server Error')
43 } else res.end(html)
44 })
45})
46
47app.listen(80)
这边与官方文档差别不大,但是使用jsdom插件解决了不存在document全局变量的问题。当项目中用到像window这样的顶层对象时,服务器因为不存在此变量报错document is not defined错误,导致渲染失败
安装jsdom插件,使用请求来源客服端的UA创建虚拟DOM,从而模拟出顶级对象下的各属性与方法
5.最后
经过官方文档的改造,再通过这些重新配置后,一个服务端渲染的@vue/cli项目总算是运行正常了,再通过与vue-meta的配合,设置返回页面的title与description等,就达到我们解决SEO的目的。边角细节的配置就需要各位再慢慢摸索了。

改造@vue/cli项目为服务端渲染-ServerSideRender的更多相关文章
- 使用 Vue 2.0 实现服务端渲染的 HackerNews
Vue 2.0 支持服务端渲染 (SSR),并且是流式的,可以做组件级的缓存,这使得极速渲染成为可能.同时, 和 2.0 也都能够配合 SSR 提供同构路由和客户端 state hydration.v ...
- 基于vue-cli项目添加服务端渲染
两个示例的git地址: 1. 我的环境 2. 方式一:使用prerender-spa-plugin插件获得SSR的效果. 2.1 说明 2.2 初始化 1 vue init webpack vue-p ...
- 使用 PHP 来做 Vue.js 的 SSR 服务端渲染
对于客户端应用来说,服务端渲染是一个热门话题.然而不幸的是,这并不是一件容易的事,尤其是对于不用 Node.js 环境开发的人来说. 我发布了两个库让 PHP 从服务端渲染成为可能.spatie/se ...
- 实例PK(Vue服务端渲染 VS Vue浏览器端渲染)
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue服务端渲染和Vue浏览器端渲染的性能对比
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue服务端渲染 VS Vue浏览器端渲染)
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue.js 服务端渲染业务入门实践
作者:威威(沪江前端开发工程师) 本文原创,转载请注明作者及出处. 背景 最近, 产品同学一如往常笑嘻嘻的递来需求文档, 纵使内心万般拒绝, 身体倒是很诚实. 接过需求,好在需求不复杂, 简单构思 后 ...
- Egg + Vue 服务端渲染工程化实现
在实现 egg + vue 服务端渲染工程化实现之前,我们先来看看前面两篇关于Webpack构建和Egg的文章: 在 Webpack工程化解决方案easywebpack 文章中我们提到了基于 Vue ...
- Vue(服务端渲染)
一.前言 1.服务端渲染图解 2.简介服务端渲染 ...
随机推荐
- web服务器-nginx虚拟主机
web服务器-nginx虚拟主机 一 虚拟主机介绍 就是把一台物理服务器划分成多个虚拟的服务器, 每一个虚拟主机都可以有独立的域名和独立的目录,同时发布俩个网站. 二. 基于IP的虚拟主机 应用场景: ...
- CodeUp Problem D: More is better
根据题目意思,输入的每一对A.B都是直接朋友,并且最后只会得到一个集合,该集合就是Mr Wang选择的男孩. 因此很容易写出代码,甚至不需要自己构建一个并查集,只需要使用C++的set模板,每次读入一 ...
- 选择Key-Value Store
[IT168 专稿]在之前的文章中,给大家介绍了<Redis快速入门:Key-Value存储系统简介>,今天进一步给大家介绍为什么选择Key-Value Store.Key-Value S ...
- 有限差分法(Finite Difference Method)解方程:边界和内部结点的控制方程
FDM解常微分方程 问题描述 \[\frac{d^2\phi}{dx^2}=S_{\phi} \tag{1} \] 这是二阶常微分方程(second-order Ordinary Differenti ...
- C++ 并发编程2 --向线程函数传递参数
1向线程函数传递参数比较简单,一般的形式如下 void f(int i,std::string const& s);std::thread t(f,3, "hello"); ...
- 深入理解Java虚拟机-垃圾收集算法
一.判断对象是否可进行回收 1.引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用的.但是主流的 ...
- 解释一下Spring AOP里面的几个名词?
(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象.在Spring AOP中,切面可以使用通用类(基于模式的风格)或者在普通类中以@AspectJ注解来实现. (2)连接点(Join p ...
- 有哪些类型的通知(Advice)?
Before - 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置. After Returning - 这些类型的 Advice 在连接点方法 ...
- C++面试问题汇总
作者:勿忘心安~~链接:https://www.nowcoder.com/discuss/197611来源:牛客网 1 C++基础: (1)多态是怎么样的?写个样例? https://www.cnbl ...
- 时间工具类之"获取相差天数"
一.时间工具类DateUtils之"获取相差天数" 1 /** 2 * 相差天数 3 * 4 * <p>TODO 方法功能描述 5 * 6 * @param start ...