Skip to content

Releases: leejpsd/nextjs-cache-handler

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 26 Aug 16:58
348ec05

Major Changes

  • 3c064b5 Thanks @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.
    • CacheHandlerOptions and 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, a neshClassicCache equivalent) are additive and do not need a breaking change to land.

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 05 Aug 07:03
3c20bbc

Patch Changes

  • c165563 Thanks @leejpsd! - Align 'use cache' tag invalidation exactly with upstream semantics.

    Fixed: hard/soft polarity was inverted for updateTags without durations. Next.js calls updateTags(tags) with NO durations for hard invalidation (updateTag() server actions, single-arg revalidateTag()) and updateTags(tags, { expire }) for profile-based soft invalidation (revalidateTag(tag, "max")). The handler treated no-durations as soft, so updateTag() — 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.expire seconds after the invalidation, then miss (Infinity = never). Tag markers store a "<stale>|<expired>" pair; 0.4.1 readers parseInt the 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 the expire boundary 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).
    • getExpiration reports only the hard deadline, mirroring the built-in handler.
    • expire: 0 entries 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).

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 03 Aug 16:45
3b01e0a

Patch Changes

  • 8555fbe Thanks @leejpsd! - Fix ISR record serialization writing Buffers as JSON byte arrays instead of base64.

    Buffer.prototype.toJSON runs before a JSON.stringify replacer 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-toJSON original via this[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

Choose a tag to compare

@github-actions github-actions released this 02 Aug 05:30
f547f66

Minor Changes

  • #7 bf047c9 Thanks @leejpsd! - Build-output cache seeding: seedBuildOutput() (new /seed entry point) and
    npx nextjs-cache-handler seed walk .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 request x-nextjs-cache: HIT). The
    RedisClientLike.set contract gains an optional NX flag.

  • #7 1e57e03 Thanks @leejpsd! - New nextjs-cache-handler CLI (zero-dependency): init detects 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 (--yes to
    apply, --skills to install the agent skill locally). doctor verifies
    Redis connectivity, inspects cache key namespaces, and runs a write/read
    round-trip — the first command an agent should reach for when debugging.

  • #7 6cd5901 Thanks @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. RedisClientLike gains optional publish/subscribe.

v0.3.4

Choose a tag to compare

@github-actions github-actions released this 31 Jul 23:56
6f67361

Patch Changes

  • 79e112b Thanks @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

Choose a tag to compare

@github-actions github-actions released this 31 Jul 17:20
b27b3a7

Patch Changes

  • 692b32b Thanks @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 — lastModified is backdated just past the entry's
    own revalidate window (captured at set() time as revalidateSec) 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: false or 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

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:59
6818ae7

Patch Changes

  • 6f35ff3 Thanks @leejpsd! - Fix: soft revalidation of explicit cacheTag() 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 past revalidate so 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

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:49
249ace5

Patch Changes

  • f07e91b Thanks @leejpsd! - Refresh the npm package description and keywords for the 0.3 feature set:
    Next.js 15/16, built-in compression, Redis Sentinel, OpenTelemetry, and the
    multi-instance AWS validation results.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:41

Minor Changes

  • dc1ac0a Thanks @leejpsd! - Reliability fixes (reconnect backoff instead of a permanent connect-failure
    latch, bounded memory fallback, namespace-scoped tag propagation without
    truncation, strict fallback: "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, and memoryMaxEntries.

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

Choose a tag to compare

@leejpsd leejpsd released this 09 May 21:36

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 bundled refresh-tag-lock.lua script 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 new MetricEvent types (cache.stale.refresh.leader,
    cache.stale.refresh.follower) appear in onMetric so 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 of onMetric to 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
    both redis@5 and ioredis adapters. 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 / :down for 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
    with id-token: write and NPM_CONFIG_PROVENANCE=true. Currently
    scoped to workflow_dispatch trigger so the first GHA-driven publish
    can be observed; flip to push: main once the changesets cadence
    stabilizes. Tarballs published from the workflow will land on npm
    with verified provenance.

Changed

  • MetricEventType union grew three new entries:
    cache.stale.refresh.leader, cache.stale.refresh.follower,
    cache.stale.refresh.skipped. The legacy cache.stale event still
    fires when singleFlight is off (default).
  • HandlerState now carries an instanceId (read from HOSTNAME /
    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; singleFlight defaults to false.

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