FrameworkEdit this page

Caching

@lazarv/react-server provides a caching mechanism for the rendering response and a built-in in-memory cache for any value with TTL outage and compound cache keys.

You can enable response caching when you use a server component with the withCache wrapper or using the useResponseCache hook. Not only the wrapped component or the component where you used the cache hook will be cached, but the entire HTTP response where the cache was enabled.

The response cache is using both a cache provider and HTTP Cache-Control with stale-while-revalidate. The server-side cache is used for any subsequent requests until the cache is invalidated. The client-side cache is used for any subsequent requests from the same client.

import { withCache } from "@lazarv/react-server";

export default withCache(async function App() {
  return <div>{Math.random()}</div>;
}, 30 * 1000);
import { useResponseCache } from "@lazarv/react-server";

export default async function App() {
  useResponseCache(30 * 1000);

  return <div>{Math.random()}</div>;
}

You can use the in-memory cache by importing the useCache helper function from @lazarv/react-server. You can use this caching solution to cache any async value with a TTL outage and a compound cache key. The cache is shared between all server components.

import { useCache } from "@lazarv/react-server";
import { readFile } from "node:fs/promises";

export default async function FileContent({ filename }) {
  const file = await useCache(
    ["file", filename],
    async () => readFile(filename, "utf-8"),
    30 * 1000,
  );

  return <pre>{file}</pre>;
}

You can use the revalidate function to revalidate the cache using a compound key. Calling this function will instantly invalidate the cache for the given key. This function is only available in server components.

import { revalidate } from "@lazarv/react-server";

export default async function App() {
  return (
    <div>
      <FileContent filename="temp.txt" />
      <form
        action={async () => {
          "use server";
          revalidate(["file", filename]);
          redirect("/");
        }}
      >
        <button type="submit">Refresh</button>
      </form>
    </div>
  );
}

The "use cache" directive can be used to enable caching in any function. The directive accepts the profile, ttl, and tags options. The profile option is used to specify the cache profile to use. The ttl option is used to specify the time-to-live for the cache in milliseconds, which overrides the ttl option from the cache profile. The tags option is used to specify the tags for the cache key.

You can use the tags option to specify a comma-separated list of tags to address when invalidating the cache for a specific group of tags. For example, if you have a function that fetches todos and you want to invalidate the cache for all todos, you can use the tags option to add the todos tag to the cache key.

App.jsx
import { invalidate } from "@lazarv/react-server"; async function getTodos() { "use cache; ttl=200; tags=todos"; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); return { timestamp: Date.now(), data: await res.json(), }; } export default async function App() { const todos = await getTodos(); return ( <form action={async () => { "use server"; await invalidate(getTodos); }} > <button type="submit">Refresh</button> <pre>{JSON.stringify(todos, null, 2)}</pre> </form> ); }

Cache profiles are defined in the server configuration. You can specify any number of cache profiles and reference them by name in the "use cache" directive. The cache profile can include the ttl and tags options which will be used when the "use cache" directive does not specify them.

react-server.config.json
{ "cache": { "profiles": { "todos": { "ttl": 30000, "tags": "todos" } } } }

After defining the cache profiles, you can reference them by name in the "use cache" directive.

App.jsx
async function getTodos() { "use cache; profile=todos"; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); return { timestamp: Date.now(), data: await res.json(), }; }

You can use different cache providers to store the cached data. The default cache provider is an in-memory cache, but you can also use file-based caching or any other custom cache provider.

To use a specific cache provider with the "use cache" directive, you can specify provider by using the "use cache: <provider>;" syntax. This allows you to use different cache providers for different parts of your application.

async function getTodos() {
  "use cache: file; tags=todos";
  const res = await fetch("https://jsonplaceholder.typicode.com/todos");
  return {
    timestamp: Date.now(),
    data: await res.json(),
  };
}

@lazarv/react-server uses the Unstorage library to provide a unified API for different storage backends. You can use any of the available drivers, such as fs, localStorage, memory, or any custom driver you create.

With cache providers, you can use the "use cache" directive even with "use client" components. This allows you to cache data on the client side and use it in your client components. You can use the built-in local or session cache providers to store the cached data in the browser's local storage or session storage, while you can use an existing or implement a new custom cache driver to store the cached data in a different storage backend, like the indexedb driver from Unstorage.

To define the cache provider, you can use the cache.providers option in the server configuration. The driver option specifies the driver using the module path, and the options option specifies the options for the driver.

export default {
  cache: {
    providers: {
      file: {
        driver: "unstorage/drivers/fs",
        options: {
          base: ".cache",
        },
      }
    }
  },
};

You can also set a special option for your provider, called type, to indicate the type of the cache provider. This is useful for @lazarv/react-server to know how to handle the cached value. Set type to "raw" to use setItemRaw from Unstorage when storing the cache value. This is useful for caching memory structs, like a React tree, without encoding them to a string when using an in-memory cache driver, like LRU.

export default {
  cache: {
    providers: {
      file: {
        driver: "unstorage/drivers/lru",
        options: {
          type: "raw",
        },
      }
    }
  },
};

You can also use the cache.provider option to set the default cache provider for the server. This will be used when no specific cache provider is specified in the "use cache" directive.

export default {
  cache: {
    provider: {
      default: "unstorage/drivers/lru",
    },
  },
};

You can also specify cache provider aliases in the server configuration. This allows you to route cache requests to different providers based on the alias used in the "use cache" directive. You can also override the default cache provider for specific aliases.

export default {
  cache: {
    provider: {
      default: "lru",
      lru: "unstorage/drivers/lru",
    },
  },
};

Using cache provider aliases allows you to easily switch between different cache providers without changing the code in your application. This allows you to switch between different caching strategies, such as in-memory caching, file-based caching, or any other custom caching solution, without modifying the code that uses the cache, or cache provider configurations using the same storage driver.

export default {
  cache: {
    provider: {
      default: "smallLRU",
      smallLRU: {
        driver: "unstorage/drivers/lru",
        options: {
          maxSize: 1000, // Set a smaller size for this alias
          type: "raw",
        },
      },
      largeLRU: {
        driver: "unstorage/drivers/lru",
        options: {
          maxSize: 10000, // Set a larger size for this alias
          type: "raw",
        },
      },
    },
  },
};

@lazarv/react-server provides a few built-in cache providers that you can use out of the box without any configuration. These are:

Storing a React component with a cache provider which is not an in-memory caching solution needs the component to be serialized. You can use the RSC format to save the component state. To use the RSC serialization in a cache provider, you can set the type option to rsc in the cache provider configuration. This will use the RSC serialization format when storing the component in the cache.

export default {
  cache: {
    providers: {
      file: {
        driver: "unstorage/drivers/fs",
        options: {
          base: ".cache",
          type: "rsc",
        },
      }
    }
  },
};

You can also specify the encoding for the stored RSC data, which is base64 by default. You can set any standard Node.js buffer encoding, such as utf8, hex, or binary. This is useful for storing the RSC data in a specific format that is compatible with your storage backend. To specify the encoding, you can set the type option to rsc;<encoding> or the encoding option in the cache provider configuration. You can use the encoding option for your cache driver when necessary, by specifying the encoding with the type option. This is useful for storing your cached data with different encodings when the cache driver supports it's own encoding option.

export default {
  cache: {
    providers: {
      file: {
        driver: "unstorage/drivers/fs",
        options: {
          base: ".cache",
          type: "rsc;utf8",
          // or
          type: "rsc",
          encoding: "utf8",
        },
      }
    }
  },
};

The RSC serializer is available for you to use for your own needs. You can use the @lazarv/react-server/rsc module to convert a React tree to a buffer or stream and back. The buffer is an Uint8Array instance and the stream is a ReadableStream instance. You can use the toBuffer and fromBuffer functions to convert a React tree to a buffer and back, or the toStream and fromStream functions to convert a React tree to a stream and back.

import { toBuffer, fromBuffer } from "@lazarv/react-server/rsc";

const buffer = await toBuffer(<div>Hello world</div>);
const tree = await fromBuffer(buffer);

const stream = await toStream(<div>Hello world</div>);
const tree = await fromStream(stream);

You can only access RSC serialization in server components for now, but it might be available in the future in a browser environment. Stay tuned for updates!