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.jsximport { 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.jsxasync 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:
memory
: A simple in-memory cache provider. This is the default cache provider.request
: A cache provider that only lives for the duration of the request. This is useful for caching data that is only needed for the current request.null
: A cache provider that does not store any data. This is useful for disabling caching in specific parts of your application. Useful with a cache provider alias.local
: A cache provider that uses the browser's local storage. This is useful for caching data that needs to persist across page reloads.session
: A cache provider that uses the browser's session storage. This is useful for caching data that needs to persist across page reloads, but only for the current session.
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!