@leejpsd/nextjs-cache-handler 0.3.3 → 0.4.0
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 +43 -0
- package/README.md +22 -3
- package/bin/cli.cjs +10 -0
- package/dist/cache-components/index.cjs +150 -4
- package/dist/cache-components/index.cjs.map +1 -1
- package/dist/cache-components/index.d.cts +1 -1
- package/dist/cache-components/index.d.ts +1 -1
- package/dist/cache-components/index.js +150 -4
- package/dist/cache-components/index.js.map +1 -1
- package/dist/cli/index.cjs +937 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +28 -0
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.js +927 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/incremental/index.cjs +77 -3
- package/dist/incremental/index.cjs.map +1 -1
- package/dist/incremental/index.d.cts +1 -1
- package/dist/incremental/index.d.ts +1 -1
- package/dist/incremental/index.js +77 -3
- package/dist/incremental/index.js.map +1 -1
- package/dist/index.cjs +153 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +153 -5
- package/dist/index.js.map +1 -1
- package/dist/ops/index.d.cts +1 -1
- package/dist/ops/index.d.ts +1 -1
- package/dist/otel/index.d.cts +1 -1
- package/dist/otel/index.d.ts +1 -1
- package/dist/seed/index.cjs +560 -0
- package/dist/seed/index.cjs.map +1 -0
- package/dist/seed/index.d.cts +53 -0
- package/dist/seed/index.d.ts +53 -0
- package/dist/seed/index.js +549 -0
- package/dist/seed/index.js.map +1 -0
- package/dist/shared/client/adapter-ioredis.cjs +40 -1
- package/dist/shared/client/adapter-ioredis.cjs.map +1 -1
- package/dist/shared/client/adapter-ioredis.d.cts +1 -1
- package/dist/shared/client/adapter-ioredis.d.ts +1 -1
- package/dist/shared/client/adapter-ioredis.js +40 -1
- package/dist/shared/client/adapter-ioredis.js.map +1 -1
- package/dist/shared/client/adapter-redis.cjs +34 -1
- package/dist/shared/client/adapter-redis.cjs.map +1 -1
- package/dist/shared/client/adapter-redis.d.cts +1 -1
- package/dist/shared/client/adapter-redis.d.ts +1 -1
- package/dist/shared/client/adapter-redis.js +34 -1
- package/dist/shared/client/adapter-redis.js.map +1 -1
- package/dist/{types-Bl-rkLKX.d.cts → types-C1vJ_sED.d.cts} +25 -1
- package/dist/{types-Bl-rkLKX.d.ts → types-C1vJ_sED.d.ts} +25 -1
- package/llms.txt +27 -0
- package/package.json +27 -2
- package/rules/nextjs-cache-rules.md +23 -0
- package/setup-instructions/setup.md +179 -0
- package/skills/nextjs-redis-cache/SKILL.md +182 -0
|
@@ -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,179 @@
|
|
|
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
|
|
26
|
+
--short HEAD`; if the directory is not a git repo, fall back to a fixed
|
|
27
|
+
value the user chooses (any stable per-deploy string works).
|
|
28
|
+
|
|
29
|
+
Constraints:
|
|
30
|
+
|
|
31
|
+
- You MUST NOT use this package for Vercel-hosted deployments — tell the
|
|
32
|
+
user Vercel's managed cache already covers them, and stop.
|
|
33
|
+
- You MUST detect the Next.js version before choosing which handlers to wire.
|
|
34
|
+
- You MUST show the `next.config` diff and get explicit approval before
|
|
35
|
+
writing it — this file frequently contains user customizations — unless
|
|
36
|
+
the user has already given blanket approval for changes in this project.
|
|
37
|
+
- You MUST NOT print or commit secrets (Redis passwords, URLs with auth).
|
|
38
|
+
- You MUST install version >= 0.3.3 (earlier versions have an incomplete
|
|
39
|
+
soft-invalidation path).
|
|
40
|
+
|
|
41
|
+
## Steps
|
|
42
|
+
|
|
43
|
+
### Step 1: Detect the project
|
|
44
|
+
|
|
45
|
+
Read `package.json`. Determine:
|
|
46
|
+
|
|
47
|
+
- `dependencies.next` version → **15.x** (singular handler only) or
|
|
48
|
+
**>= 16.1.5** (both handlers). If `< 15.0.0`, STOP: unsupported
|
|
49
|
+
(peer range is `>=15.0.0 <17`).
|
|
50
|
+
- Which Redis client is already installed (`redis` or `ioredis`); if neither,
|
|
51
|
+
default to `redis` unless the user needs Cluster or Sentinel (those
|
|
52
|
+
require `ioredis`).
|
|
53
|
+
|
|
54
|
+
**Error handling:**
|
|
55
|
+
|
|
56
|
+
| Symptom | Cause | Resolution |
|
|
57
|
+
|---------|-------|------------|
|
|
58
|
+
| No `next` in dependencies | Not a Next.js project | Ask the user for the correct project directory |
|
|
59
|
+
| Next < 15 | Unsupported | Suggest upgrading Next, or stop |
|
|
60
|
+
|
|
61
|
+
### Step 2: Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @leejpsd/nextjs-cache-handler redis
|
|
65
|
+
# or, when Cluster/Sentinel is needed:
|
|
66
|
+
npm install @leejpsd/nextjs-cache-handler ioredis
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Success:** exit code 0, no peer-dependency conflicts (the package supports
|
|
70
|
+
`next >=15.0.0 <17`).
|
|
71
|
+
|
|
72
|
+
### Step 3: Generate wrapper files
|
|
73
|
+
|
|
74
|
+
Create `cache-incremental.cjs` in the project root (ALL versions):
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const { createIncrementalCacheHandler } = require("@leejpsd/nextjs-cache-handler/incremental");
|
|
78
|
+
module.exports = createIncrementalCacheHandler({
|
|
79
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
80
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On **Next 16** also create `cache-components.cjs`:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const { createCacheComponentsHandler } = require("@leejpsd/nextjs-cache-handler/cache-components");
|
|
88
|
+
module.exports = createCacheComponentsHandler({
|
|
89
|
+
client: { type: "redis", url: process.env.REDIS_URL },
|
|
90
|
+
buildNamespace: () => process.env.DEPLOYMENT_VERSION,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `{ type: "ioredis", ... }` / `cluster` / `sentinel` variants when the
|
|
95
|
+
user's infrastructure requires them (Cluster additionally needs
|
|
96
|
+
`hashTag: true` in the handler options).
|
|
97
|
+
|
|
98
|
+
### Step 4: Patch next.config (approval required)
|
|
99
|
+
|
|
100
|
+
If no `next.config.(js|ts|mjs)` exists, CREATE `next.config.js` with just
|
|
101
|
+
these keys. If one exists, merge the keys into it — do not replace the
|
|
102
|
+
file. Show the diff and get approval first, unless the user already gave
|
|
103
|
+
blanket approval for this project's files:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
cacheHandler: require.resolve("./cache-incremental.cjs"),
|
|
107
|
+
cacheHandlers: { default: require.resolve("./cache-components.cjs") }, // Next 16 only
|
|
108
|
+
cacheComponents: true, // Next 16 only — see the warning below before enabling
|
|
109
|
+
cacheMaxMemorySize: 0, // required for multi-instance correctness
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**`cacheComponents: true` decision rule**: enabling it is a MODE SWITCH, not
|
|
113
|
+
a free feature — it is incompatible with route segment configs like
|
|
114
|
+
`export const revalidate` / `export const dynamic` and will FAIL THE BUILD
|
|
115
|
+
of apps that use them. Enable it only when the app already uses (or is
|
|
116
|
+
migrating to) `'use cache'`. When in doubt, wire `cacheHandlers` but leave
|
|
117
|
+
`cacheComponents` off and tell the user how to enable it later.
|
|
118
|
+
|
|
119
|
+
Show the diff, get approval, then write.
|
|
120
|
+
|
|
121
|
+
**Error handling:**
|
|
122
|
+
|
|
123
|
+
| Symptom | Cause | Resolution |
|
|
124
|
+
|---------|-------|------------|
|
|
125
|
+
| Config is ESM (`next.config.mjs`) | `require.resolve` unavailable | Use `import { createRequire } from "module"; const require = createRequire(import.meta.url);` at the top |
|
|
126
|
+
| `cacheHandler` already set | Another handler in use | Show both, ask the user which should win |
|
|
127
|
+
|
|
128
|
+
### Step 5: Environment
|
|
129
|
+
|
|
130
|
+
Tell the user to provide in every runtime environment (NOT build-only):
|
|
131
|
+
|
|
132
|
+
- `REDIS_URL` — e.g. `rediss://...` for TLS/managed Redis
|
|
133
|
+
- `DEPLOYMENT_VERSION` — per-deploy value (git SHA); in Docker it MUST be
|
|
134
|
+
declared in the **runner** stage
|
|
135
|
+
|
|
136
|
+
Append both to `.env.example` if the project has one.
|
|
137
|
+
|
|
138
|
+
### Step 6: Verify
|
|
139
|
+
|
|
140
|
+
With a local Redis (`docker run -d -p 6379:6379 redis:7-alpine` or a native
|
|
141
|
+
`redis-server`), run (pick a free port explicitly — do not assume 3000):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
REDIS_URL=redis://127.0.0.1:6379 DEPLOYMENT_VERSION=setup-verify npm run build
|
|
145
|
+
REDIS_URL=redis://127.0.0.1:6379 DEPLOYMENT_VERSION=setup-verify npm run start -- -p 3777 &
|
|
146
|
+
# wait for readiness before curling:
|
|
147
|
+
for i in $(seq 1 30); do curl -sf http://127.0.0.1:3777/ >/dev/null && break; sleep 1; done
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Check the `x-nextjs-cache` header on an ISR/static page. **Two valid
|
|
151
|
+
outcomes**: build-time-prerendered pages are often served as `HIT` from the
|
|
152
|
+
very first request; on-demand pages show `MISS` then `HIT`. Either way,
|
|
153
|
+
confirm keys exist with `redis-cli --scan --pattern 'next-incremental:*'`
|
|
154
|
+
(never use `KEYS` — it blocks Redis).
|
|
155
|
+
|
|
156
|
+
The strongest proof is a full round-trip: delete the page's entry key with
|
|
157
|
+
the server running, then request twice — expect `MISS` then `HIT` and the
|
|
158
|
+
key to REAPPEAR in Redis (proves both read and write paths). Kill the
|
|
159
|
+
server afterwards and verify the port is free.
|
|
160
|
+
|
|
161
|
+
**Error handling:**
|
|
162
|
+
|
|
163
|
+
| Symptom | Cause | Resolution |
|
|
164
|
+
|---------|-------|------------|
|
|
165
|
+
| `ECONNREFUSED` during build | Expected — build phase skips Redis | Ignore unless the build itself fails |
|
|
166
|
+
| No `next-*` keys after requests | Handler not loaded | Check `next.config` paths resolve; check server logs for `[next-cache]` lines |
|
|
167
|
+
| `CROSSSLOT` errors | Redis Cluster without `hashTag: true` | Add the option in both wrappers |
|
|
168
|
+
|
|
169
|
+
### Step 7: Add the agent rules block
|
|
170
|
+
|
|
171
|
+
Identify the AI tool's rules file (CLAUDE.md / AGENTS.md / .cursor/rules/)
|
|
172
|
+
and append the contents of:
|
|
173
|
+
|
|
174
|
+
https://raw.githubusercontent.com/leejpsd/nextjs-cache-handler/main/rules/nextjs-cache-rules.md
|
|
175
|
+
|
|
176
|
+
End by telling the user: "Redis caching is wired. Set REDIS_URL and
|
|
177
|
+
DEPLOYMENT_VERSION in your deployment environment, and see the
|
|
178
|
+
nextjs-redis-cache skill for invalidation semantics and production
|
|
179
|
+
checklist."
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
| `tagPubSub: true` | off | 0.4+: push-based cross-instance invalidation (~3ms vs seconds); polling stays as safety net; not on Cluster |
|
|
113
|
+
|
|
114
|
+
Reliability built in (0.3+): reconnect with exponential backoff (1s→30s cap),
|
|
115
|
+
bounded memory fallback, per-op timeouts. A Redis outage degrades to
|
|
116
|
+
in-memory serving; reconnection is automatic (validated with a live
|
|
117
|
+
ElastiCache reboot drill: 2550 requests, zero 5xx).
|
|
118
|
+
|
|
119
|
+
### 0.4+ deployment accelerators
|
|
120
|
+
|
|
121
|
+
- **Seed the cache at deploy time** so a fresh deployment's first requests
|
|
122
|
+
are HITs instead of a regeneration stampede:
|
|
123
|
+
`REDIS_URL=... DEPLOYMENT_VERSION=<deploy-id> npx nextjs-cache-handler seed`
|
|
124
|
+
(run after `next build`, e.g. a Docker entrypoint step; NX semantics —
|
|
125
|
+
never overwrites live entries).
|
|
126
|
+
- **`npx nextjs-cache-handler init --yes`** wires everything above
|
|
127
|
+
automatically; **`npx nextjs-cache-handler doctor`** is the first command
|
|
128
|
+
to run when debugging connectivity or key-layout issues.
|
|
129
|
+
- **MCP server** for cache operations from your agent:
|
|
130
|
+
`@leejpsd/nextjs-cache-handler-mcp` (cache_health, tag_state,
|
|
131
|
+
invalidate_tag dry-run, …).
|
|
132
|
+
|
|
133
|
+
## 6. Production checklist
|
|
134
|
+
|
|
135
|
+
- [ ] `DEPLOYMENT_VERSION` injected at runtime (runner stage in Docker)
|
|
136
|
+
- [ ] `cacheMaxMemorySize: 0` in next.config
|
|
137
|
+
- [ ] `output: "standalone"` + pinned `outputFileTracingRoot`
|
|
138
|
+
- [ ] Cluster → `hashTag: true`; managed Redis TLS → `rediss://`
|
|
139
|
+
- [ ] Redis `maxmemory-policy`: `allkeys-lru` (bounded) or `noeviction`
|
|
140
|
+
- [ ] Own `/api/health` endpoint pinging Redis (outages surface in monitoring, not as 5xx)
|
|
141
|
+
- [ ] OTel: `onMetric: createOtelMetricEmitter()` (needs `@opentelemetry/api` in the app)
|
|
142
|
+
|
|
143
|
+
## 7. Troubleshooting playbook (symptom → cause → fix)
|
|
144
|
+
|
|
145
|
+
| Symptom | Likely cause | Fix |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `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 |
|
|
148
|
+
| Page content never updates after `revalidateTag(tag, "max")` | version < 0.3.3 | Upgrade |
|
|
149
|
+
| 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 |
|
|
150
|
+
| `CROSSSLOT` errors | Redis Cluster without `hashTag: true` | Set the flag |
|
|
151
|
+
| Cache misses on every request across instances | `cacheMaxMemorySize` not 0, or different `keyPrefix`/namespace per instance | Align config; verify with `redis-cli KEYS 'next-*'` |
|
|
152
|
+
| 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 |
|
|
153
|
+
|
|
154
|
+
Key layout for redis-cli debugging:
|
|
155
|
+
`next-cache:entry:<deploy>:<key>` · `next-cache:tag:<deploy>:<tag>` ·
|
|
156
|
+
`next-cache:tag-expiration:<deploy>:<tag>` ·
|
|
157
|
+
`next-incremental:entry:<deploy>:<path>` · `next-incremental:tag:<tag>`
|
|
158
|
+
(ISR tag states are deliberately NOT namespaced — invalidations survive deploys).
|
|
159
|
+
|
|
160
|
+
## 8. Verification recipes
|
|
161
|
+
|
|
162
|
+
- **ISR round-trip**: `curl -D- <url>` twice → `x-nextjs-cache: MISS` then `HIT`;
|
|
163
|
+
entry visible in `redis-cli KEYS 'next-incremental:entry:*'`.
|
|
164
|
+
- **Soft SWR probe**: create a `'use cache'` + `cacheTag("probe")` +
|
|
165
|
+
`cacheLife("hours")` page rendering `Date.now()` with a deliberate ~800ms
|
|
166
|
+
delay inside the cached fn. Fire `revalidateTag("probe", "max")` from a
|
|
167
|
+
route handler. Expect: instant response with the OLD value (stale serve),
|
|
168
|
+
new value within ~1-2s on all instances. A blocking implementation would
|
|
169
|
+
eat the 800ms — that's the tell.
|
|
170
|
+
- **Multi-instance**: run two instances against one Redis; a value cached by
|
|
171
|
+
instance A must be served by instance B; after invalidation both converge.
|
|
172
|
+
|
|
173
|
+
## 9. AWS deployment
|
|
174
|
+
|
|
175
|
+
The reference topology is ALB → ECS Fargate (2+ tasks) → ElastiCache Redis.
|
|
176
|
+
Use the AWS agent skills/MCP for the infrastructure itself; use THIS skill
|
|
177
|
+
for the cache wiring on top. ElastiCache notes: `rediss://` for in-transit
|
|
178
|
+
encryption; single-node is fine to start (reconnect backoff covers node
|
|
179
|
+
reboots); Sentinel/Cluster supported per §3.
|
|
180
|
+
|
|
181
|
+
Full API reference: `node_modules/@leejpsd/nextjs-cache-handler/README.md`
|
|
182
|
+
and `docs/api.md` in the repo (https://github.com/leejpsd/nextjs-cache-handler).
|