Vue SSR路由处理与一致性保障最佳实践之二
Vue SSR 中的路由处理与一致性保障
服务端路由处理
在服务端渲染(SSR)场景下,Vue Router 的处理方式与客户端有些不同,需要特别注意以下几个关键点。
服务端路由初始化
在服务端,我们需要为每个请求创建一个新的 Router 实例,避免状态污染:
// server.js
import { createSSRApp } from 'vue'
import { createRouter } from 'vue-router'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const router = createRouter({
// ...路由配置
})
app.use(router)
return { app, router }
}
router.onReady
等待异步路由
在服务端渲染时,我们需要确保所有异步路由组件都已解析完成:
router.onReady(() => {
// 此时异步路由组件已加载完成
const matchedComponents = router.currentRoute.value.matched
.map(record => record.components.default)
// 执行服务端渲染
renderToString(app, (err, html) => {
// 处理渲染结果
})
})
实践建议:
- 始终在
onReady
回调中进行渲染,确保异步组件加载完成 - 对于复杂的异步依赖,考虑使用 Promise.all 等待所有数据就绪
路由数据预取 (serverPrefetch
)
在 SSR 中,我们通常需要在渲染前预取数据。Vue 提供了 serverPrefetch
钩子:
export default {
async serverPrefetch() {
// 预取数据并存储在 store 中
await this.$store.dispatch('fetchData', this.$route.params.id)
},
computed: {
data() {
return this.$store.state.data
}
}
}
服务端处理流程:
实践建议:
- 将数据预取逻辑放在
serverPrefetch
而非created
或mounted
- 使用 Vuex 或 Pinia 管理共享状态,确保服务端和客户端状态一致
- 对于关键数据,考虑添加加载状态和错误处理
客户端与服务端路由一致性
hydration 不匹配问题
当服务端渲染的 DOM 结构与客户端挂载时的虚拟 DOM 不匹配时,会出现 hydration 错误。常见原因包括:
- 服务端和客户端初始状态不一致
- 依赖于客户端环境的代码(如
window
对象) - 时间敏感的内容(如
Date.now()
) - 第三方库的副作用差异
解决方案:
// 确保服务端和客户端使用相同的随机种子
if (typeof window === 'undefined') {
// 服务端逻辑
} else {
// 客户端逻辑
}
路由一致性保障
- 共享路由配置:
// shared/router.js
export function createRouter() {
return new VueRouter({
mode: 'history',
routes: [
// 统一的路由配置
]
})
}
- 状态同步:
// 服务端
context.router.push(req.url)
await router.isReady()
// 客户端
router.isReady().then(() => {
app.mount('#app')
})
- 错误处理:
// 客户端入口文件
router.onError((err) => {
console.error('路由错误:', err)
// 可以跳转到错误页面
})
实践建议:
- 使用
v-if
而非v-show
处理动态内容,避免 hydration 问题 - 对于不可避免的差异,使用
v-cloak
或添加加载状态 - 在开发环境下启用 Vue 的 hydration 警告,及时发现不一致问题
完整示例:SSR 路由处理
// 服务端入口
export default context => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp()
// 设置服务端路由位置
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// 无匹配路由
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// 调用匹配组件的 serverPrefetch
Promise.all(matchedComponents.map(Component => {
if (Component.serverPrefetch) {
return Component.serverPrefetch(store)
}
})).then(() => {
// 将状态附加到上下文
context.state = store.state
resolve(app)
}).catch(reject)
}, reject)
})
}
通过以上措施,可以确保 Vue SSR 应用的路由处理在服务端和客户端保持一致,提供更好的用户体验和 SEO 效果。
评论已关闭