服务端渲染优化
服务端渲染的核心概念
服务端渲染(SSR)与传统客户端渲染(CSR)的最大区别在于HTML的生成时机。在SSR模式下,Vue组件会在服务器端被编译成HTML字符串,直接发送给浏览器。这种方式能显著改善首屏加载性能,特别是对于内容型网站或SEO敏感的页面。
// 基本SSR示例
const Vue = require('vue')
const server = require('express')()
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 lang="en">
<head><title>SSR示例</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
性能优化策略
组件级缓存
对于纯静态组件或很少变化的组件,可以实现缓存策略。Vue SSR提供了内置的缓存接口,可以显著减少组件渲染时间。
const LRU = require('lru-cache')
const renderer = createRenderer({
cache: LRU({
max: 10000,
maxAge: 1000 * 60 * 15 // 15分钟缓存
})
})
// 在组件中添加serverCacheKey
export default {
name: 'StaticComponent',
props: ['item'],
serverCacheKey: props => props.item.id,
// ...
}
流式渲染
使用流式渲染可以更快地将内容发送到客户端,特别对于大型应用效果明显。Express框架可以直接支持流式响应。
const stream = require('stream')
const renderStream = renderer.renderToStream(app)
renderStream.on('data', chunk => {
res.write(chunk)
})
renderStream.on('end', () => {
res.end()
})
数据预取优化
统一数据获取
在服务端渲染时,需要确保客户端和服务端获取的数据一致。通常使用Vuex进行状态管理,并在渲染前预取所有必要数据。
// store.js
export function createStore () {
return new Vuex.Store({
state: {
items: []
},
actions: {
fetchItems({ commit }) {
return axios.get('/api/items').then(res => {
commit('setItems', res.data)
})
}
}
})
}
// entry-server.js
export default context => {
const store = createStore()
return Promise.all([
store.dispatch('fetchItems')
]).then(() => {
context.state = store.state
return new Vue({
store,
render: h => h(App)
})
})
}
数据缓存策略
对于不经常变化的数据,可以在服务端实现缓存层,避免每次渲染都请求相同数据。
const dataCache = new LRU({
max: 1000,
maxAge: 1000 * 60 * 10 // 10分钟
})
async function fetchData (key) {
if (dataCache.has(key)) {
return dataCache.get(key)
}
const data = await axios.get(`/api/data/${key}`)
dataCache.set(key, data)
return data
}
代码分割与懒加载
动态导入组件
即使在SSR环境下,仍然可以使用动态导入来实现代码分割,减少初始包大小。
// 使用动态导入
const AsyncComponent = () => ({
component: import('./AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
// 路由配置中使用
const router = new VueRouter({
routes: [
{ path: '/async', component: AsyncComponent }
]
})
服务端友好的代码分割
需要确保服务端能正确处理动态导入的组件,通常需要额外的webpack配置。
// webpack.server.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
}
]
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1 // 服务端打包为单个文件
})
]
}
内存管理与性能监控
内存泄漏预防
SSR应用需要特别注意内存管理,避免因未正确释放资源导致的内存泄漏。
// 在渲染完成后清理Vue实例
function renderApp(req, res) {
const app = new Vue({ /* ... */ })
renderer.renderToString(app, (err, html) => {
app.$destroy() // 显式销毁实例
// ...
})
}
性能指标收集
实现渲染性能监控可以帮助发现瓶颈,优化关键路径。
const perf = require('execution-time')()
server.get('*', (req, res) => {
perf.start('render')
renderToString(app, (err, html) => {
const result = perf.stop('render')
console.log(`渲染耗时: ${result.time}ms`)
// ...
})
})
构建配置优化
生产环境配置
SSR需要特定的webpack配置来生成服务端和客户端bundle。
// webpack.base.config.js
module.exports = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js'
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
}
// webpack.client.config.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
module.exports = merge(baseConfig, {
entry: './entry-client.js',
// 客户端特定配置...
})
// webpack.server.config.js
module.exports = merge(baseConfig, {
target: 'node',
entry: './entry-server.js',
output: {
libraryTarget: 'commonjs2'
},
// 服务端特定配置...
})
资源预加载
利用<link rel="preload">
提前加载关键资源,加速客户端激活过程。
// 在渲染模板中添加预加载
const renderer = createRenderer({
template: `
<!DOCTYPE html>
<html>
<head>
<!-- 预加载关键资源 -->
<link rel="preload" href="/dist/main.js" as="script">
<link rel="preload" href="/dist/vendor.js" as="script">
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
`
})
错误处理与降级方案
优雅的错误处理
SSR过程中需要妥善处理各种可能的错误,避免服务崩溃。
// 错误处理中间件
function handleSSRError(err, req, res, next) {
if (err.code === 404) {
res.status(404).send('页面不存在')
} else {
console.error('SSR错误:', err)
// 降级为客户端渲染
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="/dist/client.js"></script>
</body>
</html>
`)
}
}
server.use(handleSSRError)
超时处理
为SSR渲染设置合理的超时时间,防止长时间阻塞。
function renderWithTimeout(app, timeout = 2000) {
return new Promise((resolve, reject) => {
let timedOut = false
const timer = setTimeout(() => {
timedOut = true
reject(new Error('渲染超时'))
}, timeout)
renderer.renderToString(app, (err, html) => {
clearTimeout(timer)
if (!timedOut) {
if (err) reject(err)
else resolve(html)
}
})
})
}
实战中的高级技巧
混合渲染策略
对关键路由使用SSR,非关键路由使用CSR,实现最佳性能平衡。
// 路由配置示例
const routes = [
{ path: '/', component: Home, meta: { ssr: true } }, // 使用SSR
{ path: '/dashboard', component: Dashboard, meta: { ssr: false } } // 使用CSR
]
// 服务端路由处理
server.get('*', (req, res) => {
const matched = router.getMatchedComponents(req.url)
const useSSR = matched.some(component => component.meta.ssr !== false)
if (useSSR) {
// 执行SSR渲染
} else {
// 返回CSR模板
}
})
渐进式激活
在客户端激活阶段采用渐进式策略,优先处理关键组件。
// 自定义客户端激活逻辑
const criticalComponents = ['Header', 'HeroSection']
function prioritizeHydration() {
criticalComponents.forEach(name => {
const elements = document.querySelectorAll(`[data-component="${name}"]`)
elements.forEach(el => {
const vm = new Vue({
el,
// 组件配置...
})
})
})
// 延迟处理非关键组件
setTimeout(() => {
const otherElements = document.querySelectorAll('[data-component]')
// 处理剩余组件...
}, 1000)
}
document.addEventListener('DOMContentLoaded', prioritizeHydration)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:编译时优化分析