A caching utility built originally on top of @neshca/cache-handler, providing additional cache handlers for specialized use cases with a focus on Redis-based caching.
Starting from version 2.0.0, this package no longer depends on @neshca/cache-handler and is fully maintained and compatible with Next.js 15 and partially 16. See the compatibility matrix for detailed feature support.
Version Requirements:
- Next.js 15: Version 2.0.0+ (version 3.0.0+ recommended for latest improvements and maintenance development)
- Next.js 16: Version 3.0.0+ required
- Documentation
- Migration
- Prerequisites
- Installation
- Quick Start
- Next.js Compatibility
- Migration
- Handlers
- Examples
- Reference to Original Package
- API Reference Links
- Troubleshooting
- Legacy / Deprecated
- Contributing
- License
The documentation at @neshca/cache-handler - caching-tools.github.io/next-shared-cache is mostly still relevant, though some details may be outdated. New features or relevant changes are described below.
If you already use @neshca/cache-handler the setup is very streamlined and you just need to replace package references. If you're starting fresh please check the example project.
Before:
// cache-handler.mjs
import { CacheHandler } from "@neshca/cache-handler";
CacheHandler.onCreation(() => {
// setup
});
export default CacheHandler;After:
// cache-handler.mjs
import { CacheHandler } from "@fortedigital/nextjs-cache-handler";
CacheHandler.onCreation(() => {
// setup
});
export default CacheHandler;Before:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { registerInitialCache } =
await import("@neshca/cache-handler/instrumentation");
const CacheHandler = (await import("../cache-handler.mjs")).default;
await registerInitialCache(CacheHandler);
}
}After:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { registerInitialCache } =
await import("@fortedigital/nextjs-cache-handler/instrumentation");
const CacheHandler = (await import("../cache-handler.mjs")).default;
await registerInitialCache(CacheHandler);
}
}Before installing, ensure you have:
- Node.js >= 22.0.0
- Next.js >= 15.2.4 (for version 2.0.0+) or >= 16.0.0 (for version 3.0.0+)
- Redis >= 5.5.6 (or compatible Redis-compatible service)
- pnpm >= 9.0.0 (for development)
Important: This package only supports the official
redispackage (also known asnode-redis). Theioredispackage is not supported.
See Version Requirements for package version compatibility.
npm i @fortedigital/nextjs-cache-handler
If upgrading from Next 14 or earlier, flush your Redis cache before running new version of the application locally and on your hosted environments. Cache formats between Next 14 and 15 are incompatible.
Here's a minimal setup to get started:
// 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;Then configure it in your next.config.js:
// next.config.js
module.exports = {
cacheHandler: require.resolve("./cache-handler.mjs"),
};For a complete example with error handling, fallbacks, and production setup, see the Examples section below. The quick start code is not meant for production use.
The original @neshca/cache-handler package does not support Next.js 15.
Prior to 2.0.0, this package provided wrappers and enhancements to allow using @neshca/cache-handler with Next.js 15.
From version 2.0.0 onward, @fortedigital/nextjs-cache-handler is a standalone solution with no dependency on @neshca/cache-handler and is fully compatible with Next.js 15 and redis 5.
Version Requirements:
- Next.js 15: Version 2.0.0+ (version 3.0.0+ recommended for latest improvements and maintenance development)
- Next.js 16: Version 3.0.0+ required
We aim to keep up with new Next.js releases and will introduce major changes with appropriate version bumps.
| Feature | Next.js 15 | Next.js 16 | Notes |
|---|---|---|---|
| Fetch API Caching | |||
fetch with default cache (force-cache) |
✅ | ✅ | Default behavior, caches indefinitely |
fetch with no-store |
✅ | ✅ | Never caches, always fresh |
fetch with no-cache |
✅ | ✅ | Validates cache on each request |
fetch with next.revalidate |
✅ | ✅ | Time-based revalidation |
fetch with next.tags |
✅ | ✅ | Tag-based cache invalidation |
| Cache Invalidation | |||
revalidateTag(tag) |
✅ | N/A | Breaking change in Next.js 16 |
revalidateTag(tag, cacheLife) |
N/A | ✅ | New required API in Next.js 16 |
updateTag(tag) |
N/A | ✅ | New API for immediate invalidation in Server Actions |
revalidatePath(path) |
✅ | ✅ | Path-based revalidation |
revalidatePath(path, type) |
✅ | ✅ | Type-specific path revalidation |
| Function Caching | |||
unstable_cache() |
✅ | ✅ | Cache any function with tags and revalidation |
| Static Generation | |||
generateStaticParams() |
✅ | ✅ | Static params generation |
| ISR (Incremental Static Regeneration) | ✅ | ✅ | On-demand regeneration |
Route segment config (revalidate, dynamic, etc.) |
✅ | ✅ | All segment config options |
| Redis Client Support | |||
redis package (node-redis) |
✅ | ✅ | Official Redis client - fully supported |
ioredis package |
✅ | ✅ | IORedis client - fully supported |
| Next.js 16 New Features | |||
cacheHandlers config (for 'use cache') |
❌ | ❌ | Not yet supported - Help needed |
'use cache' directive |
❌ | ❌ | Not yet supported - Help needed |
'use cache: remote' directive |
❌ | ❌ | Not yet supported - Help needed |
'use cache: private' directive |
❌ | ❌ | Not yet supported - Help needed |
cacheComponents |
❌ | ❌ | Not yet supported - Help needed |
Notes:
revalidateTag()in Next.js 16 requires acacheLifeparameter ('max','hours', or'days'). This is a breaking change from Next.js 15.cacheLifeprofiles are primarily designed for Vercel's infrastructure. Custom cache handlers may not fully differentiate between differentcacheLifeprofiles.updateTag()is only available in Server Actions, not Route Handlers.- The new
cacheHandlersAPI and'use cache'directives are not yet supported by this package.
A Redis-based handler for key- and tag-based caching. Compared to the original implementation, it prevents memory leaks caused by growing shared tag maps by implementing TTL-bound hashmaps.
Note: This handler requires the official
redispackage.ioredisis not supported.
Features:
- Key expiration using
EXATorEXPIREAT - Tag-based revalidation
- Automatic TTL management
- Automatic buffer/string conversion for Next.js 15+ compatibility (previously required
buffer-string-decoratorin version 1.x.x) - Default
revalidateTagQuerySize:10_000(safe for large caches)
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
const redisHandler = await createRedisHandler({
client: createClient({
url: process.env.REDIS_URL,
}),
keyPrefix: "myApp:",
sharedTagsKey: "myTags",
sharedTagsTtlKey: "myTagTtls",
});import { createCluster } from "@redis/client";
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
import { withAdapter } from "@fortedigital/nextjs-cache-handler/cluster/adapter";
const { hostname: redisHostName } = new URL(process.env.REDIS_URL);
redis = withAdapter(
createCluster({
rootNodes: [{ url: process.env.REDIS_URL }],
// optional if you use TLS and need to resolve shards' ip to proper hostname
nodeAddressMap(address) {
const [_, port] = address.split(":");
return {
host: redisHostName,
port: Number(port),
};
},
}),
);
// after using withAdapter you can use redis cluster instance as parameter for createRedisHandler
const redisCacheHandler = createRedisHandler({
client: redis,
keyPrefix: CACHE_PREFIX,
});Note: Redis Cluster support is currently experimental and may have limitations or unexpected bugs. Use it with caution.
If you prefer using ioredis instead of @redis/client, you can use the ioredisAdapter helper.
npm i ioredis
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:",
});The local-lru Handler uses a lru-cache ↗ instance as the cache store. It stores the cache in memory and evicts the least recently used entries when the cache reaches its limits. You can use this Handler as a fallback cache when the shared cache is unavailable.
⚠️ The local-lru Handler is not suitable for production environments. It is intended for development and testing purposes only.
Features:
- Key expiration using
EXATorEXPIREAT - Tag-based revalidation
- Automatic TTL management
- Default
revalidateTagQuerySize:10_000(safe for large caches)
import createLruHandler from "@fortedigital/nextjs-cache-handler/local-lru";
const localHandler = createLruHandler({
maxItemsNumber: 10000,
maxItemSizeBytes: 1024 * 1024 * 500,
});Routes cache operations across multiple underlying handlers.
Features:
- Multiple backend support
- Custom routing strategies
- First-available read strategy
import createCompositeHandler from "@fortedigital/nextjs-cache-handler/composite";
const compositeHandler = createCompositeHandler({
handlers: [handler1, handler2],
setStrategy: (data) => (data?.tags.includes("handler1") ? 0 : 1),
});By default, registerInitialCache populates the cache by overwriting any existing
entries with values generated from build-time artifacts (fetch calls, pages, routes).
If you want to preserve values that may already exist in the cache (for example,
entries written at runtime by another instance), you can enable the
setOnlyIfNotExists option:
await registerInitialCache(CacheHandler, {
setOnlyIfNotExists: true,
});When enabled, cache writes performed during the initial cache registration will only occur if the corresponding cache key does not already exist. This allows you to explicitly choose the cache population strategy instead of enforcing a single default.
The example project provides a comprehensive demonstration of Next.js caching features with interactive examples:
- Default Cache - Demonstrates
force-cachebehavior - No Store - Shows
no-storefor always-fresh data - Time-based Revalidation - Automatic cache revalidation
- Fetch with Tags - Tag-based cache invalidation
- unstable_cache - Function caching with tags
- ISR - Incremental Static Regeneration
- Static Params - Dynamic route static generation
To run the examples:
pnpm install
cd examples/redis-minimal
npm run build
npm run startNote: Caching only works in production mode. See the examples README for more details.
Here's a complete production-ready cache-handler.js example:
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(() => {
// Important - It's recommended to use global scope to ensure only one Redis connection is made
// This ensures only one instance get created
if (global.cacheHandlerConfig) {
return global.cacheHandlerConfig;
}
// Important - It's recommended to use global scope to ensure only one Redis connection is made
// This ensures new instances are not created in a race condition
if (global.cacheHandlerConfigPromise) {
return global.cacheHandlerConfigPromise;
}
// You may need to ignore Redis locally, remove this block otherwise
if (process.env.NODE_ENV === "development") {
const lruCache = createLruHandler();
return { handlers: [lruCache] };
}
// Main promise initializing the handler
global.cacheHandlerConfigPromise = (async () => {
let redisClient = null;
if (PHASE_PRODUCTION_BUILD !== process.env.NEXT_PHASE) {
const settings = {
url: process.env.REDIS_URL,
pingInterval: 10000,
};
// This is optional and needed only if you use access keys
if (process.env.REDIS_ACCESS_KEY) {
settings.password = process.env.REDIS_ACCESS_KEY;
}
try {
redisClient = createClient(settings);
redisClient.on("error", (e) => {
if (typeof process.env.NEXT_PRIVATE_DEBUG_CACHE !== "undefined") {
console.warn("Redis error", e);
}
global.cacheHandlerConfig = null;
global.cacheHandlerConfigPromise = null;
});
} catch (error) {
console.warn("Failed to create Redis client:", error);
}
}
if (redisClient) {
try {
console.info("Connecting Redis client...");
await redisClient.connect();
console.info("Redis client connected.");
} catch (error) {
console.warn("Failed to connect Redis client:", error);
await redisClient
.disconnect()
.catch(() =>
console.warn(
"Failed to quit the Redis client after failing to connect.",
),
);
}
}
const lruCache = createLruHandler();
if (!redisClient?.isReady) {
console.error("Failed to initialize caching layer.");
global.cacheHandlerConfigPromise = null;
global.cacheHandlerConfig = { handlers: [lruCache] };
return global.cacheHandlerConfig;
}
const redisCacheHandler = createRedisHandler({
client: redisClient,
keyPrefix: "nextjs:",
});
global.cacheHandlerConfigPromise = null;
// This example uses composite handler to switch from Redis to LRU cache if tags contains `memory-cache` tag.
// You can skip composite and use Redis or LRU only.
global.cacheHandlerConfig = {
handlers: [
createCompositeHandler({
handlers: [lruCache, redisCacheHandler],
setStrategy: (ctx) => (ctx?.tags.includes("memory-cache") ? 0 : 1), // You can adjust strategy for deciding which cache should the composite use
}),
],
};
return global.cacheHandlerConfig;
})();
return global.cacheHandlerConfigPromise;
});
export default CacheHandler;This project was originally based on @neshca/cache-handler. Versions prior to 2.0.0 wrapped or extended the original. As of 2.0.0, this project is fully independent and no longer uses or requires @neshca/cache-handler.
For context or historical documentation, you may still reference the original project.
- Caching in Next.js - Comprehensive guide to Next.js caching
- Data Fetching, Caching, and Revalidating - Fetch API caching options
fetchAPI - Next.js fetch options (next.revalidate,next.tags)revalidateTag- Tag-based cache invalidationrevalidatePath- Path-based cache invalidationupdateTag- Immediate cache invalidation (Next.js 16)unstable_cache- Function caching- Route Segment Config -
revalidate,dynamic, etc. - Incremental Static Regeneration - ISR documentation
- Redis Client for Node.js - Official Redis client library
- Redis Documentation - Redis server documentation
- Redis Commands - Redis command reference
Issue: Caching doesn't seem to work when running npm run dev.
Solution: This is expected behavior. Next.js intentionally disables caching in development mode for faster hot reloading. To test caching functionality, you must use production mode:
npm run build
npm run startIssue: Getting connection errors or "Redis client is not ready" errors.
Solutions:
- Verify Redis is running:
redis-cli pingshould returnPONG - Check
REDIS_URLenvironment variable is set correctly - Ensure Redis is accessible from your application (check firewall/network settings)
- For production, verify Redis credentials and connection string format
- Check Redis logs for connection issues
Issue: Calling revalidateTag() doesn't seem to clear the cache.
Solutions:
- In Next.js 16, ensure you're using
revalidateTag(tag, cacheLife)with the requiredcacheLifeparameter - Verify the tag matches exactly (tags are case-sensitive)
- Check that the cache entry was created with the same tag
- In development mode, caching is disabled - test in production mode
Issue: Errors after upgrading from Next.js 14 to 15/16.
Solution: Cache formats between Next.js 14 and 15 are incompatible. You must flush your Redis cache before running the new version:
redis-cli FLUSHALLOr if using a specific database:
redis-cli -n <database-number> FLUSHDBIssue: Package version doesn't work with your Next.js version.
Solutions:
- Next.js 15: Use version 2.0.0+ (3.0.0+ recommended)
- Next.js 16: Use version 3.0.0+ (required)
- Check the Version Requirements section
- Verify your Node.js version is >= 22.0.0
Issue: Need to debug what's happening with the cache.
Solution: Enable debug logging by setting the environment variable:
NEXT_PRIVATE_DEBUG_CACHE=1 npm run startThis will output detailed cache operation logs to help diagnose issues.
This project uses Turborepo to manage the monorepo structure with the main package and examples.
- Node.js >= 22.0.0
- pnpm >= 9.0.0
- Start dev server:
pnpm dev(runs all dev servers in parallel) - Run all tests:
pnpm test
Migration: Use unstable_cache instead, which provides similar functionality with better Next.js integration.
neshClassicCache allows you to cache the results of expensive operations, like database queries, and reuse them across multiple requests. Unlike the neshCache or unstable_cache ↗ function, neshClassicCache must be used in a Next.js Pages Router allowing users to cache data in the getServerSideProps and API routes.
Note
Cache entries created with neshClassicCache can be revalidated only by the revalidateTag ↗ method.
fetchData- An asynchronous function that fetches the data you want to cache. It must be a function that returns aPromise.commonOptions- An object that controls how the cache behaves:tags- An array of tags to associate with the cached resultrevalidate- The revalidation interval in secondsargumentsSerializer- Function to serialize arguments (defaults toJSON.stringify)resultSerializer- Function to serialize resultsresultDeserializer- Function to deserialize resultsresponseContext- The response context object
import { neshClassicCache } from "@fortedigital/nextjs-cache-handler/functions";
import axios from "axios";
const cachedAxios = neshClassicCache(async (url) => {
return (await axios.get(url.href)).data;
});
export default async function handler(request, response) {
const data = await cachedAxios(
{ revalidate: 5, tags: ["api-data"], responseContext: response },
new URL("https://api.example.com/data.json"),
);
response.json(data);
}Licensed under the MIT License, consistent with the original @neshca/cache-handler.