Subscribe on changes!

Export buffer related methods in server-renderer

avatar
Dec 26th 2022

What problem does this feature solve?

Since component caching was removed from the V3 releases of server-renderer, I was looking into implementing component caching myself using a wrapper component that renders its default slot to string. The string can then be cached server-side and used from there on when the component should be rendered again.

I'm using the ssrRenderSlotInner method for that, exported from vue/server-renderer. I tried other approaches but this one is the cleanest, as the wrapper component doesn't have to implement a lot of boilerplate code. The only exception is the buffer handling: ssrRenderSlotInner expects a push method that will push all the rendered parts into a buffer. After execution the resulting nested array needs to be unrolled. Two unexported methods exist in server-renderer that would be very helpful: createBuffer and unrollBuffer. With that it would be possible to implement component caching.

What does the proposed API look like?

Export createBuffer and unrollBuffer methods. With that component caching can be implemented something like this:

import { defineComponent, useSSRContext, useSlots, getCurrentInstance, h } from 'vue'
import { ssrRenderSlotInner, unrollBuffer, createBuffer } from 'vue/server-renderer'
// The global cache singleton.
import CACHE from '#cache-singleton'

export default defineComponent({
  name: 'RenderCacheable',

  props: {
    cacheKey: {
      type: String,
      default: '',
    },
  },
  async setup(props) {
    const slots = useSlots()

    if (process.server) {
      const cached = CACHE.get(props.cacheKey)

      if (cached) {
        return () =>
          h('div', {
            innerHTML: cached,
          })
      }
      const currentInstance = getCurrentInstance()
      const parent = currentInstance.parent

      // Render to string.
      const { getBuffer, push } = createBuffer()
      ssrRenderSlotInner(slots, 'default', {}, null, push, parent)
      const markup = await unrollBuffer(getBuffer())

      // Save in cache.
      CACHE.set(props.cacheKey, markup)
      return () =>
        h('div', {
          innerHTML: markup,
        })
    }

    return () => h('div', slots.default())
  },
})

Another option would be to create a new method ssrRenderSlotToString or similar, that combines ssrRenderSlotInner with the buffer handling and directly returns a string.

Also, maybe I'm totally going a wrong path here with this approach for component caching. If that's the case I'm very much open for suggestions or hints what might be the best solution here.