常见问题诊断

水合错误(Hydration Mismatch)

问题表现

// 控制台错误信息
[Vue warn]: Hydration node mismatch:
- Client vnode: div
- Server rendered DOM: span

// 或者
[Vue warn]: Hydration text content mismatch in <div>:
- Client: "Hello World"
- Server: "你好世界"

解决方案

<!-- 错误示例:客户端和服务端渲染不一致 -->
<template>
  <div>
    <!-- 时间戳会导致水合错误 -->
    <span>{{ new Date().toISOString() }}</span>
    
    <!-- 随机数会导致水合错误 -->
    <div>{{ Math.random() }}</div>
    
    <!-- 浏览器特定API会导致错误 -->
    <p v-if="window.innerWidth > 768">桌面版本</p>
  </div>
</template>

<!-- 正确示例:确保一致性 -->
<template>
  <div>
    <!-- 使用客户端渲染包装器 -->
    <ClientOnly>
      <span>{{ currentTime }}</span>
      <template #fallback>
        <span>加载中...</span>
      </template>
    </ClientOnly>
    
    <!-- 使用响应式数据 -->
    <div v-if="isMounted && isDesktop">桌面版本</div>
  </div>
</template>

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

const currentTime = ref('')
const isMounted = ref(false)
const isDesktop = ref(false)

onMounted(() => {
  isMounted.value = true
  isDesktop.value = window.innerWidth > 768
  currentTime.value = new Date().toISOString()
})
</script>

ClientOnly组件实现

<!-- components/ClientOnly.vue -->
<template>
  <div v-if="isMounted">
    <slot />
  </div>
  <div v-else>
    <slot name="fallback" />
  </div>
</template>

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

const isMounted = ref(false)

onMounted(() => {
  isMounted.value = true
})
</script>

内存泄漏问题

检测工具

// server/monitoring/memory.js
import v8 from 'v8'
import { performance } from 'perf_hooks'

class MemoryMonitor {
  constructor() {
    this.snapshots = []
    this.interval = null
  }
  
  start(intervalMs = 30000) {
    this.interval = setInterval(() => {
      this.takeSnapshot()
    }, intervalMs)
  }
  
  stop() {
    if (this.interval) {
      clearInterval(this.interval)
      this.interval = null
    }
  }
  
  takeSnapshot() {
    const memUsage = process.memoryUsage()
    const heapStats = v8.getHeapStatistics()
    
    const snapshot = {
      timestamp: Date.now(),
      rss: memUsage.rss,
      heapUsed: memUsage.heapUsed,
      heapTotal: memUsage.heapTotal,
      external: memUsage.external,
      heapSizeLimit: heapStats.heap_size_limit,
      totalHeapSize: heapStats.total_heap_size,
      usedHeapSize: heapStats.used_heap_size
    }
    
    this.snapshots.push(snapshot)
    
    // 保留最近100个快照
    if (this.snapshots.length > 100) {
      this.snapshots.shift()
    }
    
    // 检查内存泄漏
    this.checkMemoryLeak(snapshot)
  }
  
  checkMemoryLeak(current) {
    if (this.snapshots.length < 10) return
    
    const recent = this.snapshots.slice(-10)
    const trend = this.calculateTrend(recent.map(s => s.heapUsed))
    
    // 如果内存使用持续增长
    if (trend > 1024 * 1024) { // 1MB增长趋势
      console.warn('检测到可能的内存泄漏:', {
        trend: `${(trend / 1024 / 1024).toFixed(2)}MB`,
        currentHeap: `${(current.heapUsed / 1024 / 1024).toFixed(2)}MB`,
        heapLimit: `${(current.heapSizeLimit / 1024 / 1024).toFixed(2)}MB`
      })
      
      // 可以在这里触发告警或自动重启
      this.handleMemoryLeak(current)
    }
  }
  
