@leejpsd/nextjs-cache-handler 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -0
- package/CHANGELOG.md +11 -0
- package/README.md +17 -0
- package/llms.txt +27 -0
- package/package.json +6 -1
- package/rules/nextjs-cache-rules.md +23 -0
- package/setup-instructions/setup.md +157 -0
- package/skills/nextjs-redis-cache/SKILL.md +167 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @leejpsd/nextjs-cache-handler — notes for AI agents
|
|
2
|
+
|
|
3
|
+
Redis cache handler for **self-hosted** Next.js 15/16. Ships BOTH interfaces:
|
|
4
|
+
`cacheHandler` (ISR, Next 15+16) and `cacheHandlers` (`'use cache'`,
|
|
5
|
+
Next >= 16.1.5). Not for Vercel-hosted apps.
|
|
6
|
+
|
|
7
|
+
**Full agent guidance:** `skills/nextjs-redis-cache/SKILL.md` in this package
|
|
8
|
+
(also installable via `npx skills add leejpsd/nextjs-cache-handler`).
|
|
9
|
+
**Agent-executable setup:**
|
|
10
|
+
https://raw.githubusercontent.com/leejpsd/nextjs-cache-handler/main/setup-instructions/setup.md
|
|
11
|
+
|
|
12
|
+
## Rules that prevent the most common mistakes
|
|
13
|
+
|
|
14
|
+
1. `revalidateTag(tag, "max")` is SOFT = stale-while-revalidate: instant
|
|
15
|
+
stale serve + background re-render, cross-instance convergence in ~1-2s.
|
|
16
|
+
Brief stale content is BY DESIGN — not a bug. `updateTag(tag)` is HARD =
|
|
17
|
+
delete + blocking regeneration. Requires version >= 0.3.3.
|
|
18
|
+
2. `cacheMaxMemorySize: 0` in next.config is mandatory for multi-instance
|
|
19
|
+
correctness. Do not remove it.
|
|
20
|
+
3. `DEPLOYMENT_VERSION` env must exist at RUNTIME (Docker runner stage).
|
|
21
|
+
Missing → cross-deploy cache bleed / static chunk 404s.
|
|
22
|
+
4. Redis Cluster needs `hashTag: true`; TLS via `rediss://` URLs.
|
|
23
|
+
5. Build-time `ECONNREFUSED` toward Redis is expected (build-phase gate) —
|
|
24
|
+
only investigate if the build fails.
|
|
25
|
+
6. Redis outages degrade to a bounded in-memory fallback with automatic
|
|
26
|
+
reconnect (backoff 1s→30s). Do not wrap cache calls in extra try/catch.
|
|
27
|
+
7. Wiring shape: two CJS wrapper files + `require.resolve` in next.config —
|
|
28
|
+
see README "Quick start" or the setup URL above.
|
|
29
|
+
|
|
30
|
+
Entry points: `/incremental` (ISR), `/cache-components` ('use cache'),
|
|
31
|
+
`/client/redis`, `/client/ioredis` (incl. cluster/sentinel), `/ops`
|
|
32
|
+
(metric snapshot), `/otel` (OpenTelemetry emitter). API details: README.md
|
|
33
|
+
and https://github.com/leejpsd/nextjs-cache-handler/blob/main/docs/api.md
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`79e112b`](https://github.com/leejpsd/nextjs-cache-handler/commit/79e112bc03249e6e042da717c4558de733cc62f0) Thanks [@leejpsd](https://github.com/leejpsd)! - Ship agent-native assets in the package: `AGENTS.md` (agent guidance
|
|
8
|
+
discovered via node_modules scans), an agent skill
|
|
9
|
+
(`skills/nextjs-redis-cache/SKILL.md` — decision tables, invalidation
|
|
10
|
+
semantics, troubleshooting playbook), agent-executable setup instructions
|
|
11
|
+
(`setup-instructions/setup.md`), a project rules block (`rules/`), and
|
|
12
|
+
`llms.txt`. README gains a "For AI agents" section. No runtime code changes.
|
|
13
|
+
|
|
3
14
|
## 0.3.3
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -16,6 +16,23 @@
|
|
|
16
16
|
> Sentinel** support, a built-in **OpenTelemetry emitter** at `/otel`, and an
|
|
17
17
|
> LRU-bounded memory fallback.
|
|
18
18
|
|
|
19
|
+
## 🤖 For AI agents
|
|
20
|
+
|
|
21
|
+
Working with Claude Code / Codex / Cursor? Give your agent this URL and it
|
|
22
|
+
will install and wire everything (version detection, wrapper files,
|
|
23
|
+
next.config patch, verification):
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
https://raw.githubusercontent.com/leejpsd/nextjs-cache-handler/main/setup-instructions/setup.md
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
An agent skill with decision tables, invalidation semantics, and a
|
|
30
|
+
troubleshooting playbook ships in the package (`AGENTS.md`,
|
|
31
|
+
`skills/nextjs-redis-cache/SKILL.md`) and via
|
|
32
|
+
`npx skills add leejpsd/nextjs-cache-handler`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
19
36
|
The Redis cache handler for **Next.js 15/16** that ships **both** `cacheHandler`
|
|
20
37
|
(ISR / Pages Router) **and** `cacheHandlers` (`'use cache'` directive,
|
|
21
38
|
`cacheComponents: true`) — the area where
|
package/llms.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @leejpsd/nextjs-cache-handler
|
|
2
|
+
|
|
3
|
+
> Redis cache handler for self-hosted Next.js 15/16 — the only one shipping
|
|
4
|
+
> both cacheHandler (ISR) and cacheHandlers ('use cache') with built-in
|
|
5
|
+
> compression, Redis Sentinel, OpenTelemetry, stale-while-revalidate tag
|
|
6
|
+
> invalidation, per-deploy namespace isolation, and a bounded in-memory
|
|
7
|
+
> fallback with automatic reconnection. Production-validated on multi-instance
|
|
8
|
+
> AWS (ECS Fargate + ElastiCache), including a live Redis-reboot drill with
|
|
9
|
+
> zero 5xx. Install version >= 0.3.3.
|
|
10
|
+
|
|
11
|
+
Key facts: peer `next >=15.0.0 <17` (cacheHandlers needs >= 16.1.5); clients:
|
|
12
|
+
redis (node-redis v5), ioredis, ioredis Cluster (set hashTag: true), Sentinel;
|
|
13
|
+
zero runtime dependencies; ESM + CJS.
|
|
14
|
+
|
|
15
|
+
## Docs
|
|
16
|
+
|
|
17
|
+
- [README](https://github.com/leejpsd/nextjs-cache-handler/blob/main/README.md): quick start, wiring, options, production checklist
|
|
18
|
+
- [API reference](https://github.com/leejpsd/nextjs-cache-handler/blob/main/docs/api.md): entry points, options, client configs, metric events
|
|
19
|
+
- [Agent skill](https://github.com/leejpsd/nextjs-cache-handler/blob/main/skills/nextjs-redis-cache/SKILL.md): decision tables, invalidation semantics, troubleshooting playbook
|
|
20
|
+
- [Agent setup instructions](https://github.com/leejpsd/nextjs-cache-handler/blob/main/setup-instructions/setup.md): step-by-step executable install flow
|
|
21
|
+
- [Architecture](https://github.com/leejpsd/nextjs-cache-handler/blob/main/docs/architecture.md): key layout, tag propagation, single-flight
|
|
22
|
+
- [Staging verification](https://github.com/leejpsd/nextjs-cache-handler/blob/main/docs/staging-verification-2026-08-01.md): AWS multi-instance evidence
|
|
23
|
+
|
|
24
|
+
## Optional
|
|
25
|
+
|
|
26
|
+
- [Next 16 cacheHandlers spec notes](https://github.com/leejpsd/nextjs-cache-handler/blob/main/docs/next16-spec.md): verified division of labor between Next and handlers
|
|
27
|
+
- [Changelog](https://github.com/leejpsd/nextjs-cache-handler/blob/main/CHANGELOG.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leejpsd/nextjs-cache-handler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Redis cache handler for Next.js 15/16 — the only one shipping both cacheHandler (ISR) and cacheHandlers ('use cache') with built-in compression, Redis Sentinel, and OpenTelemetry. Production-validated on multi-instance AWS: reconnect backoff, cross-instance tag invalidation, zero-downtime Redis failover.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"next",
|
|
@@ -133,6 +133,11 @@
|
|
|
133
133
|
},
|
|
134
134
|
"files": [
|
|
135
135
|
"dist/",
|
|
136
|
+
"skills/",
|
|
137
|
+
"rules/",
|
|
138
|
+
"setup-instructions/",
|
|
139
|
+
"AGENTS.md",
|
|
140
|
+
"llms.txt",
|
|
136
141
|
"README.md",
|
|
137
142
|
"CHANGELOG.md",
|
|
138
143
|
"LICENSE"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Next.js Redis Cache Guidance (@leejpsd/nextjs-cache-handler)
|
|
2
|
+
|
|
3
|
+
- Before changing cache invalidation code, load the `nextjs-redis-cache`
|
|
4
|
+
skill (or read `node_modules/@leejpsd/nextjs-cache-handler/AGENTS.md`) and
|
|
5
|
+
prefer its guidance over general knowledge.
|
|
6
|
+
- Distinguish invalidation modes: `revalidateTag(tag, "max")` is SOFT
|
|
7
|
+
(stale-while-revalidate — brief stale serving is by design);
|
|
8
|
+
`updateTag(tag)` / hard expire deletes entries and blocks on regeneration.
|
|
9
|
+
Do not "fix" soft semantics by switching everything to hard.
|
|
10
|
+
- Never remove `cacheMaxMemorySize: 0` from next.config — multi-instance
|
|
11
|
+
correctness depends on it.
|
|
12
|
+
- `DEPLOYMENT_VERSION` must be set in every runtime environment (Docker:
|
|
13
|
+
the runner stage). Missing it causes cross-deploy cache bleed and static
|
|
14
|
+
chunk 404s.
|
|
15
|
+
- Redis Cluster requires `hashTag: true` in the handler options; managed
|
|
16
|
+
Redis with TLS uses `rediss://` URLs.
|
|
17
|
+
- A Redis outage is survivable by design (bounded in-memory fallback +
|
|
18
|
+
reconnect backoff) — do not add ad-hoc try/catch around cache calls, and
|
|
19
|
+
do not treat `redis.connect.failed` log lines during an outage as an
|
|
20
|
+
application bug.
|
|
21
|
+
- For AWS deployments, use the AWS skills/MCP for infrastructure and this
|
|
22
|
+
package's skill for cache wiring on top (reference topology: ALB → ECS
|
|
23
|
+
Fargate ×2+ → ElastiCache).
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Set up @leejpsd/nextjs-cache-handler for a Next.js project
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This setup file wires Redis caching into a self-hosted Next.js 15/16 app
|
|
6
|
+
using `@leejpsd/nextjs-cache-handler`: it detects the Next.js version,
|
|
7
|
+
installs the package and a Redis client, generates the handler wrapper
|
|
8
|
+
files, patches `next.config`, and verifies a cache round-trip.
|
|
9
|
+
|
|
10
|
+
The workflow includes:
|
|
11
|
+
|
|
12
|
+
- Detecting the Next.js version and router setup from `package.json`
|
|
13
|
+
- Installing `@leejpsd/nextjs-cache-handler` (>= 0.3.3) plus `redis` or `ioredis`
|
|
14
|
+
- Generating `cache-incremental.cjs` (and `cache-components.cjs` on Next 16)
|
|
15
|
+
- Patching `next.config.(js|ts|mjs)` — always show the diff and get approval
|
|
16
|
+
- Verifying MISS → HIT against a local or provided Redis
|
|
17
|
+
- Adding the project rules block to the AI tool's rules file
|
|
18
|
+
|
|
19
|
+
## Parameters
|
|
20
|
+
|
|
21
|
+
- redis_url (optional): Redis connection string. If not provided, use
|
|
22
|
+
`redis://127.0.0.1:6379` for local verification and tell the user to set
|
|
23
|
+
`REDIS_URL` in their deployment environment.
|
|
24
|
+
- deployment_version_source (optional): how the app derives
|
|
25
|
+
`DEPLOYMENT_VERSION` (git SHA, CI build id). Default: `git rev-parse --short HEAD`.
|
|
26
|
+
|
|
27
|
+
Constraints:
|
|
28
|
+
|
|
29
|
+
- You MUST NOT use this package for Vercel-hosted deployments — tell the
|
|
30
|
+
user Vercel's managed cache already covers them, and stop.
|
|
31
|
+
- You MUST detect the Next.js version before choosing which handlers to wire.
|
|
32
|
+
- You MUST show the `next.config` diff and get explicit approval before
|
|
33
|
+
writing it — this file frequently contains user customizations.
|
|
34
|
+
- You MUST NOT print or commit secrets (Redis passwords, URLs with auth).
|
|
35
|
+
- You MUST install version >= 0.3.3 (earlier versions have an incomplete
|
|
36
|
+
soft-invalidation path).
|
|
37
|
+
|
|
38
|
+
## Steps
|
|
39
|
+
|
|
40
|
+
### Step 1: Detect the project
|
|
41
|
+
|
|
42
|
+
Read `package.json`. Determine:
|
|
43
|
+
|
|
44
|
+
- `dependencies.next` version → **15.x** (singular handler only) or
|
|
45
|
+
**>= 16.1.5** (both handlers). If `< 15.0.0`, STOP: unsupported
|
|
46
|
+
(peer range is `>=15.0.0 <17`).
|
|
47
|
+
- Which Redis client is already installed (`redis` or `ioredis`); if neither,
|
|
48
|
+
default to `redis` unless the user needs Cluster or Sentinel (those
|
|
49
|
+
require `ioredis`).
|
|
50
|
+
|
|
51
|
+
**Error handling:**
|
|
52
|
+
|
|
53
|
+
| Symptom | Cause | Resolution |
|
|
54
|
+
|---------|-------|------------|
|
|
55
|
+
| No `next` in dependencies | Not a Next.js project | Ask the user for the correct project directory |
|
|
56
|
+
| Next < 15 | Unsupported | Suggest upgrading Next, or stop |
|
|
57
|
+
|
|
58
|
+
### Step 2: Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install @leejpsd/nextjs-cache-handler redis
|
|
62
|
+
# or, when Cluster/Sentinel is needed:
|
|
63
|
+
npm install @leejpsd/nextjs-cache-handler ioredis
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Success:** exit code 0, no peer-dependency conflicts (the package supports
|
|
67
|
+
`next >=15.0.0 <17`).
|
|
68
|
+
|
|
69
|
+
### Step 3: Generate wrapper files
|
|
70
|
+
|
|
71
|
+
Create `cache-incremental.cjs` in the project root (ALL versions):
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const { createIncrementalCacheHandler } = require("@leejpsd/nextjs-cache-handler/incremental");
|
|
75
|
+
module.exports = createIncrementalCacheHandler({
|
|
76
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
77
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
On **Next 16** also create `cache-components.cjs`:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const { createCacheComponentsHandler } = require("@leejpsd/nextjs-cache-handler/cache-components");
|
|
85
|
+
module.exports = createCacheComponentsHandler({
|
|
86
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
87
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use `{ type: "ioredis", ... }` / `cluster` / `sentinel` variants when the
|
|
92
|
+
user's infrastructure requires them (Cluster additionally needs
|
|
93
|
+
`hashTag: true` in the handler options).
|
|
94
|
+
|
|
95
|
+
### Step 4: Patch next.config (approval required)
|
|
96
|
+
|
|
97
|
+
Merge these keys into the existing config — do not replace the file:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
cacheHandler: require.resolve("./cache-incremental.cjs"),
|
|
101
|
+
cacheHandlers: { default: require.resolve("./cache-components.cjs") }, // Next 16 only
|
|
102
|
+
cacheComponents: true, // Next 16 only — skip if the user does not want 'use cache'
|
|
103
|
+
cacheMaxMemorySize: 0, // required for multi-instance correctness
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Show the diff, get approval, then write.
|
|
107
|
+
|
|
108
|
+
**Error handling:**
|
|
109
|
+
|
|
110
|
+
| Symptom | Cause | Resolution |
|
|
111
|
+
|---------|-------|------------|
|
|
112
|
+
| Config is ESM (`next.config.mjs`) | `require.resolve` unavailable | Use `import { createRequire } from "module"; const require = createRequire(import.meta.url);` at the top |
|
|
113
|
+
| `cacheHandler` already set | Another handler in use | Show both, ask the user which should win |
|
|
114
|
+
|
|
115
|
+
### Step 5: Environment
|
|
116
|
+
|
|
117
|
+
Tell the user to provide in every runtime environment (NOT build-only):
|
|
118
|
+
|
|
119
|
+
- `REDIS_URL` — e.g. `rediss://...` for TLS/managed Redis
|
|
120
|
+
- `DEPLOYMENT_VERSION` — per-deploy value (git SHA); in Docker it MUST be
|
|
121
|
+
declared in the **runner** stage
|
|
122
|
+
|
|
123
|
+
Append both to `.env.example` if the project has one.
|
|
124
|
+
|
|
125
|
+
### Step 6: Verify
|
|
126
|
+
|
|
127
|
+
With a local Redis (`docker run -d -p 6379:6379 redis:7-alpine` or a native
|
|
128
|
+
`redis-server`), run:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
REDIS_URL=redis://127.0.0.1:6379 DEPLOYMENT_VERSION=setup-verify npm run build
|
|
132
|
+
REDIS_URL=redis://127.0.0.1:6379 DEPLOYMENT_VERSION=setup-verify npm run start &
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then request any ISR/static page twice and check the `x-nextjs-cache`
|
|
136
|
+
header: first `MISS`, second `HIT`. Confirm keys exist:
|
|
137
|
+
`redis-cli KEYS 'next-incremental:*'`. Kill the server afterwards.
|
|
138
|
+
|
|
139
|
+
**Error handling:**
|
|
140
|
+
|
|
141
|
+
| Symptom | Cause | Resolution |
|
|
142
|
+
|---------|-------|------------|
|
|
143
|
+
| `ECONNREFUSED` during build | Expected — build phase skips Redis | Ignore unless the build itself fails |
|
|
144
|
+
| No `next-*` keys after requests | Handler not loaded | Check `next.config` paths resolve; check server logs for `[next-cache]` lines |
|
|
145
|
+
| `CROSSSLOT` errors | Redis Cluster without `hashTag: true` | Add the option in both wrappers |
|
|
146
|
+
|
|
147
|
+
### Step 7: Add the agent rules block
|
|
148
|
+
|
|
149
|
+
Identify the AI tool's rules file (CLAUDE.md / AGENTS.md / .cursor/rules/)
|
|
150
|
+
and append the contents of:
|
|
151
|
+
|
|
152
|
+
https://raw.githubusercontent.com/leejpsd/nextjs-cache-handler/main/rules/nextjs-cache-rules.md
|
|
153
|
+
|
|
154
|
+
End by telling the user: "Redis caching is wired. Set REDIS_URL and
|
|
155
|
+
DEPLOYMENT_VERSION in your deployment environment, and see the
|
|
156
|
+
nextjs-redis-cache skill for invalidation semantics and production
|
|
157
|
+
checklist."
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-redis-cache
|
|
3
|
+
description: 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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js Redis Cache Handler
|
|
7
|
+
|
|
8
|
+
`@leejpsd/nextjs-cache-handler` is a Redis cache handler for **self-hosted**
|
|
9
|
+
Next.js 15/16. It is the only Redis-backed package shipping BOTH handler
|
|
10
|
+
interfaces: `cacheHandler` (ISR / Pages Router / fetch cache) and
|
|
11
|
+
`cacheHandlers` (`'use cache'`, `cacheComponents: true`).
|
|
12
|
+
|
|
13
|
+
**Do NOT use it on Vercel-hosted apps** — Vercel provides its own managed
|
|
14
|
+
cache. The target is multi-instance self-hosting: ECS/Fargate, Kubernetes,
|
|
15
|
+
VMs behind a load balancer.
|
|
16
|
+
|
|
17
|
+
**Version rule: always install >= 0.3.3.** Earlier 0.3.x have an incomplete
|
|
18
|
+
soft-invalidation path (issue #1).
|
|
19
|
+
|
|
20
|
+
## 1. Which handler(s) — decision table
|
|
21
|
+
|
|
22
|
+
| Next.js version | `cacheHandler` (ISR) | `cacheHandlers` ('use cache') |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| 15.x | ✅ use it | ❌ does not exist in Next 15 |
|
|
25
|
+
| >= 16.1.5 | ✅ use it | ✅ use it (`cacheComponents: true`) |
|
|
26
|
+
| < 15 | ❌ unsupported (peer `next >=15.0.0 <17`) | ❌ |
|
|
27
|
+
|
|
28
|
+
Detect the version from the app's `package.json` (`dependencies.next`).
|
|
29
|
+
Install: `npm i @leejpsd/nextjs-cache-handler` plus ONE client:
|
|
30
|
+
`redis` (node-redis v5) or `ioredis` (required for Cluster/Sentinel).
|
|
31
|
+
|
|
32
|
+
## 2. Wiring recipe (exact files)
|
|
33
|
+
|
|
34
|
+
Create TWO CommonJS wrapper files in the project root (Next's
|
|
35
|
+
`require.resolve` needs CJS):
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// cache-components.cjs (Next 16 only)
|
|
39
|
+
const { createCacheComponentsHandler } = require("@leejpsd/nextjs-cache-handler/cache-components");
|
|
40
|
+
module.exports = createCacheComponentsHandler({
|
|
41
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
42
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// cache-incremental.cjs (Next 15 and 16)
|
|
48
|
+
const { createIncrementalCacheHandler } = require("@leejpsd/nextjs-cache-handler/incremental");
|
|
49
|
+
module.exports = createIncrementalCacheHandler({
|
|
50
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
51
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// next.config.ts — the four load-bearing keys
|
|
57
|
+
const nextConfig = {
|
|
58
|
+
cacheComponents: true, // Next 16 only
|
|
59
|
+
cacheHandler: require.resolve("./cache-incremental.cjs"),
|
|
60
|
+
cacheHandlers: { default: require.resolve("./cache-components.cjs") }, // Next 16 only
|
|
61
|
+
cacheMaxMemorySize: 0, // REQUIRED: disable Next's local LRU so multi-instance reads hit Redis
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Env vars: `REDIS_URL` (rediss:// enables TLS on the `redis` client type) and
|
|
66
|
+
`DEPLOYMENT_VERSION` (per-deploy namespace — git SHA or release id). In
|
|
67
|
+
Docker, `DEPLOYMENT_VERSION` MUST be set in the **runner** stage, not only
|
|
68
|
+
the builder.
|
|
69
|
+
|
|
70
|
+
## 3. Client config selection
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
{ type: "redis", url } // node-redis v5 — default choice
|
|
74
|
+
{ type: "ioredis", url } // ioredis
|
|
75
|
+
{ type: "cluster", nodes: [{host,port}] } // ioredis Cluster — MUST also set hashTag: true
|
|
76
|
+
{ type: "sentinel", sentinels: [{host,port}], name: "mymaster" } // auto master failover
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Redis Cluster without `hashTag: true` fails with `CROSSSLOT Keys in
|
|
80
|
+
request don't hash to the same slot` (multi-key Lua scripts).
|
|
81
|
+
- A factory function returning any `RedisClientLike` is accepted for custom
|
|
82
|
+
clients (Upstash etc. work as generic `rediss://`).
|
|
83
|
+
|
|
84
|
+
## 4. Invalidation semantics — the #1 source of confusion
|
|
85
|
+
|
|
86
|
+
| Call | Effect | Mechanism |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `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 |
|
|
89
|
+
| `updateTag(tag)` (server action) / hard expire | **HARD**: entries deleted now, next read blocks and regenerates | Lua atomic delete |
|
|
90
|
+
| `revalidatePath(path)` | implicit route tags (`_N_T_/...`) flow through the soft path; Next additionally hard-discards via `getExpiration` | |
|
|
91
|
+
|
|
92
|
+
Facts that prevent misdiagnosis:
|
|
93
|
+
- Soft invalidation is NOT lost: it serves STALE content briefly by design.
|
|
94
|
+
If content "never updates", check version >= 0.3.3 first.
|
|
95
|
+
- The instance that fires `revalidateTag` refreshes eagerly
|
|
96
|
+
(read-your-own-writes). Other instances converge on their next request.
|
|
97
|
+
- Explicit `cacheTag()` tags are checked by the HANDLER, not by Next —
|
|
98
|
+
cross-request soft invalidation is entirely the handler's job (verified
|
|
99
|
+
against next@16.2.3 source).
|
|
100
|
+
|
|
101
|
+
## 5. Options quick reference
|
|
102
|
+
|
|
103
|
+
| Option | Default | When to change |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `compression: "gzip"\|"brotli"` | off | Large RSC payloads; safe to enable on a live cache (reads auto-detect) |
|
|
106
|
+
| `memoryMaxEntries` | 1000 | LRU cap of the in-memory outage fallback |
|
|
107
|
+
| `fallback: "auto"\|"always"\|"never"` | auto | `never` = strict (miss on Redis failure); `always` = no Redis (build/dev) |
|
|
108
|
+
| `singleFlight: true` | off | Many instances + observed duplicate origin work at revalidate boundaries |
|
|
109
|
+
| `abortTimeoutMs` | 1500 | Per-op Redis deadline; timeouts degrade to miss |
|
|
110
|
+
| `hashTag: true` | off | REQUIRED on Redis Cluster |
|
|
111
|
+
| `onMetric` | — | Wire `createOtelMetricEmitter()` from `@leejpsd/nextjs-cache-handler/otel` |
|
|
112
|
+
|
|
113
|
+
Reliability built in (0.3+): reconnect with exponential backoff (1s→30s cap),
|
|
114
|
+
bounded memory fallback, per-op timeouts. A Redis outage degrades to
|
|
115
|
+
in-memory serving; reconnection is automatic (validated with a live
|
|
116
|
+
ElastiCache reboot drill: 2550 requests, zero 5xx).
|
|
117
|
+
|
|
118
|
+
## 6. Production checklist
|
|
119
|
+
|
|
120
|
+
- [ ] `DEPLOYMENT_VERSION` injected at runtime (runner stage in Docker)
|
|
121
|
+
- [ ] `cacheMaxMemorySize: 0` in next.config
|
|
122
|
+
- [ ] `output: "standalone"` + pinned `outputFileTracingRoot`
|
|
123
|
+
- [ ] Cluster → `hashTag: true`; managed Redis TLS → `rediss://`
|
|
124
|
+
- [ ] Redis `maxmemory-policy`: `allkeys-lru` (bounded) or `noeviction`
|
|
125
|
+
- [ ] Own `/api/health` endpoint pinging Redis (outages surface in monitoring, not as 5xx)
|
|
126
|
+
- [ ] OTel: `onMetric: createOtelMetricEmitter()` (needs `@opentelemetry/api` in the app)
|
|
127
|
+
|
|
128
|
+
## 7. Troubleshooting playbook (symptom → cause → fix)
|
|
129
|
+
|
|
130
|
+
| Symptom | Likely cause | Fix |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `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 |
|
|
133
|
+
| Page content never updates after `revalidateTag(tag, "max")` | version < 0.3.3 | Upgrade |
|
|
134
|
+
| 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 |
|
|
135
|
+
| `CROSSSLOT` errors | Redis Cluster without `hashTag: true` | Set the flag |
|
|
136
|
+
| Cache misses on every request across instances | `cacheMaxMemorySize` not 0, or different `keyPrefix`/namespace per instance | Align config; verify with `redis-cli KEYS 'next-*'` |
|
|
137
|
+
| 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 |
|
|
138
|
+
|
|
139
|
+
Key layout for redis-cli debugging:
|
|
140
|
+
`next-cache:entry:<deploy>:<key>` · `next-cache:tag:<deploy>:<tag>` ·
|
|
141
|
+
`next-cache:tag-expiration:<deploy>:<tag>` ·
|
|
142
|
+
`next-incremental:entry:<deploy>:<path>` · `next-incremental:tag:<tag>`
|
|
143
|
+
(ISR tag states are deliberately NOT namespaced — invalidations survive deploys).
|
|
144
|
+
|
|
145
|
+
## 8. Verification recipes
|
|
146
|
+
|
|
147
|
+
- **ISR round-trip**: `curl -D- <url>` twice → `x-nextjs-cache: MISS` then `HIT`;
|
|
148
|
+
entry visible in `redis-cli KEYS 'next-incremental:entry:*'`.
|
|
149
|
+
- **Soft SWR probe**: create a `'use cache'` + `cacheTag("probe")` +
|
|
150
|
+
`cacheLife("hours")` page rendering `Date.now()` with a deliberate ~800ms
|
|
151
|
+
delay inside the cached fn. Fire `revalidateTag("probe", "max")` from a
|
|
152
|
+
route handler. Expect: instant response with the OLD value (stale serve),
|
|
153
|
+
new value within ~1-2s on all instances. A blocking implementation would
|
|
154
|
+
eat the 800ms — that's the tell.
|
|
155
|
+
- **Multi-instance**: run two instances against one Redis; a value cached by
|
|
156
|
+
instance A must be served by instance B; after invalidation both converge.
|
|
157
|
+
|
|
158
|
+
## 9. AWS deployment
|
|
159
|
+
|
|
160
|
+
The reference topology is ALB → ECS Fargate (2+ tasks) → ElastiCache Redis.
|
|
161
|
+
Use the AWS agent skills/MCP for the infrastructure itself; use THIS skill
|
|
162
|
+
for the cache wiring on top. ElastiCache notes: `rediss://` for in-transit
|
|
163
|
+
encryption; single-node is fine to start (reconnect backoff covers node
|
|
164
|
+
reboots); Sentinel/Cluster supported per §3.
|
|
165
|
+
|
|
166
|
+
Full API reference: `node_modules/@leejpsd/nextjs-cache-handler/README.md`
|
|
167
|
+
and `docs/api.md` in the repo (https://github.com/leejpsd/nextjs-cache-handler).
|