- Published on
服务端渲染的简单理解
服务端渲染的好处:
- SEO友好
- 减少首屏时间
在这篇文章中,主要记录了 Vue 项目服务端渲染方式的实施。
Vue Cli 工程服务端渲染
前置知识
vue-router 的导航守卫
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
- 调用全局的
beforeEach
守卫 - 在可重用的组件里面调用
beforeRouteUpdate
。 - 在路由配置里调用
beforeEnter
- 解析异步的路由组件
- 在激活的组件里调用
beforeRouteEnter
- 调用全局的
beforeResolve
- 导航被确认
onReady
- 调用全局的
afterEach
- 触发
dom
更新 - 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调参数,其中组建实例作为函数参数被传入
编程时应该提前注意的点
- 服务端渲染的时候只有组件的
beforeCreated
和created
钩子才会调用,且无法在这两个钩子函数中使用window 中的任何对象。 - 尽量减少指令的使用,或者提供一个服务端的指令版本。应该尽可能地使用组件来完成抽象
- 对于第三方库,应该注意以上的第一条原则,适当地进行服务端的 polyfill。
- router 提供一个
onReady
钩子函数,在路由完成初始导航时调用。 router.getMatchedComponents
只限于页面组件,也就是路由组件。
挂载的区别
客户端
客户端挂载的时候,需要在路由被确认的时候,调用组件里面的asyncData函数来获取数据
router.beforeResolve(to, from, next){
// 获得 to这个route匹配到的组件
let matchComponents = route.getMatchedComponents(to),
asyncDatas = matchComponents.map(comp => comp.asyncData)
Promise.all(asyncDatas.map(hook => hook && hook(route: to, store))).then(next).catch(next)
**何时在客户端注册 ****beforeResolve**
当我们第一次访问应用的时候,onReady
钩子函数先于 beforeResolve
执行,又因为我们已经在服务端已经获取到了第一个路由所需要的数据,所以我们应该在第一个路由确认之后再注册这个 beforeResolve
route.onReady(() => {
router.beforeResolve()
})
最后以激活模式挂载
// 有 data-server-rendered 属性就会使用激活模式挂载
// 也可以传入第二个参数 true 强制使用激活模式挂载
app.$mount('#app', true)
服务端
在服务端的时候,并不执行挂载动作。因为服务端并没有浏览器环境,无法挂载。
服务端的入口js需要导出一个返回值为Promise的函数
在导航初次确认的守卫中,获取当前路由的数据,之后设置 window.__INITIALSTATE__
export default (context) => {
// check url
let { router, store, app } = createApp()
const url = context.url
const { fullPath } = router.resolve(url).route
if (fullPath !== url) {
return Promise.reject({ url: fullPath })
}
// navigate to url
router.push(url)
return new Promise((resolve, reject) => {
router.onReady(() => {
const matchComponents = router.getMatchedComponents()
if (!matchComponents.length) {
return reject({ code: 404 })
}
// wait async data
return Promise.all(
matchComponents.map(
({ asyncData }) => asyncData && asyncData({ route: router.currentRoute, store })
)
)
.then(() => {
// window.__INITIAL_STATE__
context.state = store.state
resolve(app)
})
.catch(reject)
})
})
}
打包
需要分开打包客户端和服务端所以用的版本。
其中服务端版本不需要压缩和 code-spliting , 客户端和服务端版本需要 VueSSRServerPlugin 和 VueSSRClientPlugin 来互相映射。
可以说打包脚本是固定的,需要的时候互相参考之前的项目就可以了。
打包之后将会生成两个很重要的json文件。vue-ssr-client-manifest.json 和 vue-ssr-server-bundle.json。
其中 vue-ssr-server-bundle.json 是整个项目的代码,包括 js, css。
为了保持这两个 json 的映射关系是正确的,需要注意保持两端的打包脚本 entry,ouput是一样的配置。
当然服务端的打包脚本可以指定output.libraryTarget 为‘commonjs2’。因为服务端脚本打包的文件是在node环境下加载的。
Nuxt 服务端渲染
Nuxt 是基于 Vue 的第三方框架,使用 Nuxt 可以很方便地继承服务端渲染。在每一个 Page 组件中,都提供了一个 asyncData
或者是 fetch
方法来获取数据。在服务端,或者切换至目标路由的时候,都会调用这两个函数,而且注意是在组件初始化之前调用,这就意味着你不能访问当前组件实例。
但是 asyncData
resolve 的数据会被设置为组件数据,而 fetch
函数的结果则不会。在调用的时候,会传入 context 对象。
export default {
asyncData({ $axios }) {
return $axios.get('xxxx').then((res) => res.data)
},
fetch({ params, store, $axios }) {
$axios.get('xxxx').then((res) => store.commit('xx', res.data))
},
}
部署
# 修改 nuxt.config.js 的 mode 为 universal
# 打包
nuxt build
# 启动服务
nuxt start