bubble-icon
Skip to main content

Caching

Caching can be a complex topic especially when you have multiple layers of caching.

Caching on the server

During development

  • Next.js will not cache any requests
  • Magento will cache GraphQL calls. This is done by Magento itself, not by GraphCommerce.

How long will a page be cached (getStaticProps)?

GraphCommerce uses Incremental Static Regeneration (ISR) to cache pages with getStaticProps.

If you set a revalidate time of 60, all visitors will see the same generated version of your site for one minute. The only way to invalidate the cache is from someone visiting that page after the minute has passed.

The length is determined by the revalidate property on the object that is returned by getStaticProps. In GraphCommerce we use 60 * 20.

This means that a cache will be regenerated when:

  1. 20 minutes has passed
  2. A new request is made to the page

Note: A page will never fall out of the cache if it is not requested. Even if this is a very long time. In practice this can be days.

How long will a page be cached (getServerSideProps)

Not all pages use getStaticProps, a few pages that are not static use getServerSideProps

The pages that do not use ISR are /c/[...url] and /search/[...url] which are filtered pages. These pages are not cached by the server at all.

Even though the /c/[...url] page sets a Cache-Control header it isn't cached by Cloudflare and isn't cached by the browser / service worker.

What happens when a backend is offline?

If a page is rendered with getStaticProps and the had been rendered before, it will keep showing the old page. If the page hadn't been rendered before, it will show a 500 error.

How does caching work with Magento GraphQL?

Magento caches GraphQL queries that are send as GET requests (which are all queries from GraphCommerce) and have a @cache directive configured in the schema.

Magento caches certain queries in GraphQL, the following are relevant for GraphCommerce: categories, products, route. You can also find out what is cached by doing a search in the Magento codebase.

Cache invalidation is using the same system as any page that is cached in Varnish. GraphQL invalidation docs

ApolloClient InMemory cache used on the server

By default a GraphQL API call is not cached, but by configuring fetchPolicy: 'cache-first' when running the query, we can cache the response of a GraphQL API call.

To reduce the API calls to certain backends, we use an in-memory cache on the server. There are two queries that are cached by ApolloClient's InMemory cache:

We do this because this reduces the amount of GraphQL requests made to Hygraph about 100x. The Layout and HygraphAllPages query would else be request on all pages.

The InMemory cache is kept indefinitely, it is never flushed! There currently is no way to flush this cache. This means that while a serverless funtion is running or a node process is running the cache will be kept in memory:

  • For serverless functions (Vercel) this isn't a problem as they are killed after a few minutes.
  • For node.js processes this means that they need to be restarted every now and then. (a few times a day is fine)

Caching in the browser

Apollo Client caching in the browser

By default all the information stored in the ApolloClient InMemory cache is also persisted to localStorage. When the page is loaded, the cache is restored from localStorage.

Apollo Client tries and use the cache as much as possible. This means that multiple useQuery calls with the same query+variables will return the same result and all use the cache (default fetchPolicy: 'cache-first')

The exception is when a query is made with a different fetchPolicy. We use 'cache-and-network' on quite a few queries to make sure that the user always sees up-to-date data.

Improvements since GraphCommerce 6.2.0:

We've introduced the persistenceMapper that makes sure not everything gets persisted to localStorage. We prune the cache based on a list of selectors. This aims to keep the cache as small as possible, without chaning the default behavior that 'everything is persisted to localStorage'.

What is stored in the localStorage?

All queries made with useQuery are stored in the localStorage of the user and is restored when the user visits the website

  • With GraphCommerce < 6.2.0: All queries made with useQuery are stored in the localStorage of the user and is restored when the user visits the website
  • With GraphCommerce >= 6.2.0

Which HTTP requests are cache by the browser?

  • pages and _next/data requests are not cached and are requested each time a page is visited. The _next/data requests is the actual data of a page to be able to navigate faster over the site.
  • _next/static requests are cached by the browser. These include images, fonts and js and css. All files are hashed and cleaned up when a new deployment is made.
  • _next/imagerequests are cached by the browser, but has a 'revalidate' header so requests will be revalidated by the browser.

Which HTTP requests are cached by the service worker?

Service worker sits between the browser and the network. It can cache requests and return them from the cache instead of the network. This can be seen as an additional caching layer which can be configured separately from the browser cache.

The service worker caches:

  • static fonts: Google fonts and webfonts with StaleWhileRevalidate strategy
  • static images: jpg, jpeg, gif, png, svg, ico, webp with StaleWhileRevalidate strategy
  • _next/image: Custom implementation with StaleWhileRevalidate and nextImagePlugin
  • js and css files: Only for files outside of _next/static with StaleWhileRevalidate strategy

Notable differences from previous implementation:

  • All _next/static files (js, css) are excluded from runtime caching as they are handled by the precache mechanism
  • Cross-origin requests use NetworkOnly strategy instead of NetworkFirst
  • Image caching has been optimized with custom configuration for better performance

Note: When a new deployment is made, the service worker is updated. This means that all previous caches are cleared and new caches are created.

It does not cache:

  • _next/data: Uses NetworkFirst strategy to ensure fresh data
  • pages: Uses NetworkFirst strategy, which means it will always try to fetch the resource from the network first, and only if that fails it will use the cache

Cache invalidation limitations

Pages keep having old information even though Magento has updated the data

Currently there is no communcation between Magento and Next.js to revalidate a page when a product or category is updated.

This means that a page will only be revalidated when a user visits the page again and the revalidate time has been reached.

Suggested solution: Accept the revalidate time or reduce the revalidate time of products and categories.

Pages keeps having stale global information

Even when a the LayoutDocument is refreshed by restarting a node.js process the fresh data is not automatically shown to a user.

This means that a page only gets revalidated when a user visits the page again and the revalidate time has been reached.

This results in the situation that an header change like a navigation item or a 'global message' will see the old information for a long time.

Suggested solution: Create a fresh deployment

  1. Manually create a fresh deployment.
  2. Vercel: Integrate a deploy hook as a Hygraph webhook
  3. Github actions: Integrate a webhook_run as a Hygraph webhook