阿里云主机折上折
  • 微信号
Current Site:Index > Server-side rendering optimization

Server-side rendering optimization

Author:Chuan Chen 阅读数:26317人阅读 分类: Vue.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.