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
208 stars 18 forks source link

Question - Is there support for @nuxtjs/apollo? #70

Open aaronLejeune opened 3 weeks ago

aaronLejeune commented 3 weeks ago

Hello,

TLDR; how can I enable SSR caching with Apollo combined with NuxtHub?

First of all, I want to say that I’m relatively new to caching and I’m currently trying to optimize the performance of my website. I’m using Strapi as a headless CMS and fetching content via @nuxtjs/apollo. The project is a webshop that operates in multiple languages and regions, which means a lot of dynamic content and API calls. I noticed the initial response time was realllllyyy slow, so I thought of caching some data on the server frontend as well..

I’ve started testing out NuxtHub, which looks like a great solution for easily storing cache and KV. However, I’m having trouble understanding how to properly cache the API data on the server side. The client-side caching, handled by Apollo itself, is working nicely as expected, but I want to extend caching to the server to improve performance even further.

This is a simplified version of what my current [...slug].vue looks like:

<script setup>
import GetContentBySlug from "~/apollo/queries/delta.gql";

//Fetch and client-cache the data from Strapi
const { data } = await useAsyncQuery({
  query: GetContentBySlug,
  variables: {
    slug: generateSlug(route.params.slug),
    locale: localeProperties.value.locale_strapi,
    market: localeProperties.value.country,
  },
  cache: true,
});

//to set the i18n params in different locales
setI18nParams(data.allLocaleParams);
useSeo(data.seo)

</script>
<template>
  <div>
    <h1>{{ pageData.value.title }}</h1>
    <p>{{ pageData.value.description }}</p>
  </div>
</template>

I’ve done quite a bit of research, but I think I’m stuck and could really use some guidance. Any help or pointers would be greatly appreciated!

Thanks a lot in advance!

dulnan commented 2 weeks ago

So, what you likely need is the useDataCache composable. If you want to cache most of your GraphQL queries, then it probably makes sense to create your own composable that acts as a wrapper around useDataCache, useAsyncData and useQuery.

However, I'm not entirely familiar with @nuxtjs/apollo because I maintain my own Nuxt GraphQL client. As you mentioned, Apollo has a client cache; I'm not sure if this exclusively only applies to client side or if it would be possible to provide your own "cache provider" so to say? If yes, then you could actually "hook" into this part of Apollo and use useDataCache there to get and set responses into cache. That way you could easily continue to use the default composables and the caching would happen "automatically" in the background, basically.

But assuming this is not easily possible, then a custom composable is the way to go. However, it can get tricky to get the types right, together with useAsyncData and the GraphQL types. I quickly looked at the implementations of useQuery and useAsyncQuery, however not all of the types seem to be properly exported (but I did not check 100%). So this is what a custom composable might look like, as a very rough, non-working starting point:

import { useAsyncData, type AsyncData } from '#app'
import { hash } from 'ohash'

export function useCachedQuery<TResult, TVariables extends OperationVariables>(
  document: DocumentParameter<TResult, TVariables>,
  variables?: VariablesParameter<TVariables>,
): AsyncData<TResult, unknown> {
  // Create a unique key.
  const key = hash(document, variables)

  return useAsyncData(key, async function (app) {
    // Try to get from cache.
    const { value, addToCache } = await useDataCache<TResult>(
      key,
      app?.ssrContext?.event,
    )

    // If we have a value from cache, return it.
    if (value) {
      return value
    }

    // Make the GraphQL query.
    const result = await useQueryImpl(document, variables)

    // Add to cache.
    await addToCache(result)

    // Return the value.
    return result
  })
}

If you also want to implement the options of useAsyncData (such as transform or getCachedData), then it gets even more trickier. I have done something similar with the useCachedAsyncData composable that is provided by this module. Maybe this could also be a good starting point for you.