Skip to content

Home of flexible API to replace the default Next.js cache, accommodating custom cache solutions for multi-instance deployments.

License

Notifications You must be signed in to change notification settings

itzsrikanth/next-shared-cache

Repository files navigation

neshca or next-shared-cache

This is a monorepo for @neshca/cache-handler package that provides a cache handler for Next.js Incremental Static Regeneration (ISR). It is meant to be used with the experimental.incrementalCacheHandlerPath configuration option of Next.js. More information about this option can be found in the Next.js documentation.

Native Next.js ISR cache can't be shared between multiple instances. @neshca/cache-handler on the other hand can be used with a local or remote cache store. So you can share the cache between multiple instances of your application. In the example below, you can see how to use Redis as a cache store.

Sharing the cache between multiple instances is useful if you are using a load balancer or Kubernetes for deployment.

Installation

To install the @neshca/cache-handler package, run the following command:

npm install -D @neshca/cache-handler

Usage with Redis

Create a file called cache-handler.js next to you next.config.js with the following contents:

const { IncrementalCache } = require('@neshca/cache-handler');
const { reviveFromBase64Representation, replaceJsonWithBase64 } = require('@neshca/json-replacer-reviver');
const { createClient } = require('redis');

const client = createClient({
    url: process.env.REDIS_URL,
    name: 'app:cache-testing',
});

client.connect().then();

client.on('error', (err) => {
    console.log('Redis Client Error', err);
});

IncrementalCache.prefix = 'app:cache-testing:';

IncrementalCache.cache = {
    async get(...args) {
        const result = await client.get(...args);

        if (!result) {
            return null;
        }

        try {
            return JSON.parse(result, reviveFromBase64Representation);
        } catch (error) {
            return null;
        }
    },
    async set(key, value, ttl) {
        await client.set(key, JSON.stringify(value, replaceJsonWithBase64), { EX: ttl });
    },
    async getTagsManifest(prefix) {
        const tagsManifest = await client.hGetAll(`${prefix}tagsManifest`);

        if (!tagsManifest) {
            return { version: 1, items: {} };
        }

        const items = {};

        for (const [tag, revalidatedAt] of Object.entries(tagsManifest)) {
            items[tag] = { revalidatedAt: parseInt(revalidatedAt ?? '0', 10) };
        }

        return { version: 1, items };
    },
    async revalidateTag(prefix, tag, revalidatedAt) {
        const options = {
            [tag]: revalidatedAt,
        };

        await client.hSet(`${prefix}tagsManifest`, options);
    },
};

module.exports = IncrementalCache;

Then, use the following configuration in your next.config.js file:

/** @type {import('next').NextConfig} */
const nextConfig = {
    experimental: {
        incrementalCacheHandlerPath: require.resolve('./cache-handler'), // path to the cache handler file you created
        isrFlushToDisk: false, // disable writing cache to disk
    },
};

module.exports = nextConfig;

Use the REDIS_URL environment variable to set the URL of your Redis instance.

REDIS_URL=redis://localhost:6379/

Developing

To get started with development, install the project dependencies using npm ci. Then, run npm run dev to start the development server and watch for changes to the handler and server modules:

npm ci
npm run build
npm run dev

In a separate terminal, run the cache-testing app using npm run build:app and npm run start:app. Note that you need to rebuild the cache-testing app every time the handler module is rebuilt:

npm run build:app
npm run start:app

Remember that you need to rebuild cache-testing every time handler rebuild is happening.

Using Redis

To use Redis as the cache store, you can use Docker to start a Redis instance with the following command:

docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest

Then, create a .env.local file inside the apps/cache-testing directory and add the REDIS_URL environment variable:

REDIS_URL=redis://localhost:6379

Finally, use the handler-redis module in next.config.js to configure the cache handler. The isrFlushToDisk field is required and should be set to false:

const nextConfig = {
    experimental: {
        incrementalCacheHandlerPath: require.resolve('./cache-handler-redis'),
        isrFlushToDisk: false,
    },
};

Then, run the following commands to start the development server and the cache-testing app:

npm run dev:redis
npm run build:app
npm run start:app

Contributing

Before committing changes, run the linting and formatting jobs using npm run lint and npm run format.

npm run lint
npm run format

Caveats

In-memory cache in dev mode

When you run npm run dev the server package is in watch mode. So it drops in-memory cache every time you make a change in sources.

Troubleshooting

If you are using VS Code and see a Parsing error: Cannot find module 'next/babel' error in .js files, create a .vscode/settings.json file in the root of the project and add the following configuration:

{
    "eslint.workingDirectories": [{ "pattern": "apps/*/" }, { "pattern": "packages/*/" }, { "pattern": "utils/*/" }]
}

About

Home of flexible API to replace the default Next.js cache, accommodating custom cache solutions for multi-instance deployments.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 79.9%
  • JavaScript 17.3%
  • CSS 2.0%
  • Shell 0.8%