服务端渲染、SPA、同构
- 服务端渲染:后端不仅仅进行数据的获取处理,而且也服务页面生成,最终传给客户端(浏览器)是包含数据的 html
- SPA(单页应用):即客户端渲染,后端提供 API,前端面向 API 编程,更重要是前后端分离,首屏即可加载完所有资源,但也伴随白屏时间过长和不利于 SEO 问题
- 同构:前后端共用一套 js 代码,通过 node 服务器进行首屏渲染,跟现在 B 格较高的前端微服务有千丝万缕的联系
详细的网上说的很多,比如这篇https://zhuanlan.zhihu.com/p/33383814
服务端渲染解决什么问题
- SEO
- 首屏加载耗时较多,白屏时间长
- 导航不可用,ctrl+链接不是一个完整的链接
- 一些鉴权问题
服务端渲染什么时候用
- 个人博客,购物网站,需要 SEO 蜘蛛爬的页面
- 页面交互较少,大部分都是一些偏向展示的页面
一个简单的服务端渲染
- 使用
createRenderer()创建 render 实例 - 使用
renderToString(app)将 vue 实例转成 html 字符串 ,返回一个 promise,html 字符串带有data-sercer-rendered,意思是服务端渲染的元素
const Vue = require("vue");
// 创建渲染器
const renderer = require("vue-server-renderer").createRenderer();
const app = new Vue({
template: `<div>Hello World</div>`
});
// 输出:<div data-server-rendered="true">Hello World</div>
renderer
.renderToString(app)
.then(html => {
console.log(html);
})
.catch(err => {
console.log(err);
});
vue-server-renderer 与 koa 用法
- 创建 koa koa-router,使用
createRenderer()创建 render 实例 - 在路由中间件中,创建 vue 实例
- 使用
renderToString(app)将 vue 实例转成 html 字符串 - 将 html 写入 ctx.body 中
- 加载路由组件,
app.use(router.routes()).use(router.allowMethods()) - 启动 koa 服务器
// server.js
const Vue = require("vue");
const Koa = require("koa");
const Router = require("koa-router");
const renderer = require("vue-server-renderer").createRenderer();
// 创建koa koa-router实列
const app = new Koa();
const router = new Router();
// 路由中间件
router.get("*", async (ctx, next) => {
// 创建vue实列
const app = new Vue({
data: {
url: ctx.url
},
template: `<div>访问的URL是:{{url}}</div>`
});
try {
// vue 实列转换成字符串
const html = await renderer.renderToString(app);
ctx.status = 200;
ctx.body = `
<!DOCTYPE html>
<html>
<head><title>vue服务器渲染组件</title></head>
<body>${html}</body>
</html>
`;
} catch (e) {
console.log(e);
ctx.status = 500;
ctx.body = "服务器错误";
}
});
// 加载路由组件
app.use(router.routes()).use(router.allowedMethods());
// 启动服务
app.listen(3001, () => {
console.log(`server started at localhost:3001`);
});
vue-server-renderer 、koa 和 index.template.html
- 使用
createRenderer({template:html})读取 index.template.html,作为模板,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译 - 创建 koa koa-router 实例
- 在路由中间件中,创建 vue 实例
- 使用
renderToString(app,context)将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象 - 将 html 写入 ctx.body 中,
- 加载路由组件,
app.use(router.routes()).use(router.allowMethods()) - 启动 koa 服务器
为每一个路由请求创建一个 根 vue 实例
|---- ssr-demo1
| |--- src
| | |--- app.js
| | |--- index.template.html
| |--- .babelrc
| |--- .gitignore
| |--- server.js
| |--- package.json
- app.js 只负责
createApp,创建一个 vue 实例,避免状态单例,成为引用对象,一处修改,其他引用也跟着改 - server.js 中
- 使用
createRenderer({template:html})读取 index.template.html,作为模板,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译 - 创建 koa koa-router 实例
- 在路由中间件中,执行 createApp,进而创建 vue 实例
- 使用
renderToString(app,context)将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象 - 将 html 写入 ctx.body 中,
- 加载路由组件,
app.use(router.routes()).use(router.allowMethods()) - 启动 koa 服务器
- 使用
使用 vue-router 路由的实现及代码分割
|----- ssr-demo2
| |--- build
| | |--- webpack.base.config.js # webpack 基本配置
| | |--- webpack.client.config.js # 客户端打包配置
| | |--- webpack.server.config.js # 服务器端打包配置
| |--- src
| | |--- assets
| | |--- views
| | | |--- home.vue
| | | |--- item.vue
| | |--- app.js # 创建每一个实列文件
| | |--- App.vue
| | |--- entry-client.js # 挂载客户端应用程序
| | |--- entry-server.js # 挂载服务器端应用程序
| | |--- index.template.html
| | |--- router.js # 所有的路由
| |--- .babelrc
| |--- .gitignore
| |--- server.js
| |--- package.json
- app.vue 作为根 vue 实例
- app.js 只负责
createApp,创建一个 vue 实例,并且往 vue 实例中传入路由实例,避免状态单例,成为引用对象,一处修改,其他引用也跟着改 - entry-client.js 只负责挂载客户端应用程序,调用 createApp return 的 app,在
router.onReady时执行app.\$mount()挂载 dom - entry-server.js 负责挂载服务端应用程序,返回一个 promise,并且传入 app 和 router,使用
router.push(context.url)设置 router 的位置,因为路由可能是异步路由,服务器需要再数据渲染完准备,所以返回一个 promise,并且的router.onReady中使用router.getMatchedComponents获取路由匹配的所有组件后再 resolve createApp 的 app, - router.js 跟 vue spa 一样,简单的创建路由实例
- webpack.base.config.js,的 entry 就是
entry-client.js,设置了一些 rules - webpack.client.config.js merge webpack.base.config.js,设置了 style 的 rules,压缩 css 的插件,最终执行
npm run build:client生成vue-ssr-client-manifest.json作为客户端渲染的 json 插件 - webpack.client.server.js merge webpack.base.config.js ,entry 是
entry-server.js,服务端也要编译 css,设置了一些 style 的 rules,最终执行npm run build:server生成vue-ssr-server-bundle.json作为服务端渲染的 json 插件 - server.js
- 使用
createBundleRenderer读取 index.template.html,作为模板,传入 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json`,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译createRenderer() 和 createBundleRenderer(), 那么 createRenderer()是以 vue 组件为入口的,而 createBundleRenderer() 以打包后的 JS 文件或 json 文件为入口的
- 创建 koa koa-router 实例
- 在路由中间件中,执行 createApp,进而创建 vue 实例
- 使用
renderToString(context)将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象,context 会作为打包的 entry-server.js 的 context 传入,context 带上 koa 的路由,然后传入 router 实例,作为客户端的路由 - 将 html 写入 ctx.body 中,
- 可设置一个静态资源服务器
- 加载路由组件,
app.use(router.routes()).use(router.allowMethods()) - 启动 koa 服务器
- 使用
开发环境的热更新
- server.js 中的加入 dev.config.js 支持热更新
- 线上环境就是上面的打包方式
- 生产环境需要动态监听文件发生改变的配置文件
- 动态监听文件 dev.config.js
- 原理就是使用 memory-fs 将 webpack 写入内存,不是写入磁盘,使用 chokidar 监听文件变化,配置 koa 热替换插件,进行热替换,然后手动刷新 html,实现实时监听文件,实时打包
vuex 和 ssr
- 如果页面有异步请求,再服务器渲染之前,需要提前返回异步数据,在挂在 dom 之前可以通过 vue.$options 的 asyncData 应该获取到异步数据,asyncData 在组件实例化前用,所以此时不能使用 vue 的上下文,当前 this 是 window
- 在 entry-server.js 中,当 router.onReady 的时候,需要提前在 router.getMatchedComponents 获取匹配组件,如果匹配到组件由 asyncData,就执行他, 并且传入已经已经实例化后的 store 和 router.currentRoute,再把 store 的 state 放入
window.__INITIAL_STATE__,提前注入 html 中 - 在 entry.client.js 中如果
window.__INITIAL_STATE__存在,需替换 store 的根状态,确保服务端渲染是的数据和客户端获取的数据一致

确认评论邮箱
邀请制评论