一、流式渲染与分块传输(面向性能的关键优化)
1.1 流式响应基础实现
// Node.js Express 示例(Vite SSR同理)import { renderToWebStream } from '@vue/server-renderer'app.get('/', async (req, res) => { res.setHeader('Content-Type', 'text/html;charset=utf-8') const context = await createSSRApp(req) // 流式传输关键步骤 const stream = renderToWebStream(app, context) stream.pipe(res) // 直接pipe到响应流})
效果对比:
方案 | FCP时间 | TTI时间 | 内存占用 | SEO支持 |
---|---|---|---|---|
传统SSR | 1200ms | 1500ms | 1.2GB | ✅ |
流式SSR | 600ms | 800ms | 430MB | ✅ |
CSR | 1800ms | 1800ms | 300MB | ❌ |
1.2 主动分块策略(解决首屏渲染长内容阻塞)
// 在Vue组件中定义分块点export default defineComponent({ async setup() { const data = await fetchData() // 关键:使用flush效果实现主动分块 if (import.meta.env.SSR) { const { flush } = await import('vue/server-renderer') await flush() // 强制将当前内容发送到客户端 } return { data } }})
分块规则建议:
- 首屏主内容:立即输出
- 首屏非关键内容(推荐模块):延迟到
<script>
前 - 异步数据块:使用
<script type="application/json">
注入
二、状态管理与数据预取(避免重复请求与水合异常)
2.1 Pinia + SSR的完美实践
// 在服务端预填充storeexport async function setupSSRStore(req) { const pinia = createPinia() const productStore = useProductStore(pinia) await productStore.fetch(req.params.id) // 阻塞预取 return { pinia }}// 客户端水合if (!import.meta.env.SSR) { const initialState = window.__PINIA_STATE__ if (initialState) { pinia.state.value = initialState }}
2.2 数据序列化安全方案
// 服务端序列化const serialized = devalue(pinia.state.value) // 使用devalue防御XSS// 客户端注入res.write(` <script> window.__PINIA_STATE__ = ${serialized} </script>`)
常见踩坑点:
- 避免在
onServerPrefetch
中进行耗时操作 - 客户端水合时必须校验数据格式
- 禁止序列化包含$开头的Vue私有属性
三、静态资源优化(每日必用的性能技巧)
3.1 智能预加载策略
<!-- 在服务端模板中动态生成资源提示 --><% if (criticalCSS) { %> <link rel="preload" href="<%= criticalCSS %>" as="style"> <% } %> <% if (firstImage) { %> <link rel="preload" href="<%= firstImage %>" as="image"> <% } %>
3.2 Vite构建配置优化
// vite.config.js export default defineConfig({ build: { cssCodeSplit: true, rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { return 'vendor' } if (id.includes('src/components')) { return 'components' } } } } } })
构建产物规则建议:
- 三方库单独打包(Vendor)
- 业务组件集中打包(Components)
- 路由级代码分割(需配合异步组件)
四、错误处理与日志追踪(生产环境必备)
4.1 SSR容错中间件
app.use('*', async (req, res, next) => { try { // 正常处理流程 } catch (error) { if (error instanceof ServerRedirectError) { return res.redirect(302, error.url) } // 关键:降级到CSR(保底措施) res.status(500).send(await renderCSRFallback()) // 记录详细错误日志 logSSRError({ url: req.url, error: error.stack, userAgent: req.headers['user-agent'] }) }})
4.2 全链路追踪标记
// 在SSR上下文中注入追踪IDconst traceId = generateTraceId()context.traceId = traceId// 在响应头中携带 res.set('X-Trace-Id', traceId)// 客户端同步标记 <script>window.__TRACE_ID__ = "<%= traceId %>"</script>
错误日志关键字段:
- Trace ID(全链路追踪)
- 用户设备信息
- 错误堆栈(敏感信息需脱敏)
- 页面路由状态
五、开发调试技巧(提效30%的实战工具)
5.1 使用vite-plugin-ssr调试
# 快速定位水合不匹配问题 DEBUG=vite:ssr:* npm run dev # 专用SSR调试面板 import { debugSSR } from 'vite-plugin-ssr' debugSSR({ logPerformance: true, highlightMismatches: true })
5.2 客户端水合检查工具
// 在入口文件添加检查 if (!import.meta.env.SSR) { const rootEl = document.getElementById('app') if (rootEl?.textContent === '') { console.error('SSR水合失败: 服务端渲染内容为空') } }
常用调试命令合集:
# 分析Bundle大小 npx vite-bundle-visualizer # 压力测试(需wrk工具) wrk -t12 -c400 -d30s http://localhost:3000 # 内存泄漏检查 NODE_OPTIONS=--inspect node server.js
🔧 每日必备工具链
- Lighthouse CI:自动化性能检查
- sentry-vue:生产环境错误监控
- server-timing:监控SSR各阶段耗时
- @vue/apollo-ssr:GraphQL预处理集成
- express-http-context:全链路请求追踪
实战经验总结:
- 缓存策略:对无状态接口实施
stale-while-revalidate
- 降级方案:开发CDN静态页备用预案
- 监控指标:首屏渲染时间需控制在0.8秒内
- 内存治理:使用
--max-old-space-size
限制Node内存