Server-side rendering optimization
Core Concepts of Server-Side Rendering
The primary difference between Server-Side Rendering (SSR) and traditional Client-Side Rendering (CSR) lies in the timing of HTML generation. In SSR mode, Vue components are compiled into HTML strings on the server and sent directly to the browser. This approach significantly improves first-page load performance, especially for content-driven websites or SEO-sensitive pages.
// Basic SSR Example
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>Visited URL: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>SSR Example</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
Performance Optimization Strategies
Component-Level Caching
For purely static or rarely changing components, caching strategies can be implemented. Vue SSR provides built-in caching interfaces, which can significantly reduce component rendering time.
const LRU = require('lru-cache')
const renderer = createRenderer({
cache: LRU({
max: 10000,
maxAge: 1000 * 60 * 15 // 15-minute cache
})
})
// Add serverCacheKey to components
export default {
name: 'StaticComponent',
props: ['item'],
serverCacheKey: props => props.item.id,
// ...
}
Streaming Rendering
Using streaming rendering allows content to be sent to the client faster, especially for large applications. The Express framework directly supports streaming responses.
const stream = require('stream')
const renderStream = renderer.renderToStream(app)
renderStream.on('data', chunk => {
res.write(chunk)
})
renderStream.on('end', () => {
res.end()
})
Data Prefetching Optimization
Unified Data Fetching
In SSR, it's essential to ensure data consistency between the client and server. Typically, Vuex is used for state management, and all necessary data is prefetched before rendering.
// 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)
})
})
}
Data Caching Strategy
For infrequently changing data, a caching layer can be implemented on the server to avoid requesting the same data repeatedly.
const dataCache = new LRU({
max: 1000,
maxAge: 1000 * 60 * 10 // 10-minute cache
})
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
}
Code Splitting and Lazy Loading
Dynamic Component Import
Even in SSR environments, dynamic imports can be used for code splitting to reduce the initial bundle size.
// Using dynamic imports
const AsyncComponent = () => ({
component: import('./AsyncComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
})
// Usage in route configuration
const router = new VueRouter({
routes: [
{ path: '/async', component: AsyncComponent }
]
})
SSR-Friendly Code Splitting
Ensure the server can correctly handle dynamically imported components, typically requiring additional webpack configuration.
// webpack.server.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
}
]
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1 // Bundle server-side code into a single file
})
]
}
Memory Management and Performance Monitoring
Preventing Memory Leaks
SSR applications require special attention to memory management to avoid leaks caused by improperly released resources.
// Clean up Vue instances after rendering
function renderApp(req, res) {
const app = new Vue({ /* ... */ })
renderer.renderToString(app, (err, html) => {
app.$destroy() // Explicitly destroy the instance
// ...
})
}
Performance Metrics Collection
Implementing rendering performance monitoring helps identify bottlenecks and optimize critical paths.
const perf = require('execution-time')()
server.get('*', (req, res) => {
perf.start('render')
renderToString(app, (err, html) => {
const result = perf.stop('render')
console.log(`Rendering time: ${result.time}ms`)
// ...
})
})
Build Configuration Optimization
Production Environment Configuration
SSR requires specific webpack configurations to generate server and client bundles.
// 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',
// Client-specific configurations...
})
// webpack.server.config.js
module.exports = merge(baseConfig, {
target: 'node',
entry: './entry-server.js',
output: {
libraryTarget: 'commonjs2'
},
// Server-specific configurations...
})
Resource Preloading
Use <link rel="preload">
to preload critical resources, speeding up the client activation process.
// Add preload to the rendering template
const renderer = createRenderer({
template: `
<!DOCTYPE html>
<html>
<head>
<!-- Preload critical resources -->
<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>
`
})
Error Handling and Fallback Strategies
Graceful Error Handling
Properly handle various potential errors during SSR to avoid server crashes.
// Error handling middleware
function handleSSRError(err, req, res, next) {
if (err.code === 404) {
res.status(404).send('Page not found')
} else {
console.error('SSR Error:', err)
// Fallback to client-side rendering
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="/dist/client.js"></script>
</body>
</html>
`)
}
}
server.use(handleSSRError)
Timeout Handling
Set reasonable timeout limits for SSR rendering to prevent prolonged blocking.
function renderWithTimeout(app, timeout = 2000) {
return new Promise((resolve, reject) => {
let timedOut = false
const timer = setTimeout(() => {
timedOut = true
reject(new Error('Rendering timeout'))
}, timeout)
renderer.renderToString(app, (err, html) => {
clearTimeout(timer)
if (!timedOut) {
if (err) reject(err)
else resolve(html)
}
})
})
}
Advanced Techniques in Practice
Hybrid Rendering Strategy
Use SSR for critical routes and CSR for non-critical routes to achieve optimal performance balance.
// Route configuration example
const routes = [
{ path: '/', component: Home, meta: { ssr: true } }, // Use SSR
{ path: '/dashboard', component: Dashboard, meta: { ssr: false } } // Use CSR
]
// Server-side route handling
server.get('*', (req, res) => {
const matched = router.getMatchedComponents(req.url)
const useSSR = matched.some(component => component.meta.ssr !== false)
if (useSSR) {
// Perform SSR rendering
} else {
// Return CSR template
}
})
Progressive Hydration
Adopt a progressive strategy during client-side hydration, prioritizing critical components.
// Custom client-side hydration logic
const criticalComponents = ['Header', 'HeroSection']
function prioritizeHydration() {
criticalComponents.forEach(name => {
const elements = document.querySelectorAll(`[data-component="${name}"]`)
elements.forEach(el => {
const vm = new Vue({
el,
// Component configuration...
})
})
})
// Delay processing non-critical components
setTimeout(() => {
const otherElements = document.querySelectorAll('[data-component]')
// Process remaining components...
}, 1000)
}
document.addEventListener('DOMContentLoaded', prioritizeHydration)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:编译时优化分析