Skip to content
On this page

Caching

You will learn

  • What is a browser cache and how to use it
  • When it is reasonable to use custom caching
  • Where to store cached data
  • How to invalidate cache
  • How skip requests if data is already cached

Browser cache

Browsers have a built-in cache for HTTP requests — HTTP cache. It is a great tool to improve performance of your application that allows you to avoid unnecessary requests to the server and reduce the load on it.

Example

Real-world showcase with SolidJS around JSON API in the Farfetched repository is using HTTP cache. Try to walk through the application and observe — after the initial load it is blazing fast because of proper HTTP cache headers in API.

Browser cache is build on top of HTTP headers, so this mechanism cannot be controlled directly by frontend applications.

  • If you are using Backend-for-frontend architecture, you can set proper HTTP cache headers on your BFF.
  • If your frontend application calls a separated backend, you can communicate with your backend team to about proper HTTP cache headers.

Further reading

Read more about HTTP caching on MDN.

Custom cache

Probably you should not use custom cache

Browsers are very powerful systems, they are developed by large teams with a great experience. They have a lot of optimizations and tricks to make your application work faster. If you are using custom cache, you are bypassing all of them. This can lead to unexpected behavior and performance issues.

Consider using browser cache first, talk to your backend team to make sure that they are using proper cache headers. If you still need custom cache, make sure that you are aware of the consequences and that it is worth it.

If browser cache is not enough for your use case, you can use custom cache. Farfetched provides cache operator that allows you to do it with a simple declarative API.

TL;DR

Call cache operator with a Query that you want to cache.

ts
import { cache } from '@farfetched/core';

cache(characterQuery);

Below, let's take a bit more detailed review of this feature.

Prerequisites

Every Query has to have a unique key that is used to identify it. You can set it with name field while creating a Query:

ts
const characterQuery = createQuery({
  name: 'character',
  // ...
});

However, it could be annoying to control uniqueness of the names manually, so you can set up code transformation tool to do it for you.

Babel plugin

If your project already uses Babel, you do not have to install any additional packages, just modify your Babel config with the following plugin:

json
{
  "plugins": ["effector/babel-plugin"]
}

INFO

Read more about effector/babel-plugin configuration in the Effector's documentation.

SWC plugin

WARNING

Note that plugins for SWC are experimental and may not work as expected. We recommend to stick with Babel for now.

SWC is a blazing fast alternative to Babel. If you are using it, you can install effector-swc-plugin to get the same DX as with Babel.

sh
pnpm add --save-dev effector-swc-plugin @swc/core
sh
yarn add --dev effector-swc-plugin @swc/core
sh
npm install --dev effector-swc-plugin @swc/core

Now just modify your .swcrc config to enable installed plugin:

json
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "experimental": {
      "plugins": ["effector-swc-plugin"]
    }
  }
}

INFO

Read more about effector-swc-plugin configuration in the plugin documentation.

Vite

If you are using Vite, please read the recipe about it.

Cache adapters

cache does not specify where to store cached data. It is up to you to choose a cache adapter. Farfetched provides a few adapters out of the box:

  • inMemoryCache (default adapter) is the simplest adapter that stores cached data in memory, so it is not persistent, and you can cache any data without serialization. Cache wipes out when the page is reloaded and could not be shared between tabs.
  • localStorageCache is an adapter that stores cached data in the localStorage of the browser. It is persistent, so you can store only serializable data with this adapter. Cache is shared between tabs and persists after page reloads.
  • sessionStorageCache is an adapter that stores cached data in the sessionStorage of the browser. It is persistent, so you can store only serializable data with this adapter. Cache is not shared between tabs, but stores after page reloads.

TIP

Use localStorageCache only with auto-deletion options, like maxAge or maxEntries. Otherwise, you can easily run out of space in the localStorage.

Auto-delete cache entries

You can use maxAge and maxEntries options to automatically delete cache entries. This is useful to avoid running out of space in the localStorage or sessionStorage or out of memory errors in the inMemoryCache. Any adapter can be configured with these options.

maxEntries

It is the maximum number of entries that can be stored in the cache. When the limit is reached, the oldest entry is deleted.

ts
import { cache, localStorageCache } from '@farfetched/core';

cache(characterQuery, {
  adapter: localStorageCache({ maxEntries: 100 }),
});

maxAge

It is the maximum age of the entry in milliseconds or human-readable format. When the limit is reached, the entry is deleted.

ts
import { cache, localStorageCache } from '@farfetched/core';

cache(characterQuery, {
  adapter: localStorageCache({ maxAge: '1h30min' }),
});

TIP

Due to browser internal limitations, the maxAge option is not precise. It is not guaranteed that the entry will be deleted exactly after the specified time. It can be deleted earlier or later. However, your application will never see outdated entries because cache operator always checks the age of the entry before returning it.

Combination

You can combine these options to achieve the desired behavior. For example, you can store data for 1 hour or 100 entries, whichever comes first.

ts
import { cache, localStorageCache } from '@farfetched/core';

cache(characterQuery, {
  adapter: localStorageCache({ maxAge: '1h30min', maxEntries: 100 }),
});

Purge all data from cache

Sometimes you need to purge all data from cache. For example, when you want to force user to reload all data after logout. Farfetched provides purgeoption of cache operator for this purpose.

ts
import { createEvent } from 'effector';
import { cache } from '@farfetched/core';

const logout = createEvent();

cache(characterQuery, { purge: logout });

document.getElementById('logout').addEventListener('click', () => {
  logout();
});

TIP

createEvent is a part of Effector API that creates Event that could be called and could be watched. In this example, we are calling logout event when user clicks on the logout button.

After calling purge Event, all data from cache will be deleted immediately.

Do not fetch fresh data

By default, Farfetched will fetch fresh data from the server immediately after Query is started even if data is already found in cache. So, default behavior is to fetch fresh data every time and provide stale data as soon as possible.

You can alter this behavior with staleAfter option of cache operator. It accepts a number of milliseconds or human-readable format and considers data as fresh if it is not older than the specified time.

ts
import { cache } from '@farfetched/core';

cache(characterQuery, { staleAfter: '10min' });

👆 if data is not older than 10 minutes, it will be considered as fresh and will not be fetched from the server.

Deep-dive

If you want to learn more about internal implementation of cache operator, consider reading the deep-dive article about it.

Released under the MIT License.