  calculateTrend(values) {
    if (values.length < 2) return 0
    
    const n = values.length
    const sumX = (n * (n - 1)) / 2
    const sumY = values.reduce((a, b) => a + b, 0)
    const sumXY = values.reduce((sum, y, x) => sum + x * y, 0)
    const sumX2 = values.reduce((sum, _, x) => sum + x * x, 0)
    
    return (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
  }
  
  handleMemoryLeak(snapshot) {
    // 生成堆快照
    if (process.env.NODE_ENV === 'development') {
      const heapSnapshot = v8.writeHeapSnapshot()
      console.log('堆快照已保存:', heapSnapshot)
    }
    
    // 强制垃圾回收
    if (global.gc) {
      global.gc()
    }
  }
  
  getStats() {
    if (this.snapshots.length === 0) return null
    
    const latest = this.snapshots[this.snapshots.length - 1]
    const oldest = this.snapshots[0]
    
    return {
      current: {
        rss: `${(latest.rss / 1024 / 1024).toFixed(2)}MB`,
        heapUsed: `${(latest.heapUsed / 1024 / 1024).toFixed(2)}MB`,
        heapTotal: `${(latest.heapTotal / 1024 / 1024).toFixed(2)}MB`
      },
      trend: {
        rss: latest.rss - oldest.rss,
        heapUsed: latest.heapUsed - oldest.heapUsed,
        heapTotal: latest.heapTotal - oldest.heapTotal
      },
      snapshots: this.snapshots.length
    }
  }
}

export const memoryMonitor = new MemoryMonitor()

// 在应用启动时开始监控
if (process.env.NODE_ENV === 'production') {
  memoryMonitor.start()
}

性能问题诊断

渲染性能分析

// utils/performance.js
import { performance } from 'perf_hooks'

class PerformanceProfiler {
  constructor() {
    this.marks = new Map()
    this.measures = []
  }
  
  mark(name) {
    const markName = `${name}-${Date.now()}`
    performance.mark(markName)
    this.marks.set(name, markName)
    return markName
  }
  
  measure(name, startMark) {
    const endMark = this.mark(`${name}-end`)
    const startMarkName = this.marks.get(startMark) || startMark
    
    try {
      performance.measure(name, startMarkName, endMark)
      const measure = performance.getEntriesByName(name, 'measure')[0]
      
      this.measures.push({
        name,
        duration: measure.duration,
        timestamp: Date.now()
      })
      
      return measure.duration
    } catch (error) {
      console.warn('性能测量失败:', error.message)
      return 0
    }
  }
  
  getReport() {
    const report = {
      totalMeasures: this.measures.length,
      averages: {},
      slowest: {},
      recent: this.measures.slice(-10)
    }
    
    // 按名称分组计算平均值
    const grouped = this.measures.reduce((acc, measure) => {
      if (!acc[measure.name]) {
        acc[measure.name] = []
      }
      acc[measure.name].push(measure.duration)
      return acc
    }, {})
    
    Object.entries(grouped).forEach(([name, durations]) => {
      const avg = durations.reduce((a, b) => a + b, 0) / durations.length
      const max = Math.max(...durations)
      
      report.averages[name] = `${avg.toFixed(2)}ms`
      report.slowest[name] = `${max.toFixed(2)}ms`
    })
    
    return report
  }
  
  clear() {
    this.marks.clear()
    this.measures = []
    performance.clearMarks()
    performance.clearMeasures()
  }
}

export const profiler = new PerformanceProfiler()

// SSR性能中间件
export function ssrPerformanceMiddleware(req, res, next) {
  const requestId = `request-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  
  profiler.mark(`${requestId}-start`)
  
  // 记录各个阶段
  const originalRender = res.render
  res.render = function(view, locals, callback) {
    profiler.mark(`${requestId}-render-start`)
    
    const renderCallback = (err, html) => {
      profiler.measure(`${requestId}-render`, `${requestId}-render-start`)
      
      if (callback) {
        callback(err, html)
      } else if (err) {
        throw err
      } else {
        res.send(html)
      }
    }
    
    originalRender.call(this, view, locals, renderCallback)
  }
  
  res.on('finish', () => {
    const totalDuration = profiler.measure(`${requestId}-total`, `${requestId}-start`)
    
    // 记录慢请求
    if (totalDuration > 1000) { // 超过1秒
      console.warn('慢请求检测:', {
        url: req.url,
        method: req.method,
        duration: `${totalDuration.toFixed(2)}ms`,
        userAgent: req.get('User-Agent')
      })
    }
  })
  
  next()
}

调试工具配置

Vue DevTools配置

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 开发环境启用Vue DevTools
if (process.env.NODE_ENV === 'development') {
  app.config.devtools = true
  
  // 启用性能追踪
  app.config.performance = true
  
  // 自定义DevTools配置
  if (typeof window !== 'undefined') {
    window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.__VUE_DEVTOOLS_GLOBAL_HOOK__ || {}
    window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = app
  }
}

export { app }

服务端调试配置

// server/debug.js
import debug from 'debug'
import util from 'util'

// 创建不同模块的调试器
export const debugSSR = debug('app:ssr')
export const debugRoute = debug('app:route')
export const debugCache = debug('app:cache')
export const debugDB = debug('app:db')
export const debugAuth = debug('app:auth')

// 格式化调试输出
function formatDebugOutput(namespace, data) {
  const timestamp = new Date().toISOString()
  const formatted = typeof data === 'object' 
    ? util.inspect(data, { colors: true, depth: 3 })
    : data
  
  return `[${timestamp}] ${namespace}: ${formatted}`
}

// 增强的调试函数
export function createDebugger(namespace) {
  const debugFn = debug(namespace)
  
  return {
    log: (data) => debugFn(formatDebugOutput(namespace, data)),
    error: (error) => {
      debugFn(formatDebugOutput(namespace, {
        message: error.message,
        stack: error.stack,
        timestamp: Date.now()
      }))
    },
    time: (label) => {
      const start = Date.now()
      return () => {
        const duration = Date.now() - start
        debugFn(formatDebugOutput(namespace, `${label}: ${duration}ms`))
      }
    }
  }
}

// 使用示例
const debug = createDebugger('app:example')

// 计时调试
const endTimer = debug.time('数据库查询')
// ... 执行数据库查询
endTimer() // 输出: 数据库查询: 150ms

错误追踪配置

// server/error-tracking.js
import * as Sentry from '@sentry/node'
import { createDebugger } from './debug.js'

const debug = createDebugger('app:error')

class ErrorTracker {
  constructor() {
    this.errors = []
    this.maxErrors = 100
  }
  
  track(error, context = {}) {
    const errorInfo = {
      id: this.generateErrorId(),
      message: error.message,
      stack: error.stack,
      timestamp: Date.now(),
      context,
      severity: this.determineSeverity(error)
    }
    
    // 添加到本地错误列表
    this.errors.unshift(errorInfo)
    if (this.errors.length > this.maxErrors) {
      this.errors.pop()
    }
    
    // 发送到Sentry
    if (Sentry.getCurrentHub().getClient()) {
      Sentry.withScope((scope) => {
        scope.setContext('error_context', context)
        scope.setLevel(errorInfo.severity)
        Sentry.captureException(error)
      })
    }
    
    // 本地调试输出
    debug.error(errorInfo)
    
    return errorInfo.id
  }
  
  generateErrorId() {
    return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  }
  
  determineSeverity(error) {
    if (error.name === 'ValidationError') return 'warning'
    if (error.status >= 400 && error.status < 500) return 'warning'
    if (error.status >= 500) return 'error'
    if (error.name === 'TypeError' || error.name === 'ReferenceError') return 'error'
    return 'info'
  }
  
  getRecentErrors(limit = 10) {
    return this.errors.slice(0, limit)
  }
  
  getErrorById(id) {
    return this.errors.find(error => error.id === id)
  }
  
  getErrorStats() {
    const now = Date.now()
    const oneHour = 60 * 60 * 1000
    const oneDay = 24 * oneHour
    
    const recentErrors = this.errors.filter(error => 
      now - error.timestamp < oneHour
    )
    
    const dailyErrors = this.errors.filter(error => 
      now - error.timestamp < oneDay
    )
    
    const severityCount = this.errors.reduce((acc, error) => {
      acc[error.severity] = (acc[error.severity] || 0) + 1
      return acc
    }, {})
    
    return {
      total: this.errors.length,
      lastHour: recentErrors.length,
      lastDay: dailyErrors.length,
      bySeverity: severityCount
    }
  }
}

export const errorTracker = new ErrorTracker()

// 全局错误处理中间件
export function errorHandlingMiddleware(err, req, res, next) {
  const errorId = errorTracker.track(err, {
    url: req.url,
    method: req.method,
    userAgent: req.get('User-Agent'),
    ip: req.ip,
    userId: req.user?.id
  })
  
  // 根据环境返回不同的错误信息
  if (process.env.NODE_ENV === 'production') {
    res.status(err.status || 500).json({
      error: '服务器内部错误',
      errorId,
      timestamp: Date.now()
    })
  } else {
    res.status(err.status || 500).json({
      error: err.message,
      stack: err.stack,
      errorId,
      timestamp: Date.now()
    })
  }
}

// 未捕获异常处理
process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error)
  errorTracker.track(error, { type: 'uncaughtException' })
  
  // 优雅关闭
  process.exit(1)
})

process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的Promise拒绝:', reason)
  errorTracker.track(new Error(reason), { 
    type: 'unhandledRejection',
    promise: promise.toString()
  })
})

日志系统

结构化日志配置

// server/logging/index.js
import winston from 'winston'
import DailyRotateFile from 'winston-daily-rotate-file'
import path from 'path'
import config from '../config/index.js'

// 自定义日志格式
const logFormat = winston.format.combine(
  winston.format.timestamp({
    format: 'YYYY-MM-DD HH:mm:ss.SSS'
  }),
  winston.format.errors({ stack: true }),
  winston.format.json(),
  winston.format.printf(({ timestamp, level, message, ...meta }) => {
    return JSON.stringify({
      timestamp,
      level,
      message,
      ...meta
    })
  })
)

// 创建日志传输器
const transports = [
  // 控制台输出
  new winston.transports.Console({
    format: config.isDevelopment 
      ? winston.format.combine(
          winston.format.colorize(),
          winston.format.simple()
        )
      : logFormat
  }),
  
  // 错误日志文件
  new DailyRotateFile({
    filename: path.join('logs', 'error-%DATE%.log'),
    datePattern: 'YYYY-MM-DD',
    level: 'error',
    format: logFormat,
    maxSize: '20m',
    maxFiles: '14d',
    zippedArchive: true
  }),
  
  // 组合日志文件
  new DailyRotateFile({
    filename: path.join('logs', 'combined-%DATE%.log'),
    datePattern: 'YYYY-MM-DD',
    format: logFormat,
    maxSize: '20m',
    maxFiles: '7d',
    zippedArchive: true
  })
]

// 创建logger实例
const logger = winston.createLogger({
  level: config.logging.level,
  format: logFormat,
  transports,
  exitOnError: false
})

// 请求日志中间件
export function requestLoggingMiddleware(req, res, next) {
  const start = Date.now()
  const requestId = req.headers['x-request-id'] || 
    `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  
  // 添加请求ID到请求对象
  req.requestId = requestId
  
  // 记录请求开始
  logger.info('请求开始', {
    requestId,
    method: req.method,
    url: req.url,
    userAgent: req.get('User-Agent'),
    ip: req.ip,
    referer: req.get('Referer')
  })
  
  // 监听响应结束
  res.on('finish', () => {
    const duration = Date.now() - start
    
    logger.info('请求完成', {
      requestId,
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      contentLength: res.get('Content-Length')
    })
  })
  
  next()
}

// 增强的logger方法
class EnhancedLogger {
  constructor(baseLogger) {
    this.logger = baseLogger
  }
  
  info(message, meta = {}) {
    this.logger.info(message, this.enrichMeta(meta))
  }
  
  warn(message, meta = {}) {
    this.logger.warn(message, this.enrichMeta(meta))
  }
  
  error(message, error = null, meta = {}) {
    const errorMeta = error ? {
      error: {
        name: error.name,
        message: error.message,
        stack: error.stack
      }
    } : {}
    
    this.logger.error(message, this.enrichMeta({ ...meta, ...errorMeta }))
  }
  
  debug(message, meta = {}) {
    this.logger.debug(message, this.enrichMeta(meta))
  }
  
  enrichMeta(meta) {
    return {
      ...meta,
      pid: process.pid,
      memory: process.memoryUsage(),
      uptime: process.uptime()
    }
  }
  
  // SSR特定的日志方法
  ssrRender(component, duration, meta = {}) {
    this.info('SSR渲染完成', {
      component,
      duration: `${duration}ms`,
      ...meta
    })
  }
  
  cacheHit(key, meta = {}) {
    this.debug('缓存命中', {
      cacheKey: key,
      ...meta
    })
  }
  
  cacheMiss(key, meta = {}) {
    this.debug('缓存未命中', {
      cacheKey: key,
      ...meta
    })
  }
  
  apiCall(url, method, duration, statusCode, meta = {}) {
    this.info('API调用', {
      url,
      method,
      duration: `${duration}ms`,
      statusCode,
      ...meta
    })
  }
}

export const enhancedLogger = new EnhancedLogger(logger)
export { logger }
export default enhancedLogger

测试和验证

SSR测试工具

// tests/ssr-test-utils.js
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
import { createMemoryHistory, createRouter } from 'vue-router'
import { createPinia } from 'pinia'
import routes from '../src/router/routes.js'

export class SSRTestHelper {
  constructor() {
    this.app = null
    this.router = null
    this.pinia = null
  }
  
  async createApp(initialRoute = '/') {
    // 创建应用实例
    this.app = createSSRApp({})
    
    // 创建路由
    this.router = createRouter({
      history: createMemoryHistory(),
      routes
    })
    
    // 创建状态管理
    this.pinia = createPinia()
    
    // 安装插件
    this.app.use(this.router)
    this.app.use(this.pinia)
    
    // 导航到初始路由
    await this.router.push(initialRoute)
    await this.router.isReady()
    
    return this.app
  }
  
  async renderToString(initialRoute = '/') {
    const app = await this.createApp(initialRoute)
    return await renderToString(app)
  }
  
  async testRoute(route, expectedContent) {
    const html = await this.renderToString(route)
    
    const tests = {
      hasContent: html.includes(expectedContent),
      hasValidHTML: this.validateHTML(html),
      hasNoErrors: !html.includes('error'),
      hasMetaTags: html.includes('<meta'),
      hasTitle: html.includes('<title>')
    }
    
    return {
      html,
      tests,
      passed: Object.values(tests).every(Boolean)
    }
  }
  
  validateHTML(html) {
    // 基础HTML验证
    const hasDoctype = html.startsWith('<!DOCTYPE html>')
    const hasHtmlTag = html.includes('<html')
    const hasHeadTag = html.includes('<head>')
    const hasBodyTag = html.includes('<body>')
    const isWellFormed = this.checkWellFormed(html)
    
    return hasDoctype && hasHtmlTag && hasHeadTag && hasBodyTag && isWellFormed
  }
  
  checkWellFormed(html) {
    // 简单的标签匹配检查
    const openTags = html.match(/<[^/][^>]*>/g) || []
    const closeTags = html.match(/<\/[^>]*>/g) || []
    
    // 自闭合标签
    const selfClosing = ['img', 'br', 'hr', 'input', 'meta', 'link']
    const filteredOpenTags = openTags.filter(tag => {
      const tagName = tag.match(/<([^\s>]+)/)?.[1]?.toLowerCase()
      return !selfClosing.includes(tagName) && !tag.endsWith('/>')
    })
    
    return Math.abs(filteredOpenTags.length - closeTags.length) <= 2 // 允许小误差
  }
  
  async testHydration(route) {
    const serverHTML = await this.renderToString(route)
    
    // 模拟客户端渲染
    const clientApp = await this.createApp(route)
    const clientHTML = await renderToString(clientApp)
    
    return {
      serverHTML,
      clientHTML,
      matches: serverHTML === clientHTML,
      diff: this.findDifferences(serverHTML, clientHTML)
    }
  }
  
  findDifferences(str1, str2) {
    const differences = []
    const maxLength = Math.max(str1.length, str2.length)
    
    for (let i = 0; i < maxLength; i++) {
      if (str1[i] !== str2[i]) {
        const context = {
          position: i,
          server: str1.substring(Math.max(0, i - 20), i + 20),
          client: str2.substring(Math.max(0, i - 20), i + 20)
        }
        differences.push(context)
        
        // 只记录前5个差异
        if (differences.length >= 5) break
      }
    }
    
    return differences
  }
}

// 测试用例示例
export async function runSSRTests() {
  const helper = new SSRTestHelper()
  const results = []
  
  const testCases = [
    { route: '/', expectedContent: '首页' },
    { route: '/about', expectedContent: '关于我们' },
    { route: '/posts', expectedContent: '文章列表' },
    { route: '/posts/1', expectedContent: '文章详情' }
  ]
  
  for (const testCase of testCases) {
    try {
      const result = await helper.testRoute(testCase.route, testCase.expectedContent)
      results.push({
        route: testCase.route,
        ...result
      })
    } catch (error) {
      results.push({
        route: testCase.route,
        error: error.message,
        passed: false
      })
    }
  }
  
  return results
}

性能基准测试

负载测试脚本

// scripts/load-test.js
import autocannon from 'autocannon'
import { performance } from 'perf_hooks'

class LoadTester {
  constructor(baseUrl = 'http://localhost:3000') {
    this.baseUrl = baseUrl
  }
  
  async runTest(options = {}) {
    const defaultOptions = {
      url: this.baseUrl,
      connections: 10,
      duration: 30,
      pipelining: 1,
      headers: {
        'User-Agent': 'LoadTest/1.0'
      }
    }
    
    const testOptions = { ...defaultOptions, ...options }
    
    console.log('开始负载测试:', testOptions)
    
    const start = performance.now()
    const result = await autocannon(testOptions)
    const duration = performance.now() - start
    
    return {
      ...result,
      testDuration: duration,
      summary: this.generateSummary(result)
    }
  }
  
  generateSummary(result) {
    return {
      totalRequests: result.requests.total,
      requestsPerSecond: result.requests.average,
      averageLatency: `${result.latency.average}ms`,
      maxLatency: `${result.latency.max}ms`,
      throughput: `${(result.throughput.average / 1024 / 1024).toFixed(2)}MB/s`,
      errors: result.errors,
      timeouts: result.timeouts,
      successRate: `${((result.requests.total - result.errors) / result.requests.total * 100).toFixed(2)}%`
    }
  }
  
  async testMultipleRoutes(routes, options = {}) {
    const results = []
    
    for (const route of routes) {
      console.log(`测试路由: ${route}`)
      
      const routeOptions = {
        ...options,
        url: `${this.baseUrl}${route}`
      }
      
      const result = await this.runTest(routeOptions)
      results.push({
        route,
        ...result
      })
      
      // 测试间隔
      await new Promise(resolve => setTimeout(resolve, 2000))
    }
    
    return results
  }
  
  async comparePerformance(routes, scenarios) {
    const comparison = []
    
    for (const scenario of scenarios) {
      console.log(`\n测试场景: ${scenario.name}`)
      
      const results = await this.testMultipleRoutes(routes, scenario.options)
      
      comparison.push({
        scenario: scenario.name,
        results,
        averageRPS: results.reduce((sum, r) => sum + r.requests.average, 0) / results.length,
        averageLatency: results.reduce((sum, r) => sum + r.latency.average, 0) / results.length
      })
    }
    
    return comparison
  }
}

// 运行测试
async function main() {
  const tester = new LoadTester()
  
  const routes = ['/', '/about', '/posts', '/posts/1']
  
  const scenarios = [
    {
      name: '低负载',
      options: { connections: 5, duration: 30 }
    },
    {
      name: '中等负载',
      options: { connections: 20, duration: 30 }
    },
    {
      name: '高负载',
      options: { connections: 50, duration: 30 }
    }
  ]
  
  try {
    const comparison = await tester.comparePerformance(routes, scenarios)
    
    console.log('\n=== 性能测试报告 ===')
    comparison.forEach(result => {
      console.log(`\n${result.scenario}:`)
      console.log(`  平均RPS: ${result.averageRPS.toFixed(2)}`)
      console.log(`  平均延迟: ${result.averageLatency.toFixed(2)}ms`)
    })
    
  } catch (error) {
    console.error('测试失败:', error)
  }
}

if (import.meta.url === `file://${process.argv[1]}`) {
  main()
}

export { LoadTester }

下一步

在下一章节中,我们将学习Vue SSR的最佳实践和进阶技巧,包括代码组织、架构设计和团队协作等内容。