next-shared-cache
repository·canary·Indexed 19 days ago
https://github.com/caching-tools/next-shared-cacheA specialized ISR/Data cache API for Next.js applications designed for shared caching in distributed, multi-instance environments. It provides @neshca/cache-handler for managing shared caches, on-demand revalidation, and TTL management across App and Pages Routers. The library includes @neshca/json-replacer-reviver for Buffer serialization and supports custom storage implementations via the Handler interface.
What's inside next-shared-cache
- @neshca/cache-handler is a specialized ISR (Incremental Static Regeneration) and Data cache API designed for Next.js applications. It is specifically built to solve the problem of non-shared caches in distributed, self-hosted environments where multiple application instances run simultaneously. By using a shared cache, you ensure data consistency across instances and simplify on-demand revalidation across all replicas.
Use @neshca/server for HTTP caching
canaryNote:
@neshca/serveris deprecated as of@neshca/cache-handlerversion 1.7.0 and will be removed in the next major release. Users should migrate to using@neshca/cache-handler/serverHandler directly.@neshca/serveris an efficient HTTP caching server designed to work with@neshca/cache-handler/server. It implements an LRU (Least Recently Used) eviction policy, which automatically removes the least recently accessed items when the cache reaches its capacity to optimize resource usage.Implementing revalidation in a custom Cache Handler
canaryIf you are building a custom cache handler for
@neshca/cache-handler, you must handle the 'marked as revalidated' state to support App Router'srevalidatePathbehavior.- In
revalidateTag: You must manually mark the cache entry as revalidated (e.g., by updating a flag or metadata in your storage). - In
get: You must check if the entry has been marked as revalidated. If it has, returnnullto signal that the cache is stale and needs to be refreshed.
- In
Configure `keyExpirationStrategy` for Redis Cluster
canaryYou can choose between two expiration strategies for cache keys:
'EXAT': Uses theEXAToption of theSETcommand. This is more efficient thanEXPIREATbut requires Redis server 6.2.0 or newer.'EXPIREAT': Uses theEXPIREATcommand. This requires an additional command call and requires Redis server 4.0.0 or newer.
Use multiple cache handlers for layered caching
canaryThe
handlersproperty accepts an array of objects conforming to theHandlerinterface. You can use multiple handlers to create a caching hierarchy (e.g., a fast local cache followed by a shared remote cache).If a handler in the array is
nullorundefined, it is ignored. This is useful for conditional caching, such as opting out of a remote cache during build processes or falling back to a local cache if a service like Redis is unavailable.CacheHandler.onCreation(async () => { let handler; if (process.env.REDIS_AVAILABLE) { await client.connect(); handler = await createRedisHandler({ client, }); } else { handler = { // ... fallback local handler implementation }; } return { handlers: [handler], }; });Choose a `keyExpirationStrategy` for `redis-strings`
canaryYou can choose between two expiration strategies for cache keys:
'EXAT': Uses theEXAToption of theSETcommand. This is more efficient thanEXPIREAT. Requires Redis server 6.2.0 or newer.'EXPIREAT': Uses theEXPIREATcommand. This requires an additional command call. Requires Redis server 4.0.0 or newer.
Optimize `revalidateTagQuerySize`
canaryThe
revalidateTagQuerySizeoption controls the number of tags retrieved in a single query when scanning or searching for tags in Redis.- Higher values: Reduce the number of commands sent to Redis but increase the amount of data transferred over the network.
- Considerations: Redis uses TCP; typically, 65,535 bytes is the maximum packet size (though this depends on MTU). Adjust this value to balance command frequency against network payload size.
Key features of @neshca/cache-handler
canaryThe library provides several capabilities for managing Next.js caching:
- Shared Cache: Enables distributed caching for self-hosted deployments with multiple replicas.
- On-Demand Revalidation: Simplifies the process of revalidating cache across all application instances.
- TTL Management: Automatically handles cache cleanup to maintain storage efficiency.
- Router Support: Provides unified setup for both Next.js App Router and Pages Router.
- Advanced Caching Functions:
neshCache: A replacement for Next.jsunstable_cachethat offers more granular control.neshClassicCache: Designed for the Pages Router to cache expensive operations withingetServerSidePropsand API routes.
- Cache Pre-population: Supports using the Next.js instrumentation hook to automatically populate the cache with initial data when the application starts.
Optimize `revalidateTagQuerySize` for performance
canaryThe
revalidateTagQuerySizeoption controls how many tags are retrieved in a single query when scanning or searching for tags in Redis.- Higher values: Reduce the total number of commands sent to Redis but increase the amount of data transferred over the network.
- Lower values: Increase the number of commands but reduce the payload size per command.
When tuning this value, consider that Redis uses TCP and typically has a maximum packet size of 65,535 bytes (though this may be lower depending on your MTU).
Create a custom Redis handler for @neshca/cache-handler
canaryTo create a custom Redis handler, you must implement a handler object containing
name,get,set, andrevalidateTagmethods, and register it usingCacheHandler.onCreation.Implementation Details:
onCreation: Use this callback to initialize your connection (e.g., a Redis client). Always create the client inside this callback.get(key, { implicitTags }): Retrieves the cached value. You must handle JSON parsing and tag validation (comparing stored tags against revalidation timestamps).set(key, cacheHandlerValue): Stores the value. You should handle stringification, expiration (usingcacheHandlerValue.lifespan), and tag storage.revalidateTag(tag): Invalidates cache entries associated with a specific tag. This involves updating revalidation timestamps for implicit tags and scanning/deleting keys for explicit tags.- Error Handling: Do not use
try/catchblocks inside handler methods. If a method throws an error,@neshca/cache-handlerwill automatically fall back to the next available handler in thehandlersarray. name: A string identifier useful for debugging and logging.
import { CacheHandler } from '@neshca/cache-handler'; import { isImplicitTag } from '@neshca/cache-handler/helpers'; import { createClient, commandOptions } from 'redis'; CacheHandler.onCreation(async () => { const client = createClient({ url: 'redis://localhost:6379' }); await client.connect(); const customRedisHandler = { name: 'redis-strings-custom', async get(key, { implicitTags }) { // Implementation logic... }, async set(key, cacheHandlerValue) { // Implementation logic... }, async revalidateTag(tag) { // Implementation logic... }, }; return { handlers: [customRedisHandler], }; }); export default CacheHandler;Build the app without requiring a Redis connection
canaryTo prevent build failures when a Redis server is unavailable (e.g., during simultaneous deployment of the app and the Redis server), you can modify your
cache-handler.mjsto conditionally initialize the Redis handler.Use an environment variable, such as
REDIS_AVAILABLE, to wrap the connection logic and handler creation inside theCacheHandler.onCreationmethod. If the variable is not present or is falsy, the handler will not be initialized, allowing the build to proceed without a Redis connection.CacheHandler.onCreation(async () => { let handler; if (process.env.REDIS_AVAILABLE) { await client.connect(); handler = await createRedisHandler({ client, }); } return { handlers: [handler], }; });Verify that the Cache Handler is active using Console Logging
canaryTo manually verify that your custom cache handler is being invoked, you can add
console.logstatements directly into your cache handler implementation. This is useful for confirming that thegetandsetmethods are actually being called by Next.js during runtime.- Add logs to your handler methods (e.g., in
cache-handler.mjs). - Build and start your application.
- Navigate through your application in a browser and check the server console for your log output.
const handler = { name: 'my-cache-handler', async get(key) { console.log('handler.get', key); return cacheStore.get(key); }, async set(key, value) { console.log('handler.set', key, value); cacheStore.set(key, value); }, };- Add logs to your handler methods (e.g., in