dulnan / nuxt-multi-cache

Advanced caching of components, routes and data for Nuxt 3. Dynamically define CDN cache control headers. Provides cache management API for purging items by key or using cache tags.
https://nuxt-multi-cache.dulnan.net
MIT License
207 stars 18 forks source link

Client side request are not cached #53

Closed plcdnl closed 1 week ago

plcdnl commented 2 months ago

Hi, sorry for the disturb.

I'm using the data cache api and I was wondering if it was the normal functioning of the plugin that the client side returns undefined as the cached value.

Is there a way to make this work on the client side too?

dulnan commented 2 months ago

The module is specifically only made for SSR caching. Especially the unstorage dependency would have to be included in the client bundle for a seamless experience, which would not be that great. And it would then also only be compatible with the memory driver. Of course it would be possible to hardcode a memory cache into the client side code of the composable.

On the client side you already have Nuxt's own utilities such as useState or useAsyncData available to implement your own caching. For a seamless experience, useDataCache would have to pass the cached data back to the client as a payload so it's available on hydration. However, when used together with useState or useAsyncData, the same data would then be passed as payload twice.

However, I do see the value of having a single useDataCache composable that handles both server and client caching. I will keep this open to explore possible solutions.

plcdnl commented 2 months ago

Thank you for your quick response. The problem with useState and useAsyncData is that at the moment it should do at least one time a graphql call on client side. If the data already persist in the cache i'd like to avoid that graphql call.

I don't know if my thought is right but I hope it can be useful

dulnan commented 2 months ago

Yeah I know exactly what you mean! Just using useAsyncData out of the box will only return a cached value on hydration. You'd have to define your own getCachedData method in useAsyncData to make it work. That would give you the possibility to return cached data from payload. And when using transform you could also cache subsequent GraphQL calls done inside useAsyncData.

plcdnl commented 2 months ago

Thank you and sorry again. I know that this question can riuslt annoying. What do you mean when you say "define my own getCachedData" method?

Do you think that a server endpoint that handle this for me can be a good solution? Or do you have an alternative idea?

🙏

dulnan commented 2 months ago

You can pass a getCachedData method to the useAsyncData options: https://nuxt.com/docs/api/composables/use-async-data#params

By default, the getCachedData method is this: key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]. This will only load the cached data during hydration. But you can implement it as key => nuxt.payload.data[key] || nuxt.static.data[key] instead, which should also work after hydration.

Note that this only works on the client side. On the server these methods are not called, if I'm not mistaken.

plcdnl commented 2 months ago

Wow, simpler than i thought. It works fine with subsequent calls but in the client at the first access between one page and another it is always a new graphql when I am sure that the data has already been stored with the addToCache function.

export async function useGqlData({ key, query, variables }, args) {
    const transform = args?.transform
    const options = {
        ...args,
        cacheKey: key,
        transform: undefined,
    }
    const nuxt = useNuxtApp()
    const { $graphqlApi } = nuxt

    return useAsyncData(
        key,
        async () => {
            const { value, addToCache } = await useDataCache(key)

            if (value) {
                return value
            } else {
                const res = await $graphqlApi({ query, variables })
                if (res.error || res.errors || !res.data) {
                    console.error(res.error || res.errors || "No data found.")
                    throw new Error("Error in fetching data")
                }

                const result = transform ? transform(res.data) : res.data

                if (key) {
                    addToCache(result, options.cacheTags, options.cacheExpires)
                }
                return result
            }
        },
        {
            ...options,
            getCachedData: (key) => {
                return nuxt.payload.data[key] || nuxt.static.data[key]
            },
        }
    )
}

This is my custom useAsyncData

dulnan commented 2 months ago

I would try to move const { value, addToCache } = await useDataCache(key) outside of useAsyncData into your composable. You might also need to enable the experimental asyncContext feature of Nuxt for this to work properly. Also keep in mind you should await addToCache, because it's possible that it never stores in cache if execution continues.

dulnan commented 2 months ago

@plcdnl In #58 a new composable called useCachedAsyncData was implemented that now also implements a client-side cache. The plan is to also add this functionality to useDataCache in #59.

I hope to release the new composable some time next week.

plcdnl commented 1 month ago

Wow! Incredible work. Thank you ! 🙏

dulnan commented 1 week ago

As this has already been released in the last release, I'm going to close this issue.