关于 vue 做服务端渲染,目前主要有俩种解决方案,使用 vue-server-renderer 或者使用 nuxt 。但个人感觉使用 nuxt 写法太死,以及即使你用 nuxt 写出了 vue ssr 页面,可能你也只是说熟悉了 nuxt 这个框架,而不清楚 vue ssr 具体的原理。以下我会基于 vue ssr 渲染示例 vue-hackernews-2.0 以及豆瓣 api 实现一个简单的 vue-ssr。开始之前,请先大致阅读下官方文档
简单示例
首先通过一个简单的示例了解什么是服务端渲染。所谓服务端渲染就是通过服务端请求获取数据,根据 vue 模版渲染好 html 页面返回给前端,验证一个页面是不是通过服务端渲染的可以在页面上右键查看源代码有没有页面的 html 信息。
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>访问的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
// 返回 html 信息
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
源码结构
|--build: webpack 配置文件
|--dist: 编译后文件目录
|--log: 日志目录
|--node_modules: npm 包
|--public: 公共文件目录
|--src: 项目开发目录
|--api: api 目录
|--assets: 资源目录,图片,css等
|--components: 公共组件
|--router: 路由
|--store: vuex 状态管理
|--util: 公共方法
|--views: 各目录对应各个页面
|--.babelrc: babel 配置
|--eslintrc.js: eslint 配置
|--ecosystem.js: pm2 配置文件
|--package.json: npm 包配置
|--postcss.config.js: postcss 配置
|--config.js: 配置文件
|--server.js: node 服务
了解 vue 服务端渲染最主要的是要知道上面的图片到底讲了什么。
store、Router和component
这三者为不管是服务端渲染还是客户端渲染均可使用的公共模块
app.js
定义好 vue 实例 app,路由 router、数据 store。方便服务端与客户端做同步。
Server entry 与 Client entry
首先为什么会有 Server entry 和 Client entry 俩个入口。我们假设页面有俩个路由。当俩个路由均是通过浏览器输入 url 直接访问的,则均通过服务端请求数据渲染好页面返回给前端,俩个页面均是走 Server entry。当一个路由是通过浏览器输入,而另一个路由是通过当前路由点击跳转到达的,走spa(单页) 前端请求数据渲染页面,则第一个路由走 Server entry,跳转的页面走 Client entry。
Server entry 与 Client entry 定义了一个钩子函数 asyncData 参数为 store 与 router,在服务端执行。同步了客户端与服务端的路由与数据。
webpack 打包
左侧 source 通过 webpack 打包,让 node 执行 Server enter 打包的代码,浏览器执行 Client entry 打包的代码。
同步路由
不管是走服务端渲染还是客户端渲染,我们均使用同一套路由,在 app.js 中定义一个 router 对象,在 Server entry 与 Client entry 中均使用该对象。
同步数据
服务端渲染好页面返回给客户端,客户端渲染好页面需要做一些交互修改一些数据,数据是通过服务端请求获取的,此时还存在服务端,客户端如何获取到这些数据呢?
服务端请求的数据会存放在 store 中,在 Server entry 中将 数据赋值给 context.state。执行 renderTostring 时会将数据存放在 window.__INITIAL_STATE__ 返回给前端。Client entry 将会读取 window.__INITIAL_STATE__ 中的数据存放在 store 中。这样就可以在 store 中获取到数据。
异步请求
前端页面直接调用第三方接口会报跨域错误,vue-hackernews-2.0 是使用 firebase 做处理,我这边直接在 node 端定义了俩个路由通过后端去请求。
遇到的坑
从主页跳到页面详情页时,后端请求接口获取到的数据我通过详情页 key - value的形式存放在 store 中,当请求的多次不同的详情也时导致返回的 window. INITIAL_STATE 数据量爆炸,页面加载缓慢。其实只需每个路由建一个 key 即可,例详情页只建立一个 detail 的 key,请求的数据赋值给 detail 即可。见 issue
参考
https://ssr.vuejs.org/zh/
https://github.com/vuejs/vue-hackernews-2.0
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。