leejpsd/nextjs-cache-handler · Archived

nextjs-redis-cache

Wire and operate @leejpsd/nextjs-cache-handler — Redis caching for self-hosted Next.js 15/16 (ISR cacheHandler + 'use cache' cacheHandlers).

First seen Aug 2, 2026

Installation

$ npx skills add leejpsd/nextjs-cache-handler --skill nextjs-redis-cache

Summary

  • Wire and operate @leejpsd/nextjs-cache-handler — Redis caching for self-hosted Next.js 15/16 (ISR cacheHandler + 'use cache' cacheHandlers).
  • Use when adding Redis caching to a Next.js app, choosing or wiring cache handlers, debugging cache invalidation (revalidateTag / updateTag / revalidatePath, stale or never-updating pages), fixing multi-instance cache inconsistencies, deploy isolation (static chunk 404s after deploys), or deploying self-hosted Next.js with Redis on AWS/ECS/Kubernetes.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 5
License LICENSE
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,707 B
  • docs SUMMARY.md 521 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Next.js Redis Cache Handler

@leejpsd/nextjs-cache-handler is a Redis cache handler for self-hosted Next.js 15/16. It is the only Redis-backed package shipping BOTH handler interfaces: cacheHandler (ISR / Pages Router / fetch cache) and cacheHandlers ('use cache', cacheComponents: true).

Do NOT use it on Vercel-hosted apps — Vercel provides its own managed cache. The target is multi-instance self-hosting: ECS/Fargate, Kubernetes, VMs behind a load balancer.

