阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 服务端渲染的特殊处理

服务端渲染的特殊处理

作者:陈川 阅读数:23828人阅读 分类: Vue.js

服务端渲染的特殊处理

Vue3的服务端渲染(SSR)与客户端渲染存在显著差异,核心在于如何在不同环境中处理组件生命周期、状态管理和DOM操作。运行时需要区分当前执行环境,对特定API进行替换或降级处理。

环境变量与全局状态隔离

服务端渲染首要问题是避免单例状态污染。在Node.js环境下,每个请求应该获得全新的应用实例:

// 创建工厂函数避免单例
export const createApp = () => {
  const app = createSSRApp(App)
  const store = createStore()
  app.use(store)
  return { app, store }
}

Vue3通过__VUE_PROD_SSR__编译标志区分环境,内部会自动处理window等客户端特有对象的引用问题。对于全局变量访问,推荐使用ssrContext注入:

// 服务端设置上下文
const ctx = {}
const { app } = createApp()
renderToString(app, ctx)

// 组件内获取
import { useSSRContext } from 'vue'
const ctx = inject(SSR_CONTEXT)

生命周期钩子调整

服务端执行时仅会调用特定生命周期,需要特别注意:

  • beforeCreatecreated 会在服务端执行
  • beforeMountmounted 只会在客户端执行
  • beforeUpdateupdated 不会在服务端执行
  • activateddeactivated 需要配合KeepAlive使用

异步组件需要特殊处理:

defineAsyncComponent({
  loader: () => import('./Component.vue'),
  suspensible: false // 禁止Suspense包装
})

模板编译差异

服务端渲染时,编译器会生成优化后的字符串拼接代码。通过compileSSR方法会产生不同的渲染函数:

const { compile } = require('vue/compiler-ssr')
const result = compile(`<div>{{ msg }}</div>`, {
  isCustomElement: tag => tag.startsWith('x-')
})

关键差异点包括:

  1. 跳过事件监听器编译
  2. 不生成虚拟DOM补丁标志
  3. 静态节点提升为字符串常量
  4. 禁用v-model等双向绑定语法

客户端激活处理

混合渲染时需要确保客户端激活正确执行。createSSRApp会生成特殊标记:

<div id="app" data-server-rendered="true">
  <!-- 服务端渲染内容 -->
</div>

激活过程会对比现有DOM与虚拟DOM,关键源码逻辑在packages/runtime-core/src/renderer.ts

if (container._vnode) {
  // 激活现有DOM
  hydrate(container._vnode, container)
} else {
  // 全新挂载
  render(vnode, container)
}

数据预取与状态同步

服务端数据预取需要特殊处理以避免客户端重复请求:

// 服务端路由配置
router.beforeResolve(async (to, from, next) => {
  const matched = to.matched.flatMap(record => {
    return Object.values(record.components).map(component => {
      return component.asyncData?.({ store, route: to })
    })
  })
  await Promise.all(matched)
  next()
})

// 客户端数据同步
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

流式渲染处理

高性能场景下可以使用流式渲染,Vue3通过renderToStream实现:

const stream = renderToStream(app)
stream.on('data', chunk => {
  res.write(chunk)
})
stream.on('end', () => {
  res.end()
})

流式渲染需要特别注意组件顺序,异步组件需要提前声明:

defineComponent({
  ssrPrefetch: true, // 强制预加载
  async setup() {
    const data = await fetchData()
    return { data }
  }
})

跨平台API适配

Vue3通过@vue/server-renderer提供平台无关的渲染API,内部实现了跨平台的基本方法:

interface RendererOptions {
  createElement: (tag: string) => any
  insert: (el: any, parent: any) => void
  setElementText: (el: any, text: string) => void
  patchProp: (el: any, key: string, prevValue: any, nextValue: any) => void
}

对于特殊环境如小程序,可以自定义实现这些接口。

错误处理与降级方案

服务端渲染需要完善的错误捕获机制:

try {
  const html = await renderToString(app)
} catch (err) {
  // 记录错误日志
  console.error('SSR error:', err)
  // 降级为客户端渲染
  res.send(`<div id="app"></div>`)
}

对于第三方库兼容性问题,可以通过ssrMock模拟浏览器对象:

global.window = {
  localStorage: {
    getItem: () => null
  }
}

性能优化策略

SSR性能关键点在于缓存和复用:

const LRU = require('lru-cache')
const microCache = new LRU({
  max: 100,
  maxAge: 1000 * 60 // 1分钟缓存
})

function render(req, res) {
  const cacheKey = req.url
  if (microCache.has(cacheKey)) {
    return res.send(microCache.get(cacheKey))
  }
  
  renderToString(app).then(html => {
    microCache.set(cacheKey, html)
    res.send(html)
  })
}

组件级别缓存通过serverCacheKey实现:

export default {
  name: 'CachedComponent',
  props: ['item'],
  serverCacheKey: props => props.item.id
}

构建配置差异

SSR构建需要生成不同资源:

// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.entry('app').clear().add('./src/entry-client.js')
    
    config.entry('server').clear().add('./src/entry-server.js')
  }
}

Webpack配置需要处理Node.js模块:

externals: [
  nodeExternals({
    allowlist: [/\.css$/, /\?vue&type=style/]
  })
]

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