nextjs-cache-handler

repository·master·Indexed 41 days ago

https://github.com/fortedigital/nextjs-cache-handler

A caching utility for Next.js 15 and 16 providing specialized cache handlers, primarily focused on Redis-based caching. It supports Fetch API caching, tag-based invalidation, ISR, and provides a composite handler for routing across multiple backends. Includes support for @redis/client and ioredis, as well as a local-lru handler for development.

Tokens
11.2K
Snippets
36
Records
49
Agent score
52%

What's inside nextjs-cache-handler

  1. Implement a custom value serializer for Redis

    master

    By default, the redis-strings handler uses JSON.stringify and JSON.parse. You can provide a custom valueSerializer object to implement compression (e.g., gzip, brotli) or encryption.

    Contract:

    • serialize(value): Receives the full cache object (including tags, lastModified, lifespan, and value). Must return a string suitable for Redis SET.
    • deserialize(stored): Receives the string from Redis GET. Must return a parsed object compatible with the handler, or null to trigger a cache miss.

    Note: Changing the serializer makes existing keys unreadable. You must flush the cache or use a new keyPrefix when deploying a change to the serializer.

    // Example: gzip compression
    import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
    import { gzipSync, gunzipSync } from "zlib";
    
    const redisCacheHandler = createRedisHandler({
      client: redisClient,
      keyPrefix: "nextjs:",
      valueSerializer: {
        serialize: (value) => gzipSync(JSON.stringify(value)).toString("base64"),
        deserialize: (stored) => {
          const parsed = JSON.parse(
            gunzipSync(Buffer.from(stored, "base64")).toString("utf-8"),
          );
          return parsed;
        },
      },
    });
  2. Next.js Compatibility and Version Requirements

    master

    The package versioning is tied to Next.js major versions:

    • Next.js 15: Requires @fortedigital/nextjs-cache-handler version 2.0.0+ (version 3.0.0+ is recommended).
    • Next.js 16: Requires @fortedigital/nextjs-cache-handler version 3.0.0+.

    Critical Warning for Next.js 16.3.0+

    Starting with Next.js 16.3.0, prefetch requests for the app router's route tree (/_tree) require segmentData to be present in the cache entry.

    You must use version 3.3.0 or later if you are on Next.js 16.3.0+. Versions prior to 3.3.0 will cause the Next.js client to receive unparseable responses for /_tree prefetch requests, leading to infinite retries.

  3. Use `ioredis` with the Redis handler

    master

    If you prefer using the ioredis library instead of @redis/client, use the ioredisAdapter helper to wrap your client before passing it to createRedisHandler.

    import Redis from "ioredis";
    import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
    import { ioredisAdapter } from "@fortedigital/nextjs-cache-handler/helpers/ioredisAdapter";
    
    const client = new Redis(process.env.REDIS_URL);
    const redisClient = ioredisAdapter(client);
    
    const redisHandler = createRedisHandler({
      client: redisClient,
      keyPrefix: "my-app:",
    });
  4. Install @fortedigital/nextjs-cache-handler

    master

    Install the package using npm:

    npm i @fortedigital/nextjs-cache-handler

    Important for Upgrades: If you are upgrading from Next.js 14 or earlier, you must flush your Redis cache before running the new version. Cache formats between Next.js 14 and 15 are incompatible.

    npm i @fortedigital/nextjs-cache-handler
  5. Update Cache Handler export for 2.x.x+

    master

    In version 2.x.x and later, the way you export the cache handler has changed. You should now import CacheHandler directly from the main package instead of using the specific Next15CacheHandler from the subpath.

    Additionally, createBufferStringHandler is now integrated into redis-strings and is no longer required as a separate step for Next.js 15+.

    // After (2.x.x+)
    const { CacheHandler } = require("@fortedigital/nextjs-cache-handler");
    module.exports = CacheHandler;
  6. Install and run the Redis-minimal example

    master

    To run the Next.js Cache Handler example application, install the dependencies and use production mode.

    Note: Next.js does not use the cache handler in development mode. To test caching, you must build and start the application in production mode.

    # Install dependencies
    npm i
    
    # Run in production mode (Required for caching)
    npm run build
    npm run start
    
    # Run in development mode (Caching is disabled)
    npm run dev
  7. Implement a production-ready CacheHandler

    master

    To use @fortedigital/nextjs-cache-handler in production, you must implement a cache-handler.js file. It is highly recommended to use the global scope to manage the handler instance and the Redis connection. This prevents race conditions and ensures only one connection is established during the Next.js build and runtime phases.

    Key implementation details:

    • Use CacheHandler.onCreation() to initialize your handlers.
    • Use global.cacheHandlerConfig and global.cacheHandlerConfigPromise to cache the instance.
    • For local development, you can fallback to a local LRU cache to avoid requiring Redis.
    • In production, use createRedisHandler for persistent storage and createCompositeHandler if you want to layer multiple handlers (e.g., LRU + Redis).
    import { createClient } from "redis";
    import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
    import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
    import createLruHandler from "@fortedigital/nextjs-cache-handler/local-lru";
    import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
    import createCompositeHandler from "@fortedigital/nextjs-cache-handler/composite";
    
    CacheHandler.onCreation(() => {
      if (global.cacheHandlerConfig) return global.cacheHandlerConfig;
      if (global.cacheHandlerConfigPromise) return global.cacheHandlerConfigPromise;
    
      if (process.env.NODE_ENV === "development") {
        const lruCache = createLruHandler();
        return { handlers: [lruCache] };
      }
    
      global.cacheHandlerConfigPromise = (async () => {
        let redisClient = null;
        const settings = {
          url: process.env.REDIS_URL,
          pingInterval: 10000,
        };
    
        if (process.env.REDIS_ACCESS_KEY) {
          settings.password = process.env.REDIS_ACCESS_KEY;
        }
    
        try {
          redisClient = createClient(settings);
          await redisClient.connect();
        } catch (error) {
          console.warn("Failed to connect Redis client:", error);
        }
    
        const lruCache = createLruHandler();
    
        if (!redisClient?.isReady) {
          global.cacheHandlerConfig = { handlers: [lruCache] };
          return global.cacheHandlerConfig;
        }
    
        const redisCacheHandler = createRedisHandler({
          client: redisClient,
          keyPrefix: "nextjs:",
        });
    
        global.cacheHandlerConfig = {
          handlers: [
            createCompositeHandler({
              handlers: [lruCache, redisCacheHandler],
              setStrategy: (ctx) => (ctx?.tags.includes("memory-cache") ? 0 : 1),
            }),
          ],
        };
    
        return global.cacheHandlerConfig;
      })();
    
      return global.cacheHandlerConfigPromise;
    });
    
    export default CacheHandler;
  8. Swap from @neshca/cache-handler to @fortedigital/nextjs-cache-handler

    master

    If you are migrating from @neshca/cache-handler, you only need to update your package imports. The API remains compatible.

    Update Cache Handler

    Replace the import in your cache-handler.mjs:

    Before:

    import { CacheHandler } from "@neshca/cache-handler";

    After:

    import { CacheHandler } from "@fortedigital/nextjs-cache-handler";

    Update Instrumentation

    Replace the import in your instrumentation.ts:

    Before:

    const { registerInitialCache } = await import("@neshca/cache-handler/instrumentation");

    After:

    const { registerInitialCache } = await import("@fortedigital/nextjs-cache-handler/instrumentation");
    // Example of the 'After' state for cache-handler.mjs
    import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
    
    CacheHandler.onCreation(() => {
      // setup
    });
    
    export default CacheHandler;
  9. Migrate Instrumentation import for version 1.3.x

    master

    When upgrading from version 1.2.x to ^1.3.x, the registerInitialCache function was moved from @neshca/cache-handler/instrumentation to @fortedigital/nextjs-cache-handler/instrumentation. Ensure your instrumentation logic uses the @fortedigital package path.

    // After migration to ^1.3.x
    if (process.env.NEXT_RUNTIME === "nodejs") {
      const { registerInitialCache } = await import(
        "@fortedigital/nextjs-cache-handler/instrumentation"
      );
      const CacheHandler = (await import("./cache-handler.js")).default;
      await registerInitialCache(CacheHandler);
    }
  10. Quick Start: Setup Redis-based caching

    master

    To implement a minimal Redis-based cache handler, follow these two steps:

    1. Create a cache-handler.mjs file to configure the CacheHandler with a Redis client using createRedisHandler from the redis-strings entry point.
    2. Register the handler in your next.config.js using require.resolve.
    // cache-handler.mjs
    import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
    import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
    import { createClient } from "redis";
    
    const client = createClient({ url: process.env.REDIS_URL });
    await client.connect();
    
    CacheHandler.onCreation(() => ({
      handlers: [createRedisHandler({ client })],
    }));
    
    export default CacheHandler;
    // next.config.js
    module.exports = {
      cacheHandler: require.resolve("./cache-handler.mjs"),
    };