Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@vue/server-renderer doesn't have support for component-level caching #1791

Open
RomyPr opened this issue Aug 5, 2020 · 14 comments
Open

@vue/server-renderer doesn't have support for component-level caching #1791

RomyPr opened this issue Aug 5, 2020 · 14 comments
Labels
scope: ssr ✨ feature request New feature or request

Comments

@RomyPr
Copy link

RomyPr commented Aug 5, 2020

What problem does this feature solve?

ServerCacheKey is useful feature for large projects with SSR. Will it be added to @vue/server-renderer?

What does the proposed API look like?

https://ssr.vuejs.org/guide/caching.html#component-level-caching

@yyx990803 yyx990803 added ✨ feature request New feature or request scope: ssr labels Aug 6, 2020
@CyberAP
Copy link
Contributor

CyberAP commented Aug 16, 2020

I wonder if it makes sense to support this in a form of a built-in component instead of a component option?
It would then be possible to use promises to get a cache key:

<template>
  <server-cache :key="cacheKey">
    <!-- rendered on cache miss, otherwise cached stringified component is used -->
    <heavy-component v-bind="$props" />
  </server-cache>
</template>

<script>
  export default {
    async setup(props) {
      const cacheKey = ref(null);
      cacheKey.value = await CachingService.get(props.id);
      return { cacheKey }
    }
  }
</script>

It also makes caching more versatile because it's not bound to the component.

@yaquawa
Copy link

yaquawa commented Apr 15, 2021

Hi, had any progress on this?? or any plan to implement this in the future or not?
It was possible on Vue2, now how to do this on Vue3?

@vertcitron
Copy link

vertcitron commented Jul 5, 2021

Hi everybody and @yyx990803.
We need to migrate a very large scale SSR app written in Vue 2.6 / TS to Vue 3, and our Vue 2.6 makes an heavy use of component caching to ensure the best performance.
In consequence we can't wait this feature to be added, and we need to try to implement it ourself.
Before starting, I'd like to know the best way to achieve it with the goal to get the solution retained to be included in the library and available later to the community.
There are several ways to get it done :

  • Reproduce the vue 2.6 server-renderer behavior, by passing the cache and its options to the renderer constructor, and implementing the serverCacheKey option. It could just be enhanced by passing the cache client we want and not only the LRU in-memory cache, which could remain useful in certain situations, but could also be for example a Redis client to share caching between different pods of a kubernetes workload for example.
  • Go to a new way of caching, implementing a wrapper component like presented above, making use of a render function which decides to use the renderer or a cached rendered string. It could be also an elegant solution because it could be implemented as a plugin, staying independant from the base libraries.

The second solution seems likely to me, but at this time I can't figure out how to get the rendered string in the wrapper's render function without modifying the renderToString function in the server renderer...

EDIT : to give a better understanding, here are some measures on the on the landing page of galerieslafaytette.com (a big french department store). these measures were made on the webapp deployed on a GKE kubernetes workload, with it's autoscaler setup for the test to give always only one pod. We fire it it with npx autocannon -c 40 -r 1 -t 20 the_technical_url_of_the_deployment, which means 40 simultaneous users firing each one 1 request per second during 20 seconds. The time is the first server response time (the SSR page itself) without subsequent asset loading and the rate is the number of request the pod has been able to serve per second, both averaged through the 20 seconds :

  • Vue 2.6 without component-level caching : 1680 ms - 7 req/s
  • Vue 2.6 with component-level caching : 260 ms - 28 req/s
  • Vue 3 without component-level caching: 1230 ms - 11 req/s

As you can see, migrating to Vue 3 has a benefit, but not enough regarding the micro-caching available on the Vue 2 server-renderer...

@vertcitron
Copy link

@CyberAP Your idea is very good, and I think I'll try to implement it this way, and maybe I will not have to modify the server-renderer package, if I'm able to call the renderToString of the server renderer function from the render function of this cache wrapper on the default slot content...
And on Client Side, the cache wrapper component should be totally transparent...

@CyberAP
Copy link
Contributor

CyberAP commented Aug 18, 2021

I am using serverCacheKey extensively in Vue 2 and I really hope that the caching interface would support stream piping as well as returning just strings. This siginifcantly boosts app performance for streaming rendering if you're caching large html chunks and don't want to deal with unnecessary IO from copying strings back and forth.

The implementation could look like this:

const app = createApp({
  ssrCache: {
    get(key) {
      if (!canStream) return resolveItem(key) // resolves to string
      else return getItemStream(key) // resolves to ReadableStream
    },
    set(key, value) { cacheItem(key, value) },
  },
});

@gokalpfirat05
Copy link

Any progress on this request? I guess there are tons of applications use this feature at Vue 2 but it's missing in Vue 3 strangely. I don't know if it's not implemented on purpose. @yyx990803

@PatrikRacsko
Copy link

@CyberAP Your idea is very good, and I think I'll try to implement it this way, and maybe I will not have to modify the server-renderer package, if I'm able to call the renderToString of the server renderer function from the render function of this cache wrapper on the default slot content... And on Client Side, the cache wrapper component should be totally transparent...

Hi everybody and @yyx990803. We need to migrate a very large scale SSR app written in Vue 2.6 / TS to Vue 3, and our Vue 2.6 makes an heavy use of component caching to ensure the best performance. In consequence we can't wait this feature to be added, and we need to try to implement it ourself. Before starting, I'd like to know the best way to achieve it with the goal to get the solution retained to be included in the library and available later to the community. There are several ways to get it done :

  • Reproduce the vue 2.6 server-renderer behavior, by passing the cache and its options to the renderer constructor, and implementing the serverCacheKey option. It could just be enhanced by passing the cache client we want and not only the LRU in-memory cache, which could remain useful in certain situations, but could also be for example a Redis client to share caching between different pods of a kubernetes workload for example.
  • Go to a new way of caching, implementing a wrapper component like presented above, making use of a render function which decides to use the renderer or a cached rendered string. It could be also an elegant solution because it could be implemented as a plugin, staying independant from the base libraries.

The second solution seems likely to me, but at this time I can't figure out how to get the rendered string in the wrapper's render function without modifying the renderToString function in the server renderer...

EDIT : to give a better understanding, here are some measures on the on the landing page of galerieslafaytette.com (a big french department store). these measures were made on the webapp deployed on a GKE kubernetes workload, with it's autoscaler setup for the test to give always only one pod. We fire it it with npx autocannon -c 40 -r 1 -t 20 the_technical_url_of_the_deployment, which means 40 simultaneous users firing each one 1 request per second during 20 seconds. The time is the first server response time (the SSR page itself) without subsequent asset loading and the rate is the number of request the pod has been able to serve per second, both averaged through the 20 seconds :

  • Vue 2.6 without component-level caching : 1680 ms - 7 req/s
  • Vue 2.6 with component-level caching : 260 ms - 28 req/s
  • Vue 3 without component-level caching: 1230 ms - 11 req/s

As you can see, migrating to Vue 3 has a benefit, but not enough regarding the micro-caching available on the Vue 2 server-renderer...

Any progress on any of the mentioned solutions? we are facing the same problem. The second solution is indeed a great idea!

@dulnan
Copy link

dulnan commented Dec 26, 2022

Like others I was also looking for a solution for component caching with Vue 3 server-renderer. I maintain nuxt-multi-cache that among other things offers a seamless integration for component caching in Nuxt 2. Currently rewriting the module for Nuxt 3 and had to find a solution for component caching.

Looked into various options but then tried out the idea suggested in one of the comments here: Using a wrapper component that caches the contents of the default slot. After some fiddling around I managed to make it work as part of the rewrite for my module.

Essentially you can use the ssrRenderSlotInner method from vue/server-renderer in your wrapper's setup method, render the slot to string and then return that string as a innerHTML child, while also caching the string. After that you can load the markup from cache.

Currently you'll need to copy paste some code from server-renderer that handles buffers. But I have created a feature request at #7414 that would export two methods so the copy pasting isn't needed anymore. In the issue you'll also find an example that shows the basic idea of this approach.

@bgondy
Copy link

bgondy commented Aug 2, 2023

Any update on this ?

Does component-level-caching support (with serverCacheKey) function has been dropped as of Vue3 ?

I can't find any information in documentation about this for Vue3/Nuxt3 as of today but this issue. It would be nice to get feedback from the core team about this topic.

@AdnanCukur
Copy link

Anyone has a solution for this ?

@Oskar-Nilsen-Roos
Copy link

Would also like to see a solution for this

@dmytro-grablov
Copy link

Not sure if this is relevant, but based on suggestion from @CyberAP I have the following working example:

<script>

import { renderToString } from '@vue/server-renderer';
import { h, createStaticVNode, getCurrentInstance, createApp } from 'vue';

// Implement your own cache service, pass to the component using provide/inject etc.
const dummyCache = {};

export default {
  name: 'ServerCache',
  props: {
    cacheKey: {
      type: String,
      required: true,
    },
    cacheName: {
      type: [ String, Symbol ],
      required: true,
    },
  },
  computed: {
    cacheEntryKey() {
      return `${this.cacheName}-${this.cacheKey}`;
    }
  },
  async beforeCreate() {
    const isClient = typeof window !== 'undefined';

    if (!this.$slots.default || isClient || typeof dummyCache[this.cacheEntryKey] !== 'undefined') {
      return;
    }
    const vNode = h(this.$slots.default);

    /*
     * wrap vNode into a temporary app and assign original context
     * so that children have access to provides, plugins, etc.
     */
    const tempApp = createApp({ render: () => vNode })
    tempApp._context = getCurrentInstance().appContext;

    const renderedString = await renderToString(tempApp);
    dummyCache[this.cacheEntryKey] = renderedString;
  },

  render() {
    const cachedEntry = dummyCache[this.cacheEntryKey];
    if (typeof cachedEntry !== 'undefined') {
      return createStaticVNode(cachedEntry)
    }
    if (this.$slots.default) {
      return h(this.$slots.default);
    }
    return null;
  },
};
</script>

Then use as

<server-cache cache-name="page-navi" :cache-key="heavyPropsAsString">
    <heavy-component v-bind="heavyProps"></heavy-component>
</server-cache>

So far the prototype worked for me.

Yes, it is using vue internals, but only within itself. And it is not messing with main app instance and hooks. If anything changes you just have to update the implementation of this one component. Or if it broke completely due to change in vue internal APIs, simply remove cache wrapper, yes your app will become slow again, but it won't break.

@CyberAP
Copy link
Contributor

CyberAP commented Mar 18, 2024

@dmytro-grablov nice implementation. I think you don't even need to create a new app instance for this. We can re-use the internal method.

export default {
  created() {
      let res = '';
      const push = (val) => { res += val };
      this.$.type.ssrRender(this.$.proxy, push, this.$.parent, this.$.attrs);
      console.log(res); // rendered markup
  }
}

Of course this would need some additional handling in case you have an async child.

@dmytro-grablov
Copy link

hm, I don't have ssrRender on my component. Is it coming from Nuxt? We do not use Nuxt currently or otherwise I do not know how to access it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope: ssr ✨ feature request New feature or request
Projects
None yet
Development

No branches or pull requests