Vue+Express SSR因Hydration不匹配失败,求原因排查
排查Vue SSR Hydration不匹配问题(本地打包Vue替代CDN引入)
可能原因及解决方案
1. 打包模式未区分SSR/客户端环境
Vite默认打包是面向客户端的,若未为SSR场景单独配置,产物会缺失SSR适配逻辑,导致服务端与客户端渲染逻辑不一致:
- 新增SSR专属打包配置:
// vite.ssr.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], build: { ssr: true, outDir: 'dist-ssr', rollupOptions: { input: './src/entry-server.js', output: { format: 'cjs' } // SSR需CommonJS格式 } } }) - 客户端打包单独配置,避免混入SSR代码:
// vite.client.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], build: { outDir: 'dist-client', rollupOptions: { input: './src/entry-client.js' } } }) - 执行打包时分别调用对应配置:
vite build --config vite.ssr.config.js和vite build --config vite.client.config.js
2. Vue版本不一致
本地打包的Vue版本与原CDN版本存在差异,会导致渲染输出的DOM结构细节不同:
- 查看原CDN引入的Vue版本(如
https://unpkg.com/vue@3.3.4/dist/vue.global.js),在本地项目中锁定相同版本:npm install vue@3.3.4 --save-exact
3. 依赖客户端环境的代码提前执行
服务端渲染时不存在window、document等浏览器API,若组件在setup阶段直接调用这些API,会导致服务端输出空值/占位符,客户端渲染时生成实际内容,引发结构不匹配:
- 错误示例:
<template> <div>{{ window.innerWidth }}</div> </template> - 修正为客户端挂载后再获取:
<script setup> import { ref, onMounted } from 'vue' const width = ref(0) onMounted(() => { width.value = window.innerWidth }) </script> <template> <div>{{ width }}</div> </template>
4. 产物引入路径错误
客户端若误引入SSR打包产物,会导致运行逻辑与服务端渲染的Vue实例不兼容:
- 确保HTML中引入的是客户端打包产物:
<!-- 正确 --> <script src="/dist-client/index.js"></script> <!-- 错误 --> <script src="/dist-ssr/entry-server.js"></script>
5. 服务端状态序列化异常
服务端注入到HTML的初始化状态未正确序列化,客户端无法恢复一致状态,导致渲染差异:
- 服务端需将状态序列化为JSON字符串:
// 服务端渲染逻辑 const app = createSSRApp(App) const renderedHtml = await renderToString(app) const initialState = JSON.stringify(app._instance.setupState) return ` <html> <body> <div id="app">${renderedHtml}</div> <script>window.__INITIAL_STATE__ = ${initialState}</script> <script src="/dist-client/index.js"></script> </body> </html> ` - 客户端读取并恢复状态:
// 客户端入口 const app = createSSRApp(App) if (window.__INITIAL_STATE__) { app._instance.setupState = window.__INITIAL_STATE__ } app.mount('#app')
快速验证步骤
- 用浏览器开发者工具对比服务端输出的原始HTML和客户端挂载后的DOM结构,高亮的差异节点就是问题根源
- 临时换回CDN引入的Vue,确认错误消失,排除业务代码本身的问题
- 查看Vite打包日志,检查是否有未处理的警告(如未兼容SSR的依赖)
内容的提问来源于stack exchange,提问作者Yang Jiang
相关产品推荐
相关产品推荐

