6.1 缓存策略

6.1.1 组件缓存

// server.js - 组件缓存配置
const LRU = require('lru-cache')
const { createBundleRenderer } = require('vue-server-renderer')

// 创建组件缓存
const microCache = new LRU({
  max: 100,
  maxAge: 1000 * 60 * 15 // 15分钟
})

const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template,
  clientManifest,
  // 组件缓存
  cache: microCache
})

// 页面级缓存
const pageCache = new LRU({
  max: 1000,
  maxAge: 1000 * 60 * 15
})

app.get('*', (req, res) => {
  const hit = pageCache.get(req.url)
  if (hit) {
    return res.end(hit)
  }

  renderer.renderToString(context, (err, html) => {
    if (err) {
      return handleError(err)
    }
    pageCache.set(req.url, html)
    res.end(html)
  })
})

6.1.2 智能缓存策略

// utils/cache.js
class SmartCache {
  constructor() {
    this.componentCache = new LRU({ max: 100, maxAge: 1000 * 60 * 15 })
    this.pageCache = new LRU({ max: 1000, maxAge: 1000 * 60 * 5 })
    this.dataCache = new LRU({ max: 500, maxAge: 1000 * 60 * 10 })
  }

  // 组件缓存键生成
  getComponentCacheKey(componentName, props) {
    return `${componentName}:${JSON.stringify(props)}`
  }

  // 页面缓存键生成
  getPageCacheKey(url, userAgent, locale) {
    return `${url}:${userAgent}:${locale}`
  }

  // 数据缓存
  cacheData(key, data, ttl = 600000) {
    this.dataCache.set(key, data, ttl)
  }

  // 缓存失效
  invalidateCache(pattern) {
    const keys = this.pageCache.keys()
    keys.forEach(key => {
      if (key.includes(pattern)) {
        this.pageCache.del(key)
      }
    })
  }
}

module.exports = new SmartCache()

6.1.3 CDN缓存配置

// middleware/cache-headers.js
function setCacheHeaders(req, res, next) {
  // 静态资源缓存
  if (req.url.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
    res.setHeader('Cache-Control', 'public, max-age=31536000') // 1年
    res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString())
  }
  // HTML页面缓存
  else if (req.url.match(/\.(html)$/) || req.url === '/') {
    res.setHeader('Cache-Control', 'public, max-age=300') // 5分钟
  }
  // API缓存
  else if (req.url.startsWith('/api/')) {
    res.setHeader('Cache-Control', 'public, max-age=60') // 1分钟
  }
  
  next()
}

module.exports = setCacheHeaders

6.2 代码分割与懒加载

6.2.1 路由级代码分割

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  },
  {
    path: '/products',
    name: 'Products',
    component: () => import('../views/Products.vue'),
    children: [
      {
        path: ':id',
        name: 'ProductDetail',
        component: () => import('../views/ProductDetail.vue')
      }
    ]
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('../views/admin/Layout.vue'),
    children: [
      {
        path: 'dashboard',
        component: () => import('../views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import('../views/admin/Users.vue')
      }
    ]
  }
]

export function createAppRouter() {
  return createRouter({
    history: createWebHistory(),
    routes
  })
}

6.2.2 组件级懒加载

<!-- components/LazyComponent.vue -->
<template>
  <div>
    <Suspense>
      <template #default>
        <AsyncComponent v-if="shouldLoad" />
      </template>
      <template #fallback>
        <div class="loading">加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

<script>
import { ref, onMounted, defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: () => import('./LoadingSpinner.vue'),
  errorComponent: () => import('./ErrorComponent.vue'),
  delay: 200,
  timeout: 3000
})

export default {
  name: 'LazyComponent',
  components: {
    AsyncComponent
  },
  setup() {
    const shouldLoad = ref(false)

    onMounted(() => {
      // 延迟加载或基于用户交互加载
      setTimeout(() => {
        shouldLoad.value = true
      }, 1000)
    })

    return {
      shouldLoad
    }
  }
}
</script>

6.2.3 智能预加载

// utils/preloader.js
class IntelligentPreloader {
  constructor() {
    this.preloadedRoutes = new Set()
    this.observer = null
    this.init()
  }