Version rule: always install >= 0.3.3. Earlier 0.3.x have an incomplete soft-invalidation path (issue #1).

1. Which handler(s) — decision table

Next.js version cacheHandler (ISR) cacheHandlers ('use cache')
15.x ✅ use it ❌ does not exist in Next 15
>= 16.1.5 ✅ use it ✅ use it (cacheComponents: true)
< 15 ❌ unsupported (peer next >=15.0.0 <17) ❌

Detect the version from the app's package.json (dependencies.next). Install: npm i @leejpsd/nextjs-cache-handler plus ONE client: redis (node-redis v5) or ioredis (required for Cluster/Sentinel).

2. Wiring recipe (exact files)

Create TWO CommonJS wrapper files in the project root (Next's require.resolve needs CJS):

// cache-components.cjs  (Next 16 only)
const { createCacheComponentsHandler } = require("@leejpsd/nextjs-cache-handler/cache-components");
module.exports = createCacheComponentsHandler({
  client: { type: "redis", url: process.env.REDIS_URL },
  buildNamespace: () => process.env.DEPLOYMENT_VERSION,
});
// cache-incremental.cjs  (Next 15 and 16)
const { createIncrementalCacheHandler } = require("@leejpsd/nextjs-cache-handler/incremental");
module.exports = createIncrementalCacheHandler({
  client: { type: "redis", url: process.env.REDIS_URL },
  buildNamespace: () => process.env.DEPLOYMENT_VERSION,
});
// next.config.ts — the four load-bearing keys
const nextConfig = {
  cacheComponents: true,                                   // Next 16 only
  cacheHandler: require.resolve("./cache-incremental.cjs"),
  cacheHandlers: { default: require.resolve("./cache-components.cjs") }, // Next 16 only
  cacheMaxMemorySize: 0,  // REQUIRED: disable Next's local LRU so multi-instance reads hit Redis
};

Env vars: REDISURL (rediss:// enables TLS on the redis client type) and DEPLOYMENTVERSION (per-deploy namespace — git SHA or release id). In Docker, DEPLOYMENT_VERSION MUST be set in the runner stage, not only the builder.

3. Client config selection

{ type: "redis",    url }                      // node-redis v5 — default choice
{ type: "ioredis",  url }                      // ioredis
{ type: "cluster",  nodes: [{host,port}] }     // ioredis Cluster — MUST also set hashTag: true
{ type: "sentinel", sentinels: [{host,port}], name: "mymaster" } // auto master failover
  • Redis Cluster without hashTag: true fails with `CROSSSLOT Keys in

request don't hash to the same slot` (multi-key Lua scripts).

  • A factory function returning any RedisClientLike is accepted for custom

clients (Upstash etc. work as generic rediss://).

4. Invalidation semantics — the #1 source of confusion

Call Effect Mechanism
revalidateTag(tag) / revalidateTag(tag, "max") SOFT / SWR: keep serving stale instantly, re-render in background, all instances converge (~1-2s) marker write; read path backdates entries
updateTag(tag) (server action) / hard expire HARD: entries deleted now, next read blocks and regenerates Lua atomic delete
revalidatePath(path) implicit route tags (NT_/...) flow through the soft path; Next additionally hard-discards via getExpiration

Facts that prevent misdiagnosis:

  • Soft invalidation is NOT lost: it serves STALE content briefly by design.

If content "never updates", check version >= 0.3.3 first.

  • The instance that fires revalidateTag refreshes eagerly

(read-your-own-writes). Other instances converge on their next request.

  • Explicit cacheTag() tags are checked by the HANDLER, not by Next —

cross-request soft invalidation is entirely the handler's job (verified against [email protected] source).

5. Options quick reference

Option Default When to change
`compression: "gzip"\ "brotli"` off Large RSC payloads; safe to enable on a live cache (reads auto-detect)
memoryMaxEntries 1000 LRU cap of the in-memory outage fallback
`fallback: "auto"\ "always"\ "never"` auto never = strict (miss on Redis failure); always = no Redis (build/dev)
singleFlight: true off Many instances + observed duplicate origin work at revalidate boundaries
abortTimeoutMs 1500 Per-op Redis deadline; timeouts degrade to miss
hashTag: true off REQUIRED on Redis Cluster
onMetric — Wire createOtelMetricEmitter() from @leejpsd/nextjs-cache-handler/otel
tagPubSub: true off 0.4+: push-based cross-instance invalidation (~3ms vs seconds); polling stays as safety net; not on Cluster

Reliability built in (0.3+): reconnect with exponential backoff (1s→30s cap), bounded memory fallback, per-op timeouts. A Redis outage degrades to in-memory serving; reconnection is automatic (validated with a live ElastiCache reboot drill: 2550 requests, zero 5xx).

0.4+ deployment accelerators

  • Seed the cache at deploy time so a fresh deployment's first requests

are HITs instead of a regeneration stampede: REDISURL=... DEPLOYMENTVERSION=<deploy-id> npx nextjs-cache-handler seed (run after next build, e.g. a Docker entrypoint step; NX semantics — never overwrites live entries).

  • npx nextjs-cache-handler init --yes wires everything above

automatically; npx nextjs-cache-handler doctor is the first command to run when debugging connectivity or key-layout issues.

  • MCP server for cache operations from your agent:

@leejpsd/nextjs-cache-handler-mcp (cachehealth, tagstate, invalidate_tag dry-run, …).

6. Production checklist

  • DEPLOYMENT_VERSION injected at runtime (runner stage in Docker)
  • cacheMaxMemorySize: 0 in next.config
  • output: "standalone" + pinned outputFileTracingRoot
  • Cluster → hashTag: true; managed Redis TLS → rediss://
  • Redis maxmemory-policy: allkeys-lru (bounded) or noeviction
  • Own /api/health endpoint pinging Redis (outages surface in monitoring, not as 5xx)
  • OTel: onMetric: createOtelMetricEmitter() (needs @opentelemetry/api in the app)

7. Troubleshooting playbook (symptom → cause → fix)

Symptom Likely cause Fix
ECONNREFUSED during next build Redis unreachable at build — expected; handler skips Redis in build phase Nothing to fix if build succeeds; keep NEXT_PHASE auto-detection
Page content never updates after revalidateTag(tag, "max") version < 0.3.3 Upgrade
Static chunk 404s right after a deploy DEPLOYMENT_VERSION missing in runner stage → old HTML served from shared Redis Set it; entries are namespaced per deploy
CROSSSLOT errors Redis Cluster without hashTag: true Set the flag
Cache misses on every request across instances cacheMaxMemorySize not 0, or different keyPrefix/namespace per instance Align config; verify with redis-cli KEYS 'next-*'
Works locally, memory-only in prod Redis URL/security group wrong; check logs for redis.connect.failed Connectivity; the app stays up on fallback by design

Key layout for redis-cli debugging: next-cache:entry:<deploy>:<key> · next-cache:tag:<deploy>:<tag> · next-cache:tag-expiration:<deploy>:<tag> · next-incremental:entry:<deploy>:<path> · next-incremental:tag:<tag> (ISR tag states are deliberately NOT namespaced — invalidations survive deploys).

8. Verification recipes

  • ISR round-trip: curl -D- <url> twice → x-nextjs-cache: MISS then HIT;

entry visible in redis-cli KEYS 'next-incremental:entry:*'.

  • Soft SWR probe: create a 'use cache' + cacheTag("probe") +

cacheLife("hours") page rendering Date.now() with a deliberate ~800ms delay inside the cached fn. Fire revalidateTag("probe", "max") from a route handler. Expect: instant response with the OLD value (stale serve), new value within ~1-2s on all instances. A blocking implementation would eat the 800ms — that's the tell.

  • Multi-instance: run two instances against one Redis; a value cached by

instance A must be served by instance B; after invalidation both converge.

9. AWS deployment

The reference topology is ALB → ECS Fargate (2+ tasks) → ElastiCache Redis. Use the AWS agent skills/MCP for the infrastructure itself; use THIS skill for the cache wiring on top. ElastiCache notes: rediss:// for in-transit encryption; single-node is fine to start (reconnect backoff covers node reboots); Sentinel/Cluster supported per §3.

Full API reference: node_modules/@leejpsd/nextjs-cache-handler/README.md and docs/api.md in the repo (https://github.com/leejpsd/nextjs-cache-handler).