1.SSR是什么?
SSR,意为 Server Side Rendering(服务端渲染),目的是为了解决单页面应用的 SEO 和首屏渲染速度慢的问题,对于一般网站影响不大,但是对于需要做SEO的网站是致命的,搜索引擎无法抓取页面相关内容,也就是用户搜不到此网站的相关信息。
2.为什么要用SSR?
客户端渲染需要:加载html=>解析html=>加载js=>解析js=>生成dom节点=>插入html文档,所以很慢; 服务端渲染就是将首屏的html结构构建好直接返回,客户端只需要:加载html=>解析html。所以首屏打开速度大大提高,但同时对服务器的压力也比客户端渲染要大。
3.关于实现SSR的几种方式
目前Vue SSR的实现有两种实现,一种是基于官方Vue SSR指南文档的官方方案,一种是vue.js通用应用框架--NUXT。 官方方案具有更直接的控制应用程序的结构,更深入底层,更加灵活,同时在使用官方方案的过程中,也会对Vue SSR有更加深入的了解。 而NUXT提供了平滑的开箱即用的体验,它建立在同等的Vue技术栈之上,但抽象出很多模板,并提供了一些额外的功能,例如静态站点生成。通过NUXT可以根据约定的规则,快速的实现Vue SSR。
(1)nuxt
官方是这么介绍自己的: Nuxt.js 是一个基于 Vue 的通用应用框架。 通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。 Nuxt.js 预设了利用 Vue 开发服务端渲染的应用所需要的各种配置。
除此之外,我们还提供了一种命令叫:nuxt generate,为基于 Vue 的应用提供生成对应的静态站点的功能。
我们相信这个命令所提供的功能,是向开发集成各种微服务(miscroservices)的 Web 应用迈开的新一步。 作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。
Nuxt.js是使用 Webpack 和 Node.js 进行封装的基于Vue的SSR框架,使用它,你可以不需要自己搭建一套 SSR 程序,而是通过其约定好的文件结构和API就可以实现一个首屏渲染的 Web 应用。
之所以叫 Nuxt.js 也是因为受到了 Next.js 的启发。 Nuxt.js 是一个 Node 程序,就像上面说的,我们是要把 Vue 跑在服务端,所以必须使用 Node 环境。
我们对 Nuxt.js 应用的访问,实际上是在访问这个 Node.js 程序的路由,程序输出首屏渲染内容 + 用以重新渲染的 SPA 的脚本代码,而路由是由 Nuxt.js 约定好的 pages 文件夹生成的。
所以,整体上,Nuxt.js 通过各个文件夹和配置文件的约束来管理我们的程序,而又不失扩展性,其有自己的插件机制。
- .nuxt : Nuxt自动生成,临时的用于编辑的文件,build
- assets:Webpack 编译的各类资源,// 用于组织未编译的静态资源入LESS、SASS 或 JavaScript
- components:各组件,用于你自己管理公共组件或非公共组件 ,比如滚动组件,日历组件,分页组件
- layouts:宿主布局页面模板组件,用于你可以把不同的页面指定使用不同的布局,不可更改。
- middleware:中间件,首屏渲染和路由跳转前均执行对应中间件,可以返回promise或直接next(很实用!)
- pages:各页面组件,用于生成对应路由,支持嵌套,支持动态路由,存放写的页面,我们主要的工作区域
- plugins:插件,SPA中用的各类第三方组件和一些node模块,JavaScript插件放的地方
- static :/ 用于存放静态资源文件,比如图片
- store:内置了vuex,可以直接返回数据模块或返回一个自建vuex根对象,具体要翻文档,用于组织应用的Vuex 状态管理
- .editorconfig : // 开发工具格式配置
- .eslintrc.js : // ESLint的配置文件,用于检查代码格式
- .gitignore : // 配置git不上传的文件
- nuxt.config.json : // 用于组织Nuxt.js应用的个性化配置,已覆盖默认配置
- package-lock.json : // npm自动生成,用于帮助package的统一性设置的,yarn也有相同的操作
- package-lock.json : // npm自动生成,用于帮助package的统一性设置的,yarn也有相同的操作
- package.json : // npm包管理配置文件
- 其他:你可以自定义文件夹和别名映射,文档都有提及,这里有配置代码
nuxt.config.js对程序的扩展管理可大概分为以下类:
- build:主要对应 Webpack 中的各配置项,可以对默认的 Webpack 配置进行扩展,如这里代码
- cache:主要对应内置的组件缓存模块lru-cache的配置对象,有默认值,可选关闭
- css:对应我们在SPA随处引用样式文件的require语句
- dev:用于自定义配置环境变量,对应之前webpack.config.js相关文件中的变量语句
- env:同上息息相关
- generate:对generate命令执行时的行为做一些定制
- head:对应vue-meta插件的全局配置,vue-meta用于VUE/SSR程序的文档元信息的管理
- loading:用于定制化Nuxt.js内置的进度条组件
- performance:用于配置Node.js服务器性能上的配置
- plugins:用于管理和应用对应plugins文件夹中的插件
- rootdir:用于设置 Nuxt.js 应用的根目录(这俩api有很大合并的意义)
- srcdir:用于设置 Nuxt.js 应用的源码目录(这俩api有很大合并的意义)
- router:用于对vue-router的扩展和定制,其中还包括了中间件的配置,但并不完美
- transition:用于定制Nuxt.js内置的页面切换过渡效果的默认属性值
- watchers:用于定制Nuxt.js内置的文件监听模块chokidar和 -Webpack 的相关配置项
同时,Nuxt.js 支持以generate命令将程序直接构建为静态 html ,就像上面说的,可以作为静态资源直接输出。 打包 npm run generate
开发中遇到的坑点
一开始用generate打包上线,发现首屏打开速度超慢,在测服上面大概需要10s+才能打开首页,但打开其他页面都是秒开,然后开始找原先,压缩vendor.js和app.js之后速度大约块一两s,此时首屏打开任很慢,仍然需要8s+才能打开页面,慢点的情况下可能需要30s+,实在不能忍,打开浏览器debug模式,发现首次加载的时候加载了所有的页面的js,这样首页打开速度慢.其他页面秒开就解释的通了,如下图,
原因找到了,接下来就是怎么解决了,于是我翻看官方文档,尝试了以下方法:在 nuxt.config.js 里面设置:
module.exports = {
render: {
resourceHints: false,
},
......
......
}
nuxt官方文档上是这样说明的
nuxt的坑还有很多,但是还是值得尝试的
(2)vue-server-renderer
让我们先创建一个项目并安装一些依赖项:
1. mkdir vue-ssr
2. cd vue-ssr
3. npm init
4 .npm i vue@next vue-server-renderer --save
package.json:
{
"name": "vue-ssr",
"version": "1.0.0",
"dependencies": {
"vue": "^2.5.17",
"vue-server-renderer": "^2.5.17"
}
}
注意:vue版本号要和vue-server-renderer版本号保持一致
webpack.base.config.js
module.exports = {
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [],
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
webpack.server.conf.js
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const path = require('path');
const root= path.resolve(__dirname, '..');
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: path.join(root, 'entry/entry-server.js'),
// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
output: {
libraryTarget: 'commonjs2',
path: path.join(root, 'dist'),
filename: 'bundle.server.js'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,
// 并生成较小的 bundle 文件。
externals: nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: /\.css$/
}),
// 这是将服务器的整个输出
// 构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
webpack.client.conf.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const path = require('path');
const root= path.resolve(__dirname, '..');
module.exports = merge(baseConfig, {
entry: path.join(root, 'entry/entry-client.js'),
output: {
path: path.join(root, 'dist'),
filename: 'bundle.client.js'
},
plugins: [
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
// 这也为你的 应用程序/vendor 代码提供了更好的缓存。
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
filename: 'manifest.js',
minChunks: Infinity
}),
// 此插件在输出目录中
// 生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
entry-server.js
/* entry-server.js */
import { createApp } from '../src/app'
export default context => {
return new Promise((resolve, reject) => {
const {app, router} = createApp()
// 更改路由
router.push(context.url)
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(() => {
const matchedComponents = app.$router.getMatchedComponents()
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) { return reject({code: 404}) }
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app)
}, reject)
})
}
entry-client.js
import { createApp } from '../src/app'
// 客户端特定引导逻辑……
const {app} = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
server.js
const path = require('path')
const express = require('express')
const app = express()
const { createBundleRenderer } = require('vue-server-renderer')
// 创建renderer
const template = require('fs').readFileSync('./index.ssr.html', 'utf-8')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json') // 这个可以动态将生成的js文件渲染到html模版中
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false, // 推荐
template: template,
clientManifest: clientManifest
})
app.use(express.static(path.join(__dirname, 'dist')))
// 响应路由请求
app.get('*', (req, res) => {
const context = { url: req.url }
// 创建vue实例,传入请求路由信息
renderer.renderToString(context, (err, html) => {
if (err) {
return res.state(500).end('运行时错误')
}
res.send(html)
})
})
// 服务器监听地址
app.listen(8099, () => {
console.log('服务器已启动!')
})
基本上到这个地方关键性构建以及服务模块代码补充完成后,一个简单的基于vue-server-renderer例子就可以运行起来了。