  init() {
    // 创建Intersection Observer
    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const route = entry.target.dataset.preload
            if (route && !this.preloadedRoutes.has(route)) {
              this.preloadRoute(route)
            }
          }
        })
      },
      { threshold: 0.1 }
    )
  }

  // 预加载路由
  async preloadRoute(routeName) {
    try {
      this.preloadedRoutes.add(routeName)
      
      // 动态导入路由组件
      const routeMap = {
        'products': () => import('../views/Products.vue'),
        'about': () => import('../views/About.vue'),
        'contact': () => import('../views/Contact.vue')
      }

      if (routeMap[routeName]) {
        await routeMap[routeName]()
        console.log(`预加载完成: ${routeName}`)
      }
    } catch (error) {
      console.error(`预加载失败: ${routeName}`, error)
    }
  }

  // 观察链接元素
  observeLinks() {
    const links = document.querySelectorAll('[data-preload]')
    links.forEach(link => this.observer.observe(link))
  }

  // 基于用户行为预测
  predictivePreload() {
    // 鼠标悬停预加载
    document.addEventListener('mouseover', (e) => {
      const link = e.target.closest('a[data-preload]')
      if (link) {
        const route = link.dataset.preload
        setTimeout(() => this.preloadRoute(route), 100)
      }
    })
  }
}

export default new IntelligentPreloader()

6.3 资源优化

6.3.1 图片优化

// utils/image-optimizer.js
class ImageOptimizer {
  constructor() {
    this.supportedFormats = this.detectSupportedFormats()
  }

  // 检测浏览器支持的图片格式
  detectSupportedFormats() {
    const canvas = document.createElement('canvas')
    canvas.width = 1
    canvas.height = 1
    
    return {
      webp: canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0,
      avif: canvas.toDataURL('image/avif').indexOf('data:image/avif') === 0
    }
  }

  // 获取最优图片URL
  getOptimizedImageUrl(baseUrl, options = {}) {
    const {
      width = 800,
      height = 600,
      quality = 80,
      format = 'auto'
    } = options

    let optimalFormat = format
    if (format === 'auto') {
      if (this.supportedFormats.avif) {
        optimalFormat = 'avif'
      } else if (this.supportedFormats.webp) {
        optimalFormat = 'webp'
      } else {
        optimalFormat = 'jpg'
      }
    }

    return `${baseUrl}?w=${width}&h=${height}&q=${quality}&f=${optimalFormat}`
  }

  // 响应式图片
  generateResponsiveImageSet(baseUrl, sizes = [400, 800, 1200, 1600]) {
    return sizes.map(size => ({
      url: this.getOptimizedImageUrl(baseUrl, { width: size }),
      width: size
    }))
  }
}

export default new ImageOptimizer()

6.3.2 字体优化

/* styles/fonts.css */
/* 字体预加载 */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-font.woff2') format('woff2'),
       url('/fonts/custom-font.woff') format('woff');
  font-display: swap; /* 字体交换策略 */
  font-weight: 400;
  font-style: normal;
}

/* 字体子集化 */
@font-face {
  font-family: 'CustomFont-Chinese';
  src: url('/fonts/custom-font-chinese.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+4E00-9FFF; /* 中文字符范围 */
}

/* 字体加载优化 */
.font-loading {
  font-family: system-ui, -apple-system, sans-serif;
}

.font-loaded {
  font-family: 'CustomFont', system-ui, -apple-system, sans-serif;
}
// utils/font-loader.js
class FontLoader {
  constructor() {
    this.loadedFonts = new Set()
  }

  // 预加载字体
  async preloadFont(fontFamily, fontUrl) {
    if (this.loadedFonts.has(fontFamily)) {
      return Promise.resolve()
    }

    return new Promise((resolve, reject) => {
      const font = new FontFace(fontFamily, `url(${fontUrl})`)
      
      font.load().then(() => {
        document.fonts.add(font)
        this.loadedFonts.add(fontFamily)
        document.body.classList.add('font-loaded')
        resolve()
      }).catch(reject)
    })
  }

  // 字体加载状态检测
  checkFontLoadStatus() {
    if (document.fonts && document.fonts.ready) {
      document.fonts.ready.then(() => {
        console.log('所有字体加载完成')
        document.body.classList.add('fonts-ready')
      })
    }
  }
}

export default new FontLoader()

6.3.3 资源压缩与合并

// build/webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      // JS压缩
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true,
            pure_funcs: ['console.log']
          },
          mangle: {
            safari10: true
          }
        },
        parallel: true
      }),
      // CSS压缩
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true },
              normalizeWhitespace: true
            }
          ]
        }
      })
    ],
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5
        }
      }
    }
  },
  plugins: [
    // Gzip压缩
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8
    }),
    // Brotli压缩
    new CompressionPlugin({
      filename: '[path][base].br',
      algorithm: 'brotliCompress',
      test: /\.(js|css|html|svg)$/,
      compressionOptions: {
        level: 11
      },
      threshold: 8192,
      minRatio: 0.8
    })
  ]
}

