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
    }
  }
}

服务端处理流程:

图1

实践建议

  • 将数据预取逻辑放在 serverPrefetch 而非 createdmounted
  • 使用 Vuex 或 Pinia 管理共享状态,确保服务端和客户端状态一致
  • 对于关键数据,考虑添加加载状态和错误处理

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

hydration 不匹配问题

当服务端渲染的 DOM 结构与客户端挂载时的虚拟 DOM 不匹配时,会出现 hydration 错误。常见原因包括:

  1. 服务端和客户端初始状态不一致
  2. 依赖于客户端环境的代码(如 window 对象)
  3. 时间敏感的内容(如 Date.now()
  4. 第三方库的副作用差异

解决方案

// 确保服务端和客户端使用相同的随机种子
if (typeof window === 'undefined') {
  // 服务端逻辑
} else {
  // 客户端逻辑
}

路由一致性保障

  1. 共享路由配置
// shared/router.js
export function createRouter() {
  return new VueRouter({
    mode: 'history',
    routes: [
      // 统一的路由配置
    ]
  })
}
  1. 状态同步
// 服务端
context.router.push(req.url)
await router.isReady()

// 客户端
router.isReady().then(() => {
  app.mount('#app')
})
  1. 错误处理
// 客户端入口文件
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 效果。

评论已关闭