Render Cache
Sometimes we cache computation results, such as with the React useMemo, useCallback Hook. By caching, we can reduce the number of computations, thus reducing CPU resource usage and enhancing the user experience.
Caching the results of server-side rendering (SSR) can reduce the computation and rendering time for each server request, enabling faster page load speeds and improving the user experience. It also lowers the server load, saves computational resources, and speeds up user access.
Configuration
You can enable caching by configuring it in server/cache.[t|j]s
:
server/cache.ts
import type { CacheOption } from '@modern-js/runtime/server';
export const cacheOption: CacheOption = {
maxAge: 500, // ms
staleWhileRevalidate: 1000, // ms
};
Configuration Explanation
Cache Configuration
The caching policy is implemented based on stale-while-revalidate.
During the maxAge
period, cache content will be returned directly. After maxAge
but within staleWhileRevalidate
, the cache content will also be returned directly, but at the same time, re-rendering will be executed asynchronously.
Object type
export interface CacheControl {
maxAge: number;
staleWhileRevalidate: number;
customKey?: string | ((pathname: string) => string);
}
In this, customKey is used for custom cache key. By default, Modern.js will use the request pathname as key for caching, but in some cases, this may not meet your needs, so developers can customise it.
Function type
export type CacheOptionProvider = (
req: IncomingMessage,
) => Promise<CacheControl> | CacheControl;
Sometimes developers need to customise the cache key through req, which can be handled using the function form, as shown in the following code:
server/cache.ts
import type { CacheOption, CacheOptionProvider } from '@modern-js/runtime/server';
const provider: CacheOptionProvider = (req) => {
const { url, headers, ... } = req;
const key = computedKey(url, headers, ...);
return {
maxAge: 500, // ms
staleWhileRevalidate: 1000, // ms
customKey: key,
}
}
export const cacheOption: CacheOption = provider;
Mapping type
export type CacheOptions = Record<string, CacheControl | CacheOptionProvider>;
Sometimes, developers need to apply different caching policies for different routes. We also provide a mapping way for configuration, as shown in example below:
server/cache.ts
import type { CacheOption } from '@modern-js/runtime/server';
export const cacheOption: CacheOption = {
'/home': {
maxAge: 50,
staleWhileRevalidate: 100,
},
'/about': {
maxAge: 1000 * 60 * 60 * 24, // one day
staleWhileRevalidate: 1000 * 60 * 60 * 24 * 2 // two day
},
'*': (req) => { // If the above routes cannot be matched, it will match to '*'
const { url, headers, ... } = req;
const key = computedKey(url, headers, ...);
return {
maxAge: 500,
staleWhileRevalidate: 1000,
customKey: key,
}
}
}
- The route
http://xxx/home
will apply the first rule.
- The route
http://xxx/about
will apply the second rule.
- The route
http://xxx/abc
will apply the last rule.
The above-mentioned /home
and /about
will be used as patterns for matching, which means that /home/abc
will also comply with this rule.
Simultaneously, you can also include regular expression syntax in it, such as /home/.+
.
Cache Container
By default, Server will use memory for caching. But typically, services will be deployed on serverless. Each service access may be a new process, so caching cannot be applied every time.
Therefore, developers can also customise the cache container, which needs to implement the Container
interface.
export interface Container<K = string, V = string> {
/**
* Returns a specified element from the container. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Container.
* @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
*/
get: (key: K) => Promise<V | undefined>;
/**
* Adds a new element with a specified key and value to the container. If an element with the same key already exists, the element will be updated.
*
* The ttl indicates cache expiration time.
*/
set: (key: K, value: V, options?: { ttl?: number }) => Promise<this>;
/**
* @returns boolean indicating whether an element with the specified key exists or not.
*/
has: (key: K) => Promise<boolean>;
/**
* @returns true if an element in the container existed and has been removed, or false if the element does not exist.
*/
delete: (key: K) => Promise<boolean>;
}
As an example in the following code, a developer can implement a Redis container.
import type { Container, CacheOption } from '@modern-js/runtime/server';
class RedisContainer implements Container {
redis = new Redis();
async get(key: string) {
return this.redis.get(key);
}
async set(key: string, value: string): Promise<this> {
this.redis.set(key, value);
return this;
}
async has(key: string): Promise<boolean> {
return this.redis.has(key);
}
async delete(key: string): Promise<boolean> {
return this.redis.delete(key);
}
}
const container = new RedisContainer();
export const customContainer: Container = container;
export const cacheOption: CacheOption = {
maxAge: 500, // ms
staleWhileRevalidate: 1000, // ms
};
Cache Identification
When the rendering cache is activated, Modern.js will identify the cache status of the current request through the response header x-render-cache
.
The following is an example of a response.
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< content-type: text/html; charset=utf-8
< x-render-cache: hit
< Date: Thu, 29 Feb 2024 02:46:49 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 2937
The x-render-cache
may have the following values.
Name |
Description |
hit |
Cache hit, returns cached content |
stale |
Cache hit, but data is stale, returns cached content while rendering again |
expired |
Cache hit, returns rendering result after re-rendering |
miss |
Cache miss |