6.4 服务端优化

6.4.1 渲染性能优化

// server/renderer.js
const { createBundleRenderer } = require('vue-server-renderer')
const cluster = require('cluster')
const numCPUs = require('os').cpus().length

class OptimizedRenderer {
  constructor() {
    this.renderers = new Map()
    this.renderQueue = []
    this.isProcessing = false
  }

  // 创建渲染器池
  createRendererPool(serverBundle, options) {
    const poolSize = Math.min(numCPUs, 4)
    
    for (let i = 0; i < poolSize; i++) {
      const renderer = createBundleRenderer(serverBundle, {
        ...options,
        runInNewContext: false, // 性能优化
        shouldPrefetch: (file, type) => {
          // 智能预取策略
          if (type === 'script') {
            return !file.includes('chunk')
          }
          return true
        }
      })
      
      this.renderers.set(i, {
        renderer,
        busy: false,
        lastUsed: Date.now()
      })
    }
  }

  // 获取可用渲染器
  getAvailableRenderer() {
    for (const [id, rendererInfo] of this.renderers) {
      if (!rendererInfo.busy) {
        rendererInfo.busy = true
        rendererInfo.lastUsed = Date.now()
        return { id, renderer: rendererInfo.renderer }
      }
    }
    return null
  }

  // 释放渲染器
  releaseRenderer(id) {
    const rendererInfo = this.renderers.get(id)
    if (rendererInfo) {
      rendererInfo.busy = false
    }
  }

  // 渲染页面
  async renderPage(context) {
    return new Promise((resolve, reject) => {
      const availableRenderer = this.getAvailableRenderer()
      
      if (!availableRenderer) {
        // 加入队列等待
        this.renderQueue.push({ context, resolve, reject })
        this.processQueue()
        return
      }

      const { id, renderer } = availableRenderer
      
      renderer.renderToString(context, (err, html) => {
        this.releaseRenderer(id)
        
        if (err) {
          reject(err)
        } else {
          resolve(html)
        }
        
        // 处理队列中的下一个请求
        this.processQueue()
      })
    })
  }

  // 处理渲染队列
  processQueue() {
    if (this.isProcessing || this.renderQueue.length === 0) {
      return
    }

    this.isProcessing = true
    
    const availableRenderer = this.getAvailableRenderer()
    if (availableRenderer) {
      const { context, resolve, reject } = this.renderQueue.shift()
      const { id, renderer } = availableRenderer
      
      renderer.renderToString(context, (err, html) => {
        this.releaseRenderer(id)
        
        if (err) {
          reject(err)
        } else {
          resolve(html)
        }
        
        this.isProcessing = false
        this.processQueue()
      })
    } else {
      this.isProcessing = false
    }
  }
}

module.exports = OptimizedRenderer

6.4.2 内存管理

// server/memory-manager.js
class MemoryManager {
  constructor() {
    this.memoryThreshold = 0.8 // 80%内存使用率阈值
    this.gcInterval = 30000 // 30秒GC间隔
    this.startMonitoring()
  }

  // 开始内存监控
  startMonitoring() {
    setInterval(() => {
      this.checkMemoryUsage()
    }, this.gcInterval)
  }

  // 检查内存使用情况
  checkMemoryUsage() {
    const memUsage = process.memoryUsage()
    const totalMemory = require('os').totalmem()
    const usedMemory = memUsage.heapUsed
    const memoryUsageRatio = usedMemory / totalMemory

    console.log('内存使用情况:', {
      heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',
      heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB',
      external: Math.round(memUsage.external / 1024 / 1024) + 'MB',
      rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',
      usageRatio: Math.round(memoryUsageRatio * 100) + '%'
    })

    // 内存使用率过高时触发GC
    if (memoryUsageRatio > this.memoryThreshold) {
      console.log('内存使用率过高,触发垃圾回收')
      this.forceGC()
    }
  }

  // 强制垃圾回收
  forceGC() {
    if (global.gc) {
      global.gc()
      console.log('垃圾回收完成')
    } else {
      console.warn('垃圾回收不可用,请使用 --expose-gc 启动参数')
    }
  }

