Vue SSR场景下应在何处调用Vue.use(plugin)?附bootstrap-vue实例问题
解决SSR中bootstrap-vue导致的Hydration不匹配问题
这个警告的核心原因很明确:服务端渲染的DOM结构和客户端hydrate时生成的DOM结构不一致——你只在客户端的router.onReady()里调用Vue.use(BootstrapVue),导致服务端的Vue实例根本没加载bootstrap-vue插件:服务端渲染的是原生HTML结构,客户端却用bootstrap-vue的组件重新渲染了一遍,结构自然对不上。
下面给你一套具体的解决步骤:
1. 统一在共享入口初始化bootstrap-vue
不要只在客户端注册插件,要让服务端和客户端的Vue环境完全同步。建议创建一个共享的应用创建函数,让服务端和客户端入口都调用它,在这个函数里完成插件的全局注册。
举个实际的代码例子:
首先创建createApp.js(服务端和客户端共用的入口):
import Vue from 'vue' import BootstrapVue from 'bootstrap-vue' // 别忘了引入样式,服务端和客户端都需要 import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css' import App from './App.vue' import router from './router' import store from './store' // 全局注册bootstrap-vue,服务端和客户端都会执行这一步 Vue.use(BootstrapVue) export function createApp() { const app = new Vue({ router, store, render: h => h(App) }) return { app, router, store } }
然后修改服务端入口entry-server.js:
import { createApp } from './createApp' export default context => { return new Promise((resolve, reject) => { const { app, router } = createApp() router.push(context.url) router.onReady(() => { const matchedComponents = router.getMatchedComponents() if (!matchedComponents.length) { return reject({ code: 404 }) } resolve(app) }, reject) }) }
再修改客户端入口entry-client.js:
import { createApp } from './createApp' const { app, router } = createApp() router.onReady(() => { app.$mount('#app') })
这样一来,服务端和客户端的Vue实例都会加载bootstrap-vue插件,渲染出来的DOM结构就能保持一致了。
2. 排查依赖客户端API的组件
如果还是有警告,那可能是某些bootstrap-vue组件依赖了客户端的API(比如window、document),服务端渲染时无法正常生成对应的DOM。这种情况下,你可以用Vue的<client-only>标签包裹这些组件,让它们只在客户端渲染:
<template> <div> <!-- 服务端能正常渲染的组件 --> <b-card title="SSR友好组件"></b-card> <!-- 依赖客户端API的组件,只在客户端渲染 --> <client-only> <b-modal id="example-modal" title="客户端专属组件"></b-modal> </client-only> </div> </template>
3. 检查其他导致DOM不匹配的因素
有时候问题不一定出在bootstrap-vue上,你可以排查一下自己的业务组件:
- 有没有在服务端渲染时调用了客户端专属的API?比如在
created钩子中使用window,这会导致服务端和客户端渲染的内容不一致。 - 有没有条件渲染依赖客户端状态?比如根据
localStorage的值显示不同内容,服务端没有localStorage,自然渲染结果不同。
这类问题的解决方法是:用process.client判断环境,或者把依赖客户端的逻辑放到mounted钩子中执行。
比如:
export default { data() { return { userTheme: 'light' } }, mounted() { // 只在客户端获取localStorage的值 if (process.client) { this.userTheme = localStorage.getItem('theme') || 'light' } } }
内容的提问来源于stack exchange,提问作者Hans Knöchel
相关产品推荐
相关产品推荐

