渲染缓存

在开发应用时,有时我们会将计算结果进行缓存,例如使用 React useMemouseCallback 等 Hook。通过缓存我们可以减少计算的次数来减少 CPU 资源占用,提高用户体验。

Modern.js 支持将服务器端渲染(SSR)结果进行缓存,减少服务器每次请求时的计算和渲染时间,从而加速页面加载速度,提高用户体验。同时,缓存也能降低服务端负载,节省计算资源,提高用户访问速度。

TIP

需要 x.43.0+

配置方式

在应用中创建 server/cache.[t|j]s 文件,并导出 cacheOption 配置缓存即可开启 SSR 渲染缓存:

server/cache.ts
import type { CacheOption } from '@modern-js/runtime/server';

export const cacheOption: CacheOption = {
  maxAge: 500, // ms
  staleWhileRevalidate: 1000, // ms
};

配置说明

缓存配置

缓存策略参考 stale-while-revalidate 进行实现。

maxAge 时间内会直接返回缓存内容,超过 maxAge 但在 staleWhileRevalidate 内也会直接返回缓存内容,但同时会异步做一次重新渲染。

Object 类型

export interface CacheControl {
  maxAge: number;
  staleWhileRevalidate: number;
  customKey?: string | ((pathname: string) => string);
}

其中 customKey 为自定义缓存 key。默认情况下 Modern.js 将会把请求 pathname 作为 key 进行缓存,但在某些情况下这不能满足你的需求,开发者可以进行自定义。

Function 类型

export type CacheOptionProvider = (
  req: IncomingMessage,
) => Promise<CacheControl | false> | CacheControl | false;

有时开发者需要通过 req 来自定义缓存 key,或者特定 URL 时缓存不生效,可以配置为函数的形式进行处理, 例如以下代码:

server/cache.ts
import type { CacheOption, CacheOptionProvider } from '@modern-js/runtime/server';

const provider: CacheOptionProvider = (req) => {
  const { url, headers, ... } = req;
  if(url.includes('no-cache=1')) {
    return false;
  }

  const key = computedKey(url, headers, ...);
  return {
    maxAge: 500, // ms
    staleWhileRevalidate: 1000, // ms
    customKey: key,
  }
}

export const cacheOption: CacheOption = provider;

Mapping 类型

export type CacheOptions = Record<string, CacheControl | CacheOptionProvider>;

有时开发者面对不同的路由需要应用不同的缓存策略。我们也提供一种映射的方式进行配置, 以下列代码为例:

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) => { // 若上述路由无法匹配,则会匹配到 '*'
    const { url, headers, ... } = req;
    const key = computedKey(url, headers, ...);

    return {
      maxAge: 500,
      staleWhileRevalidate: 1000,
      customKey: key,
    }
  }
}
  • 路由 http://xxx/home 将会应用第一条规则。
  • 路由 http://xxx/about 将会应用第二条规则。
  • 路由 http://xxx/abc 将会应用最后一条规则。

上述 /home/about 将会作为模式进行匹配,这意味着 /home/abc 也会匹配上该规则。同时,你也可以在其中编写正则语法:/home/.+

缓存容器

默认情况下,Server 将会使用内存进行缓存。但通常情况下服务将会部署在 Serverless 容器上。每一次的服务访问可能都是一个新的进程,这样每次访问都不能应用缓存。

因此,Modern.js 支持开发者自定义缓存容器,容器需实现接口 Container

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>;
}

以下面代码为例,开发者可实现一个 redis 缓存容器。

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
};

缓存标识

当开启渲染缓存后,Modern.js 将通过响应头 x-render-cache 来标识当前请求的缓存状态。下面是一个响应示例:

< 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

x-render-cache 可能会有以下几种值:

名称 说明
hit 缓存命中,返回缓存内容
stale 缓存命中,但数据陈旧,返回缓存内容的同时做重新渲染
expired 缓存命中,重新渲染后返回渲染结果
miss 缓存未命中