  // 内存泄漏检测
  detectMemoryLeaks() {
    const heapSnapshot = require('v8').writeHeapSnapshot()
    console.log('堆快照已保存:', heapSnapshot)
  }
}

module.exports = new MemoryManager()

6.4.3 集群部署

// server/cluster.js
const cluster = require('cluster')
const numCPUs = require('os').cpus().length
const path = require('path')

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`)

  // 创建工作进程
  const workers = []
  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork()
    workers.push(worker)
    
    worker.on('message', (msg) => {
      if (msg.type === 'memory-warning') {
        console.log(`工作进程 ${worker.process.pid} 内存警告:`, msg.data)
      }
    })
  }

  // 工作进程重启策略
  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`)
    
    if (code !== 0 && !worker.exitedAfterDisconnect) {
      console.log('启动新的工作进程...')
      const newWorker = cluster.fork()
      workers.push(newWorker)
    }
  })

  // 优雅关闭
  process.on('SIGTERM', () => {
    console.log('收到SIGTERM信号,开始优雅关闭...')
    
    workers.forEach(worker => {
      worker.disconnect()
      
      setTimeout(() => {
        if (!worker.isDead()) {
          worker.kill('SIGKILL')
        }
      }, 10000) // 10秒超时
    })
  })

} else {
  // 工作进程
  const app = require('./app')
  const port = process.env.PORT || 3000
  
  const server = app.listen(port, () => {
    console.log(`工作进程 ${process.pid} 在端口 ${port} 上运行`)
  })

  // 优雅关闭工作进程
  process.on('SIGTERM', () => {
    console.log(`工作进程 ${process.pid} 收到SIGTERM信号`)
    
    server.close(() => {
      console.log(`工作进程 ${process.pid} 已关闭`)
      process.exit(0)
    })
  })

  // 内存监控
  setInterval(() => {
    const memUsage = process.memoryUsage()
    const memoryUsageRatio = memUsage.heapUsed / memUsage.heapTotal
    
    if (memoryUsageRatio > 0.9) {
      process.send({
        type: 'memory-warning',
        data: {
          pid: process.pid,
          memUsage,
          ratio: memoryUsageRatio
        }
      })
    }
  }, 30000)
}

6.5 监控与分析

6.5.1 性能监控

// utils/performance-monitor.js
class PerformanceMonitor {
  constructor() {
    this.metrics = {
      renderTime: [],
      memoryUsage: [],
      requestCount: 0,
      errorCount: 0
    }
    this.startTime = Date.now()
  }

  // 记录渲染时间
  recordRenderTime(startTime, endTime) {
    const renderTime = endTime - startTime
    this.metrics.renderTime.push(renderTime)
    
    // 保持最近1000条记录
    if (this.metrics.renderTime.length > 1000) {
      this.metrics.renderTime.shift()
    }
  }

  // 记录内存使用
  recordMemoryUsage() {
    const memUsage = process.memoryUsage()
    this.metrics.memoryUsage.push({
      timestamp: Date.now(),
      heapUsed: memUsage.heapUsed,
      heapTotal: memUsage.heapTotal,
      external: memUsage.external,
      rss: memUsage.rss
    })
    
    // 保持最近100条记录
    if (this.metrics.memoryUsage.length > 100) {
      this.metrics.memoryUsage.shift()
    }
  }

  // 增加请求计数
  incrementRequestCount() {
    this.metrics.requestCount++
  }

  // 增加错误计数
  incrementErrorCount() {
    this.metrics.errorCount++
  }

  // 获取性能统计
  getStats() {
    const renderTimes = this.metrics.renderTime
    const avgRenderTime = renderTimes.length > 0 
      ? renderTimes.reduce((a, b) => a + b, 0) / renderTimes.length 
      : 0
    
    const maxRenderTime = renderTimes.length > 0 
      ? Math.max(...renderTimes) 
      : 0
    
    const uptime = Date.now() - this.startTime
    const requestsPerSecond = this.metrics.requestCount / (uptime / 1000)
    
    return {
      uptime,
      requestCount: this.metrics.requestCount,
      errorCount: this.metrics.errorCount,
      errorRate: this.metrics.requestCount > 0 
        ? this.metrics.errorCount / this.metrics.requestCount 
        : 0,
      avgRenderTime,
      maxRenderTime,
      requestsPerSecond,
      memoryUsage: this.metrics.memoryUsage.slice(-1)[0] || null
    }
  }

