logo
  • Guide
  • Config
  • Plugin
  • API
  • Examples
  • Community
  • Modern.js 2.x Docs
  • English
    • 简体中文
    • English
    • Start
      Introduction
      Quick Start
      Upgrading
      Glossary
      Tech Stack
      Core Concept
      Page Entry
      Build Engine
      Web Server
      Basic Features
      Routes
      Routing
      Config Routes
      Data Solution
      Data Fetching
      Data Writing
      Data Caching
      Rendering
      Server-Side Rendering
      Streaming SSR
      Rendering Cache
      Static Site Generation
      Render Preprocessing
      Styling
      Styling
      Use CSS Modules
      Using CSS-in-JS
      Using Tailwind CSS
      HTML Template
      Import Static Assets
      Import JSON Files
      Import SVG Assets
      Import Wasm Assets
      Debug
      Data Mocking
      Network Proxy
      Using Rsdoctor
      Using Storybook
      Testing
      Playwright
      Vitest
      Jest
      Cypress
      Path Alias
      Environment Variables
      Output Files
      Deploy Application
      Advanced Features
      Using Rspack
      Using BFF
      Basic Usage
      Runtime Framework
      Extend BFF Server
      Extend Request SDK
      File Upload
      Cross-Project Invocation
      Optimize Page Performance
      Code Splitting
      Inline Static Assets
      Bundle Size Optimization
      React Compiler
      Improve Build Performance
      Browser Compatibility
      Low-Level Tools
      Source Code Build Mode
      Server Monitor
      Monitors
      Logs Events
      Metrics Events
      Internationalization
      Basic Concepts
      Quick Start
      Configuration
      Locale Detection
      Resource Loading
      Routing Integration
      API Reference
      Advanced Usage
      Best Practices
      Custom Web Server
      Topic Detail
      Module Federation
      Introduction
      Getting Started
      Application-Level Modules
      Server-Side Rendering
      Deployment
      Integrating Internationalization
      FAQ
      Dependencies FAQ
      CLI FAQ
      Build FAQ
      HMR FAQ
      Deprecated
      📝 Edit this page
      Previous pageStreaming SSRNext pageStatic Site Generation

      #Rendering Cache

      When developing applications, sometimes we cache computation results using hooks like React's useMemo and useCallback. By leveraging caching, we can reduce the number of computations, thus saving CPU resources and improving user experience.

      Modern.js supports caching server-side rendering (SSR) results, reducing the computational and rendering time during subsequent requests. This accelerates page load time and improves user experience. Additionally, caching lowers server load, conserves computational resources, and speeds up user access.

      Tip

      Requires version x.43.0+

      #Configuration

      Create a server/cache.[t|j]s file in your application and export the cacheOption configuration to enable SSR rendering cache:

      server/cache.ts
      import type { CacheOption } from '@modern-js/server-runtime;
      
      export const cacheOption: CacheOption = {
        maxAge: 500, // ms
        staleWhileRevalidate: 1000, // ms
      };

      #Configuration Details

      #Cache Configuration

      The caching strategy implements stale-while-revalidate.

      Within the maxAge period, the cache content is directly returned. Exceeding maxAge but within staleWhileRevalidate, the cache content is still returned directly, but it re-renders asynchronously.

      Object Type

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

      Here, customKey is the custom cache key. By default, Modern.js uses the request pathname as the cache key, but developers can define it when necessary.

      Function Type

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

      Sometimes, developers need to use req to customize the cache key, or prevent caching for specific URLs. You can configure this as a function, as shown:

      server/cache.ts
      import type { CacheOption, CacheOptionProvider } from '@modern-js/server-runtime;
      
      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 Type

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

      Sometimes, different routes require different caching strategies. We also offer a mapping configuration method, as shown below:

      server/cache.ts
      import type { CacheOption } from '@modern-js/server-runtime;
      
      export const cacheOption: CacheOption = {
        '/home': {
          maxAge: 50,
          staleWhileRevalidate: 100,
        },
        '/about': {
          maxAge: 1000 * 60 * 60 * 24, // one day
          staleWhileRevalidate: 1000 * 60 * 60 * 24 * 2 // two days
        },
        '*': (req) => { // If no above route matches, this applies
          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 /home and /about are patterns, meaning /home/abc will also match. You can use regex in these patterns, such as /home/.+.

      #Cache Container

      By default, the server uses memory for caching. Typically, services are deployed in a Serverless container, creating a new process for each access, making it impossible to use the previous cache.

      Thus, Modern.js allows developers to define custom cache containers. Containers must 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>;
      }

      Developers can implement a Redis cache container as shown below:

      import Redis from 'ioredis';
      import type { Container, CacheOption } from '@modern-js/server-runtime;
      
      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.exists(key) > 0;
        }
      
        async delete(key: string): Promise<boolean> {
          return this.redis.del(key) > 0;
        }
      }
      
      const container = new RedisContainer();
      
      export const customContainer: Container = container;
      
      export const cacheOption: CacheOption = {
        maxAge: 500, // ms
        staleWhileRevalidate: 1000, // ms
      };

      #Cache Identification

      When rendering cache is enabled, Modern.js identifies the cache status of the current request through the x-render-cache response header. Here's an example 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 header can have the following values:

      NameDescription
      hitCache hit, returned cache content
      staleCache hit, but data is stale, returned cache content and re-rendered asynchronously
      expiredCache expired, re-rendered and returned new content
      missCache missed