Vue SSR 中的路由处理与一致性保障

服务端路由处理

在服务端渲染(SSR)场景中,Vue路由的处理与纯客户端应用有显著不同。服务端需要预先确定要渲染的路由,并确保所有异步操作完成后再返回HTML。

router.onReady 等待异步路由

服务端需要等待路由解析完成才能开始渲染:

// 服务端入口文件
import { createApp } from './app'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    
    // 设置服务端路由位置
    router.push(context.url)
    
    // 等待路由解析完成
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      
      // 无匹配路由时返回404
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      resolve(app)
    }, reject)
  })
}

实践建议

  • 始终使用 router.onReady 确保路由解析完成
  • 处理404情况时返回适当的HTTP状态码
  • 考虑路由重定向场景的特殊处理

路由数据预取(serverPrefetch

在服务端渲染时,我们通常需要预先获取组件所需数据:

// 组件定义
export default {
  name: 'PostDetail',
  
  async serverPrefetch() {
    // 服务端预取数据
    await this.fetchPostData(this.$route.params.id)
  },
  
  methods: {
    fetchPostData(id) {
      return store.dispatch('fetchPost', id)
    }
  }
}

数据预取流程

图1

实践建议

  • 使用 serverPrefetch 而非 createdmounted 进行数据获取
  • 确保预取的数据能够被客户端访问(通过window.__INITIAL_STATE__等方式)
  • 处理数据预取失败的情况,提供优雅降级方案

客户端与服务端路由一致性

SSR应用中最常见的挑战之一是保持客户端和服务端渲染结果的一致性,避免hydration不匹配错误。

避免hydration不匹配问题

hydration不匹配通常由以下原因引起:

  1. 服务端和客户端初始数据不一致
  2. 依赖于客户端特定API(如window对象)
  3. 时间敏感的操作(如Date.now())
  4. 随机数生成不一致

解决方案

// 确保数据同步
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

// 客户端入口文件
const { app, router, store } = createApp()

router.onReady(() => {
  // 添加路由钩子处理asyncData
  router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
    
    let diffed = false
    const activated = matched.filter((c, i) => {
      return diffed || (diffed = (prevMatched[i] !== c))
    })
    
    const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
    if (!asyncDataHooks.length) {
      return next()
    }
    
    Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))
      .then(() => next())
      .catch(next)
  })
  
  app.$mount('#app')
})

实践建议

  • 使用相同的状态管理初始化逻辑(如Vuex store)
  • 避免在组件生命周期中直接调用可能产生差异的API
  • 对于必须的客户端特定逻辑,使用mounted钩子而非created
  • 开发环境下启用Vue的hydration警告,及时发现不匹配问题

路由数据同步策略

图2

关键代码实现

// 服务端数据注入
context.rendered = () => {
  context.state = store.state
}

// 客户端模板
<!DOCTYPE html>
<html>
  <head>
    <!-- 初始状态注入 -->
    <script>
      window.__INITIAL_STATE__ = ${serialize(state)}
    </script>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

总结

Vue SSR中的路由处理需要特别注意:

  1. 服务端路由解析:使用router.onReady确保所有异步路由解析完成
  2. 数据预取:通过serverPrefetch统一服务端和客户端数据获取方式
  3. 一致性保障:精心设计数据同步策略,避免hydration不匹配
  4. 错误处理:全面考虑各种边界情况(404、重定向、数据获取失败等)

遵循这些原则,可以构建出既保持SEO友好性,又具备流畅交互体验的Vue SSR应用。

评论已关闭