  // 导出性能报告
  exportReport() {
    const stats = this.getStats()
    const report = {
      timestamp: new Date().toISOString(),
      stats,
      recommendations: this.generateRecommendations(stats)
    }
    
    return report
  }

  // 生成优化建议
  generateRecommendations(stats) {
    const recommendations = []
    
    if (stats.avgRenderTime > 100) {
      recommendations.push('平均渲染时间过长,建议启用组件缓存')
    }
    
    if (stats.errorRate > 0.01) {
      recommendations.push('错误率过高,请检查错误日志')
    }
    
    if (stats.memoryUsage && stats.memoryUsage.heapUsed > 500 * 1024 * 1024) {
      recommendations.push('内存使用过高,建议检查内存泄漏')
    }
    
    return recommendations
  }
}

module.exports = new PerformanceMonitor()

6.5.2 错误追踪

// utils/error-tracker.js
class ErrorTracker {
  constructor() {
    this.errors = []
    this.errorCounts = new Map()
  }

  // 记录错误
  trackError(error, context = {}) {
    const errorInfo = {
      timestamp: new Date().toISOString(),
      message: error.message,
      stack: error.stack,
      context,
      userAgent: context.userAgent || '',
      url: context.url || '',
      userId: context.userId || null
    }
    
    this.errors.push(errorInfo)
    
    // 错误计数
    const errorKey = `${error.message}:${context.url}`
    const count = this.errorCounts.get(errorKey) || 0
    this.errorCounts.set(errorKey, count + 1)
    
    // 保持最近1000条错误记录
    if (this.errors.length > 1000) {
      this.errors.shift()
    }
    
    // 发送到外部监控服务
    this.sendToMonitoringService(errorInfo)
  }

  // 发送到监控服务
  async sendToMonitoringService(errorInfo) {
    try {
      // 这里可以集成Sentry、Bugsnag等服务
      if (process.env.SENTRY_DSN) {
        // await Sentry.captureException(errorInfo)
      }
      
      // 或者发送到自定义API
      if (process.env.ERROR_TRACKING_API) {
        await fetch(process.env.ERROR_TRACKING_API, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(errorInfo)
        })
      }
    } catch (err) {
      console.error('发送错误信息失败:', err)
    }
  }

  // 获取错误统计
  getErrorStats() {
    const now = Date.now()
    const oneHourAgo = now - 60 * 60 * 1000
    const oneDayAgo = now - 24 * 60 * 60 * 1000
    
    const recentErrors = this.errors.filter(error => 
      new Date(error.timestamp).getTime() > oneHourAgo
    )
    
    const dailyErrors = this.errors.filter(error => 
      new Date(error.timestamp).getTime() > oneDayAgo
    )
    
    return {
      totalErrors: this.errors.length,
      recentErrors: recentErrors.length,
      dailyErrors: dailyErrors.length,
      topErrors: Array.from(this.errorCounts.entries())
        .sort((a, b) => b[1] - a[1])
        .slice(0, 10)
        .map(([error, count]) => ({ error, count }))
    }
  }
}

module.exports = new ErrorTracker()

本章小结

在本章中,我们深入学习了Vue SSR的性能优化技巧:

核心要点

  1. 缓存策略

    • 组件级缓存提升渲染性能
    • 页面级缓存减少重复渲染
    • CDN缓存优化资源加载
  2. 代码分割与懒加载

    • 路由级代码分割减少初始包大小
    • 组件级懒加载按需加载资源
    • 智能预加载提升用户体验
  3. 资源优化

    • 图片格式优化和响应式加载
    • 字体加载优化和子集化
    • 资源压缩和合并策略
  4. 服务端优化

    • 渲染器池提升并发处理能力
    • 内存管理防止内存泄漏
    • 集群部署提升系统可用性
  5. 监控与分析

    • 性能指标监控和分析
    • 错误追踪和报告
    • 优化建议生成

最佳实践

  • 合理使用缓存,避免过度缓存
  • 按需加载,避免一次性加载过多资源
  • 监控关键性能指标,及时发现问题
  • 定期进行性能测试和优化

下一章预告

下一章我们将学习测试与调试,包括单元测试、集成测试、端到端测试以及调试技巧,确保Vue SSR应用的质量和稳定性。


练习作业

  1. 实现一个智能缓存系统,支持多级缓存和自动失效
  2. 优化一个现有的Vue SSR应用,提升首屏加载速度
  3. 搭建性能监控系统,实时监控应用性能指标
  4. 实现资源预加载策略,提升用户体验
  5. 配置集群部署,提升应用的并发处理能力