服务端渲染支持
服务端渲染的基本概念
服务端渲染(Server-Side Rendering,SSR)是指在服务器端将Vue组件渲染为HTML字符串,直接发送给浏览器。这种方式与传统的客户端渲染(Client-Side Rendering,CSR)不同,后者是在浏览器中通过JavaScript动态生成DOM。
// 客户端渲染示例
new Vue({
el: '#app',
render: h => h(App)
})
SSR的主要优势包括:
- 更好的SEO:搜索引擎可以直接抓取已渲染的HTML内容
- 更快的首屏加载:浏览器无需等待所有JavaScript下载和执行
- 更好的低端设备兼容性:减少客户端计算负担
Vue SSR的实现原理
Vue的SSR实现基于两个关键部分:vue-server-renderer
包和Node.js服务器。核心流程如下:
- 创建Vue实例工厂函数
- 使用
createRenderer
创建渲染器 - 在服务器请求处理中将Vue实例渲染为HTML
// 基本SSR实现示例
const Vue = require('vue')
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: () => ({ url: req.url }),
template: `<div>访问的URL是:{{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('服务器错误')
return
}
res.end(`
<!DOCTYPE html>
<html>
<head><title>SSR示例</title></head>
<body>${html}</body>
</html>
`)
})
})
Nuxt.js框架的SSR支持
Nuxt.js是为Vue应用提供开箱即用SSR支持的流行框架。它简化了SSR配置,提供了约定优于配置的开发体验。
典型Nuxt.js项目结构:
├── pages/ // 自动生成路由
├── components/ // Vue组件
├── store/ // Vuex存储
├── nuxt.config.js // 配置文件
配置示例:
// nuxt.config.js
export default {
ssr: true, // 启用SSR
target: 'server', // 服务器端渲染目标
head: {
title: '我的Nuxt应用',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
}
}
客户端激活(Hydration)
客户端激活是SSR中的关键概念,指在浏览器中"接管"服务器渲染的静态HTML,使其变为动态Vue应用的过程。
// 客户端入口文件
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app', // 必须匹配服务器HTML中的挂载点
render: h => h(App)
})
激活过程需要注意:
- 客户端和服务器端的Vue版本必须一致
- 初始状态必须匹配
- 避免在组件生命周期中使用仅客户端API
数据预取策略
SSR应用中常见的数据获取模式:
- 组件级数据预取:
// 页面组件
export default {
async asyncData({ params }) {
const { data } = await axios.get(`/api/user/${params.id}`)
return { user: data }
}
}
- Vuex存储预取:
// store action
export const actions = {
async fetchUser({ commit }, userId) {
const { data } = await axios.get(`/api/user/${userId}`)
commit('SET_USER', data)
}
}
- 服务器端数据注入:
// 服务器端
const store = createStore()
await store.dispatch('fetchUser', userId)
const context = { url: req.url, state: store.state }
性能优化技巧
- 组件缓存:
const LRU = require('lru-cache')
const renderer = createRenderer({
cache: LRU({
max: 10000,
maxAge: 1000 * 60 * 15 // 15分钟缓存
})
})
- 代码分割:
// 动态导入组件
const Foo = () => import('./Foo.vue')
// 路由级代码分割
const router = new VueRouter({
routes: [
{ path: '/foo', component: () => import('./Foo.vue') }
]
})
- 流式渲染:
// 使用renderToStream替代renderToString
const stream = renderer.renderToStream(app)
stream.pipe(res, { end: false })
stream.on('end', () => res.end('</body></html>'))
常见问题与解决方案
问题1:window/document未定义
// 解决方案:在mounted钩子中使用或检查运行环境
export default {
mounted() {
if (process.client) {
window.addEventListener('resize', this.handleResize)
}
}
}
问题2:内存泄漏
// 确保每个请求创建新的Vue实例
export default context => {
return new Vue({
data: () => ({ count: 0 }),
template: `<div>{{ count }}</div>`
})
}
问题3:异步组件加载
// 使用Vue的异步组件和webpack魔法注释
const AsyncComponent = () => ({
component: import(/* webpackChunkName: "async" */ './AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
部署与监控
- PM2部署配置:
{
"apps": [{
"name": "my-ssr-app",
"script": "server/index.js",
"instances": "max",
"exec_mode": "cluster",
"env": {
"NODE_ENV": "production"
}
}]
}
- 性能监控:
// 使用performance API监控关键指标
const perfObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('[Perf]', entry.name, entry.duration)
}
})
perfObserver.observe({ entryTypes: ['measure'] })
- 错误追踪:
// 全局错误处理
Vue.config.errorHandler = (err, vm, info) => {
logErrorToService(err, {
component: vm.$options.name,
lifecycleHook: info
})
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn