Vue SSR开发踩坑

20.1 Vue SSR配置的常见错误

核心知识点

在配置Vue SSR时,常见的错误包括:

  1. 构建配置错误:服务端和客户端构建配置不当
  2. 环境变量配置:环境变量配置错误
  3. 服务器配置:Node.js服务器配置不当
  4. 依赖安装:缺少必要的依赖包

实用案例分析

错误场景:构建配置错误

// 错误示例:构建配置不当
// vue.config.js
module.exports = {
  // 错误:未配置SSR构建
  configureWebpack: {
    // 缺少SSR相关配置
  }
}

正确实现

// 正确示例:配置SSR构建
// vue.config.js
module.exports = {
  configureWebpack: {
    // 配置webpack以支持SSR
    target: 'node'
  },
  chainWebpack: config => {
    // 配置服务端构建
    if (process.env.VUE_ENV === 'server') {
      config.entry('app').clear().add('./src/entry-server.js')
      config.target('node')
      config.output.libraryTarget('commonjs2')
    } else {
      // 配置客户端构建
      config.entry('app').clear().add('./src/entry-client.js')
    }
  }
}

// 或使用@vue/cli-plugin-ssr
// 安装:npm install @vue/cli-plugin-ssr

// 项目结构
// src/
// ├── entry-client.js
// ├── entry-server.js
// └── main.js

// entry-server.js
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    const { url } = context
    const { fullPath } = router.resolve(url).route
    
    if (fullPath !== url) {
      return reject({ url: fullPath })
    }
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({ store, route: router.currentRoute })
        }
      })).then(() => {
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

// entry-client.js
import { createApp } from './main'

const { app, router, store } = createApp()

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  app.mount('#app')
})

错误场景:服务器配置不当

// 错误示例:Express服务器配置不当
const express = require('express')
const { createBundleRenderer } = require('vue-server-renderer')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')

const app = express()

// 错误:未配置静态文件服务
app.get('*', (req, res) => {
  const renderer = createBundleRenderer(serverBundle, {
    clientManifest
  })
  
  const context = { url: req.url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(html)
  })
})

app.listen(3000)

正确实现

// 正确示例:Express服务器配置
const express = require('express')
const { createBundleRenderer } = require('vue-server-renderer')
const path = require('path')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')

const app = express()

// 正确:配置静态文件服务
app.use('/dist', express.static(path.join(__dirname, 'dist')))

// 正确:创建渲染器
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template: require('fs').readFileSync(path.join(__dirname, 'index.template.html'), 'utf-8'),
  clientManifest
})

app.get('*', (req, res) => {
  const context = {
    url: req.url,
    title: 'Vue SSR App'
  }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.code === 404) {
        res.status(404).end('Page not found')
      } else {
        res.status(500).end('Internal Server Error')
      }
      return
    }
    res.end(html)
  })
})

app.listen(3000, () => {
  console.log('Server started at http://localhost:3000')
})

20.2 Vue SSR数据预取的陷阱

核心知识点

在进行Vue SSR数据预取时,常见的陷阱包括:

  1. 数据预取方法:错误的异步数据预取方法
  2. 状态管理:服务端和客户端状态不一致
  3. 错误处理:数据预取错误处理不当
  4. 性能优化:数据预取导致的性能问题

实用案例分析

错误场景:数据预取方法错误

// 错误示例:错误的异步数据预取方法
// 组件中
<template>
  <div>{{ user.name }}</div>
</template>

<script>
export default {
  data() {
    return {
      user: {}
    }
  },
  mounted() {
    // 错误:在mounted中获取数据,SSR时不会执行
    this.fetchUser()
  },
  methods: {
    async fetchUser() {
      const response = await fetch('/api/user')
      this.user = await response.json()
    }
  }
}
</script>

正确实现

// 正确示例:使用正确的异步数据预取方法

// 1. 使用asyncData方法
// 组件中
export default {
  data() {
    return {
      user: {}
    }
  },
  // 正确:使用asyncData方法,SSR时会执行
  asyncData({ store, route }) {
    return store.dispatch('fetchUser', route.params.id)
  }
}

// 2. 使用fetch方法(Nuxt.js风格)
export default {
  data() {
    return {
      user: {}
    }
  },
  async fetch() {
    const response = await this.$axios.get('/api/user')
    this.user = response.data
  }
}

// 3. 在服务端入口中处理
// entry-server.js
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    const { url } = context
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      // 预取数据
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({ store, route: router.currentRoute })
        }
      })).then(() => {
        // 将状态注入上下文
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

// 客户端入口中同步状态
// entry-client.js
const { app, router, store } = createApp()

if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

错误场景:服务端和客户端状态不一致

// 错误示例:状态管理不当
// store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    user: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
    async fetchUser({ commit }) {
      // 错误:服务端和客户端请求相同数据,导致重复请求
      const response = await fetch('/api/user')
      const user = await response.json()
      commit('setUser', user)
    }
  }
})

正确实现

// 正确示例:确保服务端和客户端状态一致

// store/index.js
import { createStore } from 'vuex'

export default function createStore() {
  return createStore({
    state: {
      user: null
    },
    mutations: {
      setUser(state, user) {
        state.user = user
      }
    },
    actions: {
      async fetchUser({ commit, state }) {
        // 正确:检查状态是否已存在,避免重复请求
        if (state.user) {
          return state.user
        }
        
        const response = await fetch('/api/user')
        const user = await response.json()
        commit('setUser', user)
        return user
      }
    }
  })
}

// main.js
import { createSSRApp } from 'vue'
import createStore from './store'

export function createApp() {
  const app = createSSRApp(App)
  const store = createStore()
  
  app.use(store)
  
  return { app, store }
}

// 客户端入口中同步状态
// entry-client.js
const { app, store } = createApp()

if (window.__INITIAL_STATE__) {
  // 正确:替换客户端状态为服务端状态
  store.replaceState(window.__INITIAL_STATE__)
}

20.3 Vue SSR路由处理的使用误区

核心知识点

在处理Vue SSR路由时,常见的误区包括:

  1. 路由配置:服务端和客户端路由配置不一致
  2. 路由导航:服务端路由导航处理不当
  3. 404处理:404页面处理错误
  4. 路由守卫:路由守卫在服务端和客户端的执行差异

实用案例分析

错误场景:路由配置不一致

// 错误示例:服务端和客户端路由配置不一致
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

// 错误:创建单例路由,服务端和客户端共享
const router = new Router({
  mode: 'history',
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About }
  ]
})

export default router

正确实现

// 正确示例:服务端和客户端路由配置一致
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 正确:创建路由工厂函数,每次请求都创建新实例
export function createRouter() {
  return createRouter({
    history: createWebHistory(),
    routes: [
      { path: '/', component: () => import('./views/Home.vue') },
      { path: '/about', component: () => import('./views/About.vue') }
    ]
  })
}

// main.js
export function createApp() {
  const app = createSSRApp(App)
  const router = createRouter()
  
  app.use(router)
  
  return { app, router }
}

// entry-server.js
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    const { url } = context
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      resolve(app)
    }, reject)
  })
}

错误场景:路由守卫执行差异

// 错误示例:路由守卫在服务端和客户端执行差异
// router/index.js
export function createRouter() {
  const router = createRouter({
    history: createWebHistory(),
    routes: [
      { 
        path: '/', 
        component: Home,
        meta: { requiresAuth: true }
      }
    ]
  })
  
  router.beforeEach((to, from, next) => {
    // 错误:在服务端执行时,localStorage不可用
    const token = localStorage.getItem('token')
    if (to.meta.requiresAuth && !token) {
      next('/login')
    } else {
      next()
    }
  })
  
  return router
}

正确实现

// 正确示例:处理路由守卫在服务端和客户端的执行差异
// router/index.js
export function createRouter() {
  const router = createRouter({
    history: createWebHistory(),
    routes: [
      { 
        path: '/', 
        component: Home,
        meta: { requiresAuth: true }
      }
    ]
  })
  
  router.beforeEach((to, from, next) => {
    // 正确:检查是否在浏览器环境
    if (process.client) {
      const token = localStorage.getItem('token')
      if (to.meta.requiresAuth && !token) {
        next('/login')
      } else {
        next()
      }
    } else {
      // 服务端处理
      // 从请求头或cookie中获取token
      const token = to.meta.token // 假设token通过meta传递
      if (to.meta.requiresAuth && !token) {
        next('/login')
      } else {
        next()
      }
    }
  })
  
  return router
}

// 或在服务端入口中处理
// entry-server.js
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    const { url, cookies } = context
    
    // 将cookies传递给store
    store.commit('setCookies', cookies)
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      resolve(app)
    }, reject)
  })
}

20.4 Vue SSR状态管理的常见问题

核心知识点

在使用Vue SSR状态管理时,常见的问题包括:

  1. 状态序列化:状态序列化和反序列化问题
  2. 状态注入:服务端状态注入不当
  3. 状态同步:服务端和客户端状态同步错误
  4. 性能问题:状态管理导致的性能问题

实用案例分析

错误场景:状态序列化问题

// 错误示例:状态序列化错误
// store/index.js
export function createStore() {
  return createStore({
    state: {
      user: {
        id: 1,
        name: 'Alice',
        // 错误:包含不可序列化的数据
        updateTime: new Date() // Date对象在序列化时会变成字符串
      }
    }
  })
}

正确实现

// 正确示例:处理状态序列化

// 1. 确保状态可序列化
// store/index.js
export function createStore() {
  return createStore({
    state: {
      user: {
        id: 1,
        name: 'Alice',
        // 正确:使用可序列化的数据类型
        updateTime: new Date().toISOString() // 转换为字符串
      }
    },
    getters: {
      // 在getter中转换回Date对象
      userUpdateTime: state => new Date(state.user.updateTime)
    }
  })
}

// 2. 自定义序列化和反序列化
// entry-server.js
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    const { url } = context
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({ store, route: router.currentRoute })
        }
      })).then(() => {
        // 正确:序列化状态
        context.state = JSON.parse(JSON.stringify(store.state))
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

// 3. 在客户端中恢复状态
// entry-client.js
const { app, store } = createApp()

if (window.__INITIAL_STATE__) {
  // 正确:反序列化状态
  store.replaceState(window.__INITIAL_STATE__)
}

错误场景:状态注入不当

// 错误示例:状态注入不当
// 服务器中
const renderer = createBundleRenderer(serverBundle, {
  template: fs.readFileSync('./index.html', 'utf-8')
})

app.get('*', (req, res) => {
  const context = { url: req.url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    
    // 错误:未注入状态到HTML
    res.end(html)
  })
})

正确实现

// 正确示例:正确注入状态

// 1. 使用模板注入
// index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
</head>
<body>
  <div id="app"><!--vue-ssr-outlet--></div>
  <!-- 正确:注入状态 -->
  <script>window.__INITIAL_STATE__ = {{ state }}</script>
  <script src="/dist/client.js"></script>
</body>
</html>

// 2. 在服务器中设置状态
const renderer = createBundleRenderer(serverBundle, {
  template: fs.readFileSync('./index.html', 'utf-8'),
  // 正确:配置模板渲染
  renderState: (context) => {
    return `<script>window.__INITIAL_STATE__ = ${JSON.stringify(context.state)}</script>`
  }
})

app.get('*', (req, res) => {
  const context = { url: req.url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    
    res.end(html)
  })
})

// 3. 在客户端中同步状态
// entry-client.js
const { app, store } = createApp()

if (window.__INITIAL_STATE__) {
  // 正确:替换客户端状态
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  app.mount('#app')
})

20.5 Vue SSR性能优化的陷阱

核心知识点

在进行Vue SSR性能优化时,常见的陷阱包括:

  1. 缓存策略:缓存策略不当
  2. 预渲染:预渲染配置错误
  3. 资源优化:静态资源优化不当
  4. 服务器优化:Node.js服务器性能优化不当

实用案例分析

错误场景:缓存策略不当

// 错误示例:缓存策略不当
// 服务器中
app.get('*', (req, res) => {
  const context = { url: req.url }
  
  // 错误:每次请求都重新渲染,未使用缓存
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    
    res.end(html)
  })
})

正确实现

// 正确示例:使用合理的缓存策略

// 1. 使用页面缓存
const LRU = require('lru-cache')
const cache = new LRU({
  max: 100,
  maxAge: 1000 * 60 * 15 // 15分钟
})

app.get('*', (req, res) => {
  const url = req.url
  
  // 检查缓存
  const cachedHtml = cache.get(url)
  if (cachedHtml) {
    return res.end(cachedHtml)
  }
  
  const context = { url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    
    // 设置缓存
    cache.set(url, html)
    res.end(html)
  })
})

// 2. 使用组件缓存
// 组件中
export default {
  // 启用组件缓存
  serverCacheKey: props => props.id
}

// 3. 使用内存缓存
const memoryFs = require('memory-fs')
const fs = new memoryFs()
fs.writeFileSync('/dist/server-bundle.json', JSON.stringify(serverBundle))

// 4. 优化服务器
// 使用cluster模块
const cluster = require('cluster')
const numCPUs = require('os').cpus().length

if (cluster.isMaster) {
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork()
  }
  
  cluster.on('exit', worker => {
    console.log(`Worker ${worker.process.pid} died`)
    cluster.fork()
  })
} else {
  // 启动服务器
  const app = express()
  // 服务器配置
  app.listen(3000)
}

错误场景:预渲染配置错误

// 错误示例:预渲染配置不当
// vue.config.js
module.exports = {
  // 错误:未配置预渲染
}

正确实现

// 正确示例:配置预渲染

// 1. 使用@vue/preload-webpack-plugin
// 安装:npm install @vue/preload-webpack-plugin

// vue.config.js
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')

module.exports = {
  configureWebpack: {
    plugins: [
      new PreloadWebpackPlugin({
        rel: 'preload',
        as: 'script'
      })
    ]
  }
}

// 2. 使用prerender-spa-plugin
// 安装:npm install prerender-spa-plugin

// vue.config.js
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  configureWebpack: {
    plugins: [
      new PrerenderSPAPlugin({
        staticDir: path.join(__dirname, 'dist'),
        routes: ['/', '/about', '/contact'],
        renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
          renderAfterDocumentEvent: 'render-event'
        })
      })
    ]
  }
}

// 3. 在main.js中触发事件
new Vue({
  router,
  store,
  render: h => h(App),
  mounted() {
    document.dispatchEvent(new Event('render-event'))
  }
}).$mount('#app')

20.6 Vue SSR部署的常见错误

核心知识点

在部署Vue SSR应用时,常见的错误包括:

  1. 服务器配置:生产服务器配置不当
  2. 构建输出:构建输出文件路径配置错误
  3. 环境变量:生产环境变量配置错误
  4. 监控和日志:缺少监控和日志配置

实用案例分析

错误场景:服务器配置不当

// 错误示例:生产服务器配置不当
const express = require('express')
const app = express()

// 错误:未配置生产环境
app.use(express.static('dist'))

app.get('*', (req, res) => {
  // 错误:未处理生产环境的错误
  renderer.renderToString({ url: req.url }, (err, html) => {
    res.end(html)
  })
})

app.listen(3000)

正确实现

// 正确示例:生产服务器配置
const express = require('express')
const compression = require('compression')
const helmet = require('helmet')
const morgan = require('morgan')
const app = express()

// 正确:使用生产环境中间件
app.use(compression()) // 启用gzip压缩
app.use(helmet()) // 增强安全性
app.use(morgan('combined')) // 日志记录

// 正确:配置静态文件服务
app.use('/dist', express.static('dist', {
  maxAge: '1y' // 静态文件缓存1年
}))

// 正确:处理错误
app.get('*', (req, res) => {
  const context = { url: req.url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.code === 404) {
        res.status(404).end('Page not found')
      } else {
        console.error(err) // 记录错误
        res.status(500).end('Internal Server Error')
      }
      return
    }
    
    res.end(html)
  })
})

// 正确:设置端口
const port = process.env.PORT || 3000
app.listen(port, () => {
  console.log(`Server running on port ${port}`)
})

错误场景:构建输出路径配置错误

// 错误示例:构建输出路径配置错误
// vue.config.js
module.exports = {
  outputDir: 'dist',
  // 错误:未配置服务端和客户端构建输出
  configureWebpack: {
    output: {
      // 缺少输出路径配置
    }
  }
}

正确实现

// 正确示例:配置构建输出路径
// vue.config.js
module.exports = {
  outputDir: 'dist',
  configureWebpack: {
    output: {
      path: path.resolve(__dirname, 'dist'),
      publicPath: '/'
    }
  },
  chainWebpack: config => {
    if (process.env.VUE_ENV === 'server') {
      // 服务端构建输出
      config.output
        .path(path.resolve(__dirname, 'dist/server'))
        .filename('server-bundle.js')
    } else {
      // 客户端构建输出
      config.output
        .path(path.resolve(__dirname, 'dist/client'))
        .filename('[name].[hash].js')
    }
  }
}

// 服务器中正确引用构建文件
const serverBundle = require('./dist/server/server-bundle.json')
const clientManifest = require('./dist/client/vue-ssr-client-manifest.json')

const renderer = createBundleRenderer(serverBundle, {
  clientManifest,
  template: fs.readFileSync('./index.html', 'utf-8')
})

20.7 Vue SSR与第三方库的集成陷阱

核心知识点

在集成Vue SSR与第三方库时,常见的陷阱包括:

  1. 库的服务端兼容性:第三方库在服务端的兼容性问题
  2. DOM依赖:库依赖DOM API导致的问题
  3. 全局状态:第三方库的全局状态导致的问题
  4. 异步加载:第三方库异步加载导致的问题

实用案例分析

错误场景:库依赖DOM API

// 错误示例:使用依赖DOM的库
// 组件中
import $ from 'jquery'

export default {
  mounted() {
    // 在客户端工作正常,但在服务端会报错
    $('#element').hide()
  }
}

正确实现

// 正确示例:处理依赖DOM的库

// 1. 检查环境
// 组件中
export default {
  mounted() {
    // 正确:检查是否在浏览器环境
    if (process.client) {
      const $ = require('jquery')
      $('#element').hide()
    }
  }
}

// 2. 使用服务端兼容的库
// 例如,使用axios替代fetch
import axios from 'axios'

export default {
  asyncData() {
    // axios在服务端和客户端都可使用
    return axios.get('/api/data')
  }
}

// 3. 使用动态导入
// 组件中
export default {
  mounted() {
    // 动态导入,只在客户端执行
    import('jquery').then($ => {
      $('#element').hide()
    })
  }
}

// 4. 配置webpack以模拟浏览器环境
// webpack.config.js
module.exports = {
  configureWebpack: {
    node: {
      // 模拟浏览器环境变量
      fs: 'empty',
      net: 'empty',
      tls: 'empty'
    }
  }
}

错误场景:第三方库的全局状态

// 错误示例:使用有全局状态的库
// 组件中
import moment from 'moment'

// 错误:修改全局配置,影响所有请求
moment.locale('zh-cn')

export default {
  data() {
    return {
      now: moment().format('YYYY-MM-DD')
    }
  }
}

正确实现

// 正确示例:处理有全局状态的库

// 1. 不修改全局配置
// 组件中
import moment from 'moment'

export default {
  data() {
    return {
      // 正确:每次使用时指定配置
      now: moment().locale('zh-cn').format('YYYY-MM-DD')
    }
  }
}

// 2. 使用实例化的库
// 例如,使用axios实例
import axios from 'axios'

// 创建实例
const api = axios.create({
  baseURL: '/api',
  timeout: 10000
})

export default {
  asyncData() {
    return api.get('/data')
  }
}

// 3. 在服务端入口中重置全局状态
// entry-server.js
export default context => {
  // 重置全局状态
  require('moment').locale('en')
  
  return new Promise((resolve, reject) => {
    // 服务端渲染逻辑
  })
}

20.8 Vue SSR内存管理的误区

核心知识点

在管理Vue SSR内存时,常见的误区包括:

  1. 内存泄漏:内存泄漏导致的问题
  2. 资源清理:服务端资源清理不当
  3. 内存限制:Node.js内存限制配置
  4. 垃圾回收:垃圾回收优化不当

实用案例分析

错误场景:内存泄漏

// 错误示例:内存泄漏
// 服务器中
const app = express()
const connections = []

app.get('*', (req, res) => {
  // 错误:未清理连接
  connections.push(res)
  
  renderer.renderToString({ url: req.url }, (err, html) => {
    res.end(html)
  })
})

app.listen(3000)

正确实现

// 正确示例:内存管理
const app = express()

// 正确:使用runInNewContext: false
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false // 避免每次请求创建新的V8上下文
})

// 正确:处理连接
app.get('*', (req, res) => {
  const context = { url: req.url }
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    
    res.end(html)
    
    // 正确:清理上下文
    context = null
  })
})

// 正确:设置内存限制
// 启动服务器时:node --max-old-space-size=4096 server.js

// 正确:使用cluster模块
const cluster = require('cluster')
const numCPUs = require('os').cpus().length

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`)
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork()
  }
  
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.process.pid} died`)
    cluster.fork()
  })
} else {
  // 启动服务器
  app.listen(3000)
  console.log(`Worker ${process.pid} started`)
}

// 正确:定期重启工作进程
setInterval(() => {
  if (cluster.worker) {
    cluster.worker.kill()
  }
}, 60 * 60 * 1000) // 每小时重启一次

错误场景:资源清理不当

// 错误示例:资源清理不当
// 服务端入口中
import { createApp } from './main'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    // 错误:未清理资源
    router.push(context.url)
    
    router.onReady(() => {
      resolve(app)
    }, reject)
  })
}

正确实现

// 正确示例:资源清理
import { createApp } from './main'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    const { url } = context
    
    router.push(url)
    
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({ store, route: router.currentRoute })
        }
      })).then(() => {
        context.state = store.state
        resolve(app)
        
        // 正确:清理资源
        // 注意:不要在这里清理app,因为Vue需要它来渲染
      }).catch(reject)
    }, reject)
  })
}

// 正确:在服务器中处理内存使用
setInterval(() => {
  const memoryUsage = process.memoryUsage()
  console.log('Memory usage:', {
    rss: (memoryUsage.rss / 1024 / 1024).toFixed(2) + 'MB',
    heapTotal: (memoryUsage.heapTotal / 1024 / 1024).toFixed(2) + 'MB',
    heapUsed: (memoryUsage.heapUsed / 1024 / 1024).toFixed(2) + 'MB'
  })
  
  // 如果内存使用超过阈值,重启进程
  if (memoryUsage.heapUsed > 1024 * 1024 * 1024) { // 1GB
    console.log('Memory usage too high, restarting...')
    process.exit(1)
  }
}, 60000) // 每分钟检查一次
« 上一篇 Vue移动端开发踩坑 下一篇 » Vue PWA开发踩坑