Releases: leejpsd/nextjs-cache-handler
Release list
v1.0.0
Major Changes
-
3c064b5Thanks @leejpsd! - 1.0 — the public API is now stable under semver.Nothing changes for existing 0.4.x users: there are no breaking changes in this release. The version bump is a commitment, not a rewrite. From here, anything that would break a working setup waits for 2.0.
What that commitment covers:
- The eight entry points and everything they export, all of which are now documented in
docs/api.md— including the SWR helpers (partitionEntry,shouldServeStale) that had been exported since 0.1 without being written down. CacheHandlerOptionsand its twelve options.- Redis key shapes and the tag-marker wire format, so mixed-version fleets stay readable in both directions during a rolling deploy.
- Metric event names.
Why now: the handler has been running against a real multi-instance deployment (24h live-traffic soak, plus a Redis-reboot drill with zero 5xx), both cache interfaces are exercised end-to-end against Next.js 16.3 stable, and the tag-invalidation semantics were re-derived from the Next.js source and corrected in 0.4.2. The remaining roadmap items (Vercel KV / Upstash adapters,
'use cache: remote'multi-tier, aneshClassicCacheequivalent) are additive and do not need a breaking change to land. - The eight entry points and everything they export, all of which are now documented in
v0.4.2
Patch Changes
-
c165563Thanks @leejpsd! - Align'use cache'tag invalidation exactly with upstream semantics.Fixed: hard/soft polarity was inverted for
updateTagswithout durations. Next.js callsupdateTags(tags)with NO durations for hard invalidation (updateTag()server actions, single-argrevalidateTag()) andupdateTags(tags, { expire })for profile-based soft invalidation (revalidateTag(tag, "max")). The handler treated no-durations as soft, soupdateTag()— which must guarantee read-your-own-writes — could serve stale content. No-durations is now hard.Also in this release:
- Soft invalidations now honor their hard deadline: entries serve stale until
durations.expireseconds after the invalidation, then miss (Infinity= never). Tag markers store a"<stale>|<expired>"pair; 0.4.1 readersparseIntthe same marker safely, and legacy bare-number markers are treated as hard-at-timestamp, so mixed-version fleets stay correct in both directions. - Tag-stale entries are served with the truthful timestamp and
revalidate: -1(the built-in handler's refresh signal) instead of a backdated timestamp, which could cross theexpireboundary on tight profiles and confused the implicit-tags discard check. - Soft invalidations serve-while-revalidating regardless of
staleWhileRevalidate(that option now governs time-based staleness only, matching the upstream contract). getExpirationreports only the hard deadline, mirroring the built-in handler.expire: 0entries are no longer persisted in production (mirrors Next 16.3's built-in handler; they are regenerated on every read anyway).- Verified end-to-end against Next.js 16.3.0 stable (both interfaces; build, serve, hard/soft invalidation, ISR SWR).
- Soft invalidations now honor their hard deadline: entries serve stale until
v0.4.1
Patch Changes
-
8555fbeThanks @leejpsd! - Fix ISR record serialization writing Buffers as JSON byte arrays instead of base64.Buffer.prototype.toJSONruns before aJSON.stringifyreplacer sees the value, so the base64 envelope branch was unreachable and every Buffer field (rendered HTML, RSC payloads, PPR segment data) was stored as{ type: "Buffer", data: [...] }— roughly 2.8x larger than base64. The replacer now reads the pre-toJSONoriginal viathis[key].No migration needed: the reviver has always accepted both shapes, so records written by either version deserialize correctly in both directions.
v0.4.0
Minor Changes
-
#7
bf047c9Thanks @leejpsd! - Build-output cache seeding:seedBuildOutput()(new/seedentry point) and
npx nextjs-cache-handler seedwalk.next/after a build and insert
prerendered App Router routes (including PPR segment data), Pages Router
routes, and fetch-cache entries into Redis in the handler's own record
format — with NX semantics so entries already written by live instances are
never overwritten. A fresh deployment's first requests are cache HITs
instead of a regeneration stampede (verified on a real Next 16 app: cold
server + seeded Redis → first requestx-nextjs-cache: HIT). The
RedisClientLike.setcontract gains an optionalNXflag. -
#7
1e57e03Thanks @leejpsd! - Newnextjs-cache-handlerCLI (zero-dependency):initdetects the Next.js
version and Redis client, generates the handler wrapper shims, shows the
next.config keys to add (never edits it), appends env templates, and injects
the agent rules block into CLAUDE.md/AGENTS.md idempotently (--yesto
apply,--skillsto install the agent skill locally).doctorverifies
Redis connectivity, inspects cache key namespaces, and runs a write/read
round-trip — the first command an agent should reach for when debugging. -
#7
6cd5901Thanks @leejpsd! - Opt-in push-based tag propagation (tagPubSub: true, plural handler):
updateTags()publishes invalidations on a namespaced channel and every
instance maintains a subscription on a dedicated duplicate connection,
updating its local tag mirror in ~3 ms (measured cross-instance over real
Redis with both redis@5 and ioredis) instead of waiting for the next
refreshTags()scan (~seconds). The scan keeps running as the consistency
safety net, so a dropped subscription degrades to the previous behavior —
never to staleness. Cluster clients fall back to polling with a one-time
warning.RedisClientLikegains optionalpublish/subscribe.
v0.3.4
Patch Changes
79e112bThanks @leejpsd! - Ship agent-native assets in the package:AGENTS.md(agent guidance
discovered via node_modules scans), an agent skill
(skills/nextjs-redis-cache/SKILL.md— decision tables, invalidation
semantics, troubleshooting playbook), agent-executable setup instructions
(setup-instructions/setup.md), a project rules block (rules/), and
llms.txt. README gains a "For AI agents" section. No runtime code changes.
v0.3.3
Patch Changes
692b32bThanks @leejpsd! - Complete the soft-invalidation fix at the route layer: the ISR handler now
serves non-fetch entries whose tag was softly revalidated as
stale-while-revalidate —lastModifiedis backdated just past the entry's
own revalidate window (captured at set() time asrevalidateSec) so Next
serves the cached HTML instantly and regenerates in the background, which
also re-executes the route's'use cache'functions. Entries without an SWR
window (revalidate: falseor pre-upgrade records) degrade to a blocking
miss so the invalidation still lands. Verified end-to-end on Next 16.2.3
with two instances sharing Redis: stale served in ~15 ms during an 800 ms
render, background refresh converging both instances.
v0.3.2
Patch Changes
6f35ff3Thanks @leejpsd! - Fix: soft revalidation of explicitcacheTag()tags —revalidateTag(tag, "max")— was a no-op on time-fresh entries.get()now folds the entry's own tags into the freshness check and serves tag-invalidated entries as stale-while-revalidate (backdated pastrevalidateso Next schedules a background re-render), matching the spec's soft-invalidation semantics. Hard invalidation ({ expire: 0 }) was already correct. Thanks to @eveyrat for the report (#1) and @unitedworldwrestling for the fix approach (#2).
v0.3.1
v0.3.0
Minor Changes
dc1ac0aThanks @leejpsd! - Reliability fixes (reconnect backoff instead of a permanent connect-failure
latch, bounded memory fallback, namespace-scoped tag propagation without
truncation, strictfallback: "never"honored by the ISR handler, working
ESM peer loading) plus new features: Next.js 15 ISR support, request-scoped
GET deduplication, transparent gzip/brotli value compression, Redis Sentinel
support, a built-in OpenTelemetry adapter at/otel, andmemoryMaxEntries.
All notable changes to this project are documented here. The format is
based on Keep a Changelog, and
this project adheres to Semantic Versioning.
v0.2.0 — single-flight, OTel, integration tests
First minor bump. Three differentiators land in this release.
Added
- Single-flight refresh lock (
singleFlight: true,
singleFlightLockTtlSec: 10). Opt-in stampede protection at the SWR
boundary. Uses the bundledrefresh-tag-lock.luascript over a Redis
SETNX-style lock; only the leader's stale read triggers the
background refresh, all others fall through to the follower path.
Two newMetricEventtypes (cache.stale.refresh.leader,
cache.stale.refresh.follower) appear inonMetricso operators can
verify leadership balance. Lock-acquisition failures are best-effort
and always degrade to the safe follower path; the stale entry is
served regardless. - OpenTelemetry reference adapter at
examples/opentelemetry/.
The library itself stays dependency-free; the example shows the
smallest viable wiring ofonMetricto OTel counters and histograms
with bounded cardinality (no cache keys or tag names emitted as
attributes). - Integration tests against real Redis 7 (21 scenarios) covering
bothredis@5andioredisadapters. Each adapter runs the same
test grid so a regression in either client library is pinpointed
immediately. New scripts:npm run test:integration,
npm run test:integration:up/:downfor the docker-compose
fixture. - CI integration job runs the integration suite against an
ephemeral Redis 7 service container on every PR. - GitHub Actions OIDC publish path in
.github/workflows/release.yml
withid-token: writeandNPM_CONFIG_PROVENANCE=true. Currently
scoped toworkflow_dispatchtrigger so the first GHA-driven publish
can be observed; flip topush: mainonce the changesets cadence
stabilizes. Tarballs published from the workflow will land on npm
with verified provenance.
Changed
MetricEventTypeunion grew three new entries:
cache.stale.refresh.leader,cache.stale.refresh.follower,
cache.stale.refresh.skipped. The legacycache.staleevent still
fires whensingleFlightis off (default).HandlerStatenow carries aninstanceId(read fromHOSTNAME/
ECS_TASK_ID/pid-<pid>) and embeds it in every refresh-lock
acquisition for operator-side observability.
Compatibility
- No breaking changes. All existing 0.1.x configurations continue to
work;singleFlightdefaults tofalse.
Verified
- 72 unit tests (8 files) + 21 integration tests (1 file × 21 scenarios)
all green. - arethetypeswrong: 28/28 cells green.
- publint: clean.
Install: npm install @leejpsd/nextjs-cache-handler
npm: https://www.npmjs.com/package/@leejpsd/nextjs-cache-handler/v/0.2.0