热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

改造@vue/cli项目为服务端渲染ServerSideRender

VUESEO方案二-SSR服务端渲染在上一章中,我们分享了预渲染的方案来解决SEO问题,个人还是很中意此方案的,既简单又能解决大部分问题。但是也有着一定的缺陷,所以我们继续来看下一

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        sourceMaptrue
43    }
44    ...
45}

其中与官方文档不同的几处:



  1. nodeExternals配置的whitelist属性是旧版的,新版要用allowlist

  2. 我们需要关闭vue-loader的optimizeSSR属性,否则你将遇到下面这样的错误,具体原因可查询 vue-loader文档 的相关部分



  3. 服务端打包的时候,你可能也会遇到这样的错误:

1(node:18696) UnhandledPromiseRejectionWarning: Error: Server-side bundle should have one single entry file.
2Avoid using CommonsChunkPlugin in the server config.

这时还需要关闭config.optimization.splitChunks,看来服务端渲染的时候对分包不是很友好,不过这也没有必要,所有的包都在服务器本地,当然不需要分包来优化下载。



  1. 关于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    runInNewContextfalse,
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的目的。边角细节的配置就需要各位再慢慢摸索了。






推荐阅读
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • Windows服务与数据库交互问题解析
    本文探讨了在Windows 10(64位)环境下开发的Windows服务,旨在定期向本地MS SQL Server (v.11)插入记录。尽管服务已成功安装并运行,但记录并未正确插入。我们将详细分析可能的原因及解决方案。 ... [详细]
  • PyCharm下载与安装指南
    本文详细介绍如何从官方渠道下载并安装PyCharm集成开发环境(IDE),涵盖Windows、macOS和Linux系统,同时提供详细的安装步骤及配置建议。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • MySQL 用户创建失败的解决方案
    本文详细介绍了在 MySQL 中遇到用户创建失败问题时的解决方法,包括如何正确配置环境、执行命令以及常见错误排查技巧。通过逐步指导,帮助用户顺利添加和管理 MySQL 用户。 ... [详细]
  • 本文介绍了如何利用npm脚本和concurrently工具,实现本地开发环境中多个监听服务的同时启动,包括HTTP服务、自动刷新、Sass和ES6支持。 ... [详细]
  • 本文详细介绍如何在VSCode中配置自定义代码片段,使其具备与IDEA相似的代码生成快捷键功能。通过具体的Java和HTML代码片段示例,展示配置步骤及效果。 ... [详细]
  • 深入解析 Vue 的 Transition 组件与第三方动画库的结合使用
    本文详细介绍了 Vue 中的 Transition 组件,探讨其内置类名机制、触发时机及自定义类名的应用。同时,结合 animate.css 和 GSAP 等第三方库,展示了如何实现复杂的动画效果。 ... [详细]
  • 本文介绍如何通过 JavaScript 实现一个基于鼠标坐标的 Tooltip 弹出层,详细解释了如何获取窗口和文档的尺寸及滚动位置,并优化了代码结构。 ... [详细]
  • 本文详细探讨了如何在Docker环境中实现单机部署Redis集群的方法,提供了详细的步骤和配置示例,帮助读者更好地理解和应用这一技术。 ... [详细]
author-avatar
手机用户2502926901
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有