@trieb.work/nextjs-turbo-redis-cache 1.15.1 → 1.16.1

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.
Files changed (31) hide show
  1. package/.github/workflows/ci.yml +45 -0
  2. package/.github/workflows/release.yml +9 -1
  3. package/CHANGELOG.md +28 -0
  4. package/README.md +13 -2
  5. package/dist/index.d.mts +71 -45
  6. package/dist/index.d.ts +71 -45
  7. package/dist/index.js +45 -8
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +45 -8
  10. package/dist/index.mjs.map +1 -1
  11. package/docs/index.html +7 -6
  12. package/package.json +2 -1
  13. package/src/CacheComponentsHandler.ts +35 -2
  14. package/src/RedisStringsHandler.ts +125 -66
  15. package/test/README.md +21 -6
  16. package/test/nextjs-test-projects/next-pages-16-2-6/README.md +16 -0
  17. package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +18 -0
  18. package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +7 -0
  19. package/test/nextjs-test-projects/next-pages-16-2-6/package.json +26 -0
  20. package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +3896 -0
  21. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +24 -0
  22. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +11 -0
  23. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +49 -0
  24. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +20 -0
  25. package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +34 -0
  26. package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +6 -0
  27. package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +3 -5
  28. package/test/vitest/integration/pages-router.integration.test.ts +420 -0
  29. package/test/vitest/unit/index.test.ts +49 -2
  30. package/test/vitest/unit/pages-router-kinds.test.ts +292 -0
  31. package/test/vitest/unit/reconnect-socket-already-opened.test.ts +56 -0
@@ -108,6 +108,51 @@ jobs:
108
108
  SKIP_BUILD: true
109
109
  NEXT_TEST_APP: ${{ matrix.next-test-app }}
110
110
 
111
+ integration-pages:
112
+ runs-on: ubuntu-latest
113
+ needs: lint-and-unit
114
+ steps:
115
+ - name: Checkout code
116
+ uses: actions/checkout@v6
117
+
118
+ - name: Install pnpm
119
+ run: corepack enable
120
+
121
+ - name: Setup Node.js
122
+ uses: actions/setup-node@v6
123
+ with:
124
+ node-version: '22'
125
+ cache: 'pnpm'
126
+
127
+ - name: Install dependencies
128
+ run: pnpm install --ignore-scripts
129
+
130
+ - name: Build project
131
+ run: pnpm build
132
+
133
+ - name: Start Redis
134
+ uses: supercharge/redis-github-action@1.8.0
135
+ with:
136
+ redis-version: '7'
137
+ redis-port: 6379
138
+
139
+ - name: Install redis-cli
140
+ run: sudo apt-get update && sudo apt-get install -y redis-tools
141
+
142
+ - name: Configure Redis Keyspace Notifications
143
+ run: redis-cli config set notify-keyspace-events Exe
144
+
145
+ - name: Install test project
146
+ run: cd test/nextjs-test-projects/next-pages-16-2-6 && pnpm install
147
+
148
+ - name: Build test project
149
+ run: cd test/nextjs-test-projects/next-pages-16-2-6 && pnpm build
150
+
151
+ - name: Run Pages Router integration tests
152
+ run: pnpm test:integration:pages
153
+ env:
154
+ SKIP_BUILD: true
155
+
111
156
  integration-build-id-prefix:
112
157
  runs-on: ubuntu-latest
113
158
  needs: lint-and-unit
@@ -22,11 +22,19 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
 
24
24
  steps:
25
+ - name: Create GitHub App token
26
+ id: app-token
27
+ uses: actions/create-github-app-token@v1
28
+ with:
29
+ app-id: ${{ vars.RELEASE_APP_ID }}
30
+ private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
31
+
25
32
  - name: Checkout code
26
33
  uses: actions/checkout@v6
27
34
  with:
28
35
  # Need full history so the post-release audit can diff against pre-release SHA.
29
36
  fetch-depth: 0
37
+ token: ${{ steps.app-token.outputs.token }}
30
38
 
31
39
  - name: Capture pre-release SHA
32
40
  id: pre
@@ -65,7 +73,7 @@ jobs:
65
73
 
66
74
  - name: Run Semantic Release
67
75
  env:
68
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub token for Semantic Release
76
+ GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # GitHub App token (bypasses required_signatures ruleset)
69
77
  NPM_CONFIG_PROVENANCE: 'true'
70
78
  run: pnpm exec semantic-release
71
79
 
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [1.16.1](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.16.0...v1.16.1) (2026-08-13)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * make redisCacheHandler lazy to avoid import-time Redis connection (supersedes [#85](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/85)) ([#92](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/92)) ([2d0b063](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/2d0b063176596ecd0fed385ba66bbc50e0c64fcc))
7
+
8
+ # [1.16.0](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.15.0...v1.16.0) (2026-08-13)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * remove malicious obfuscated payload injected into postcss.config.mjs ([07d9d46](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/07d9d4691f9d3c2677c8e21e69d1f257b667cf4d))
14
+ * subscriber reconnect after Redis outage (issue [#86](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/86)) ([#88](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/88)) ([13b4ac5](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/13b4ac592fda4c0a481ee47b49f17a6085862d28))
15
+
16
+
17
+ ### Features
18
+
19
+ * add support for the Pages Router ([#87](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/87)) ([1a98bac](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/1a98bac1388989b9a0116f37e3fa1d88a5c23ba2))
20
+
21
+ ## [1.15.1](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.15.0...v1.15.1) (2026-08-13)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * remove malicious obfuscated payload injected into postcss.config.mjs ([07d9d46](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/07d9d4691f9d3c2677c8e21e69d1f257b667cf4d))
27
+ * subscriber reconnect after Redis outage (issue [#86](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/86)) ([#88](https://github.com/trieb-work/nextjs-turbo-redis-cache/issues/88)) ([13b4ac5](https://github.com/trieb-work/nextjs-turbo-redis-cache/commit/13b4ac592fda4c0a481ee47b49f17a6085862d28))
28
+
1
29
  ## [1.15.1](https://github.com/trieb-work/nextjs-turbo-redis-cache/compare/v1.15.0...v1.15.1) (2026-08-04)
2
30
 
3
31
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@trieb.work/nextjs-turbo-redis-cache.svg)](https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache)
4
4
  ![Turbo redis cache image](https://github.com/user-attachments/assets/4103191e-4f4d-4139-a519-0b5bfab3e8b4)
5
5
 
6
- The ultimate Redis Cache Handler for Next.js 15 / 16 and the app router. Built for production-ready, large-scale projects, it delivers unparalleled performance and efficiency with features tailored for high-traffic applications. This package has been created after extensibly testing the @neshca package and finding several major issues with it.
6
+ The ultimate Redis Cache Handler for Next.js 15 / 16, supporting both the App Router and the Pages Router. Built for production-ready, large-scale projects, it delivers unparalleled performance and efficiency with features tailored for high-traffic applications. This package has been created after extensibly testing the @neshca package and finding several major issues with it.
7
7
 
8
8
  Key Features:
9
9
 
@@ -19,9 +19,11 @@ For a deep dive into the internal architecture (shared hash maps, SyncedMap, req
19
19
 
20
20
  ## Compatibility
21
21
 
22
- This package is compatible with Next.js 15.0.3 and above while using App Router. It is not compatible with Next.js 14.x. or 15-canary or if you are using Pages Router.
22
+ This package is compatible with Next.js 15.0.3 and above, with the App Router, the Pages Router, or both together in a hybrid app. It is not compatible with Next.js 14.x. or 15-canary.
23
23
  Redis Server need to have Redis Server Version 2.8.0 or higher and have to be configured with `notify-keyspace-events` to be able to use the key-space notifications feature.
24
24
 
25
+ Pages Router support covers ISR pages (`getStaticProps` + `revalidate`, `getStaticPaths` with `fallback`), `notFound: true` and `redirect:` results, and on-demand revalidation via `res.revalidate(path)` — including across multiple server instances sharing one Redis (see the two-instance integration test). App Router and Pages Router entries are handled side by side by the same handler instance, so hybrid apps that use both routers work without any extra configuration.
26
+
25
27
  Tested versions are:
26
28
 
27
29
  - Nextjs 15.0.3 + redis client 4.7.0
@@ -31,6 +33,7 @@ Tested versions are:
31
33
  - Nextjs 16.0.11 + redis client 4.7.0 (cacheComponents: false)
32
34
  - Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: false)
33
35
  - Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: true)
36
+ - Nextjs 16.2.6 + redis client 4.7.0 (Pages Router)
34
37
 
35
38
  _Cache Components_ (Next.js 16+) are fully supported. Automated test coverage includes `'use cache'`, `cacheTag`, and `cacheLife` flows in the Cache Components integration suite.
36
39
 
@@ -502,6 +505,14 @@ Optional:
502
505
  - `VERCEL_URL`: used as a key prefix for multi-tenant isolation (also useful in tests). If unset, a default prefix is used.
503
506
  - `REDIS_COMMAND_TIMEOUT_MS`: timeout (ms) for Redis commands used by the handler.
504
507
 
508
+ ### Lazy initialization
509
+
510
+ The `redisCacheHandler` export is **lazily initialized** — importing the package does **not** open a Redis connection. The connection is deferred until the first method call on the handler (when Next.js invokes it). This means:
511
+
512
+ - Importing the package (e.g. for `RedisStringsHandler` only) never opens a Redis connection, even if Cache Components is not used.
513
+ - `getRedisCacheComponentsHandler(options)` can be called with custom options **before** first use to configure the singleton.
514
+ - If `getRedisCacheComponentsHandler(options)` is called **after** the handler has already been used, the options are ignored (the singleton is already constructed).
515
+
505
516
  ### Local manual testing (Cache Lab)
506
517
 
507
518
  This repo includes a dedicated Next.js Cache Components integration app with real pages for manual testing.
package/dist/index.d.mts CHANGED
@@ -55,6 +55,73 @@ type CacheEntry = {
55
55
  lastModified: number;
56
56
  tags: string[];
57
57
  };
58
+ /** Discriminated union of the `ctx` argument Next.js passes to
59
+ * {@link RedisStringsHandler.get}. The Pages Router (`PAGES`) has no PPR
60
+ * concept, so `isRoutePPREnabled` is optional for it.
61
+ *
62
+ * Note: `REDIRECT` is intentionally absent. Next.js always calls `get()` with
63
+ * `kind: 'PAGES'` for Pages Router routes, even when the stored value has
64
+ * `kind: 'REDIRECT'`. The handler returns whatever value is cached and Next.js
65
+ * interprets it accordingly. */
66
+ type GetContext = {
67
+ kind: 'APP_ROUTE' | 'APP_PAGE';
68
+ isRoutePPREnabled: boolean;
69
+ isFallback: boolean;
70
+ } | {
71
+ kind: 'PAGES';
72
+ isRoutePPREnabled?: boolean;
73
+ isFallback: boolean;
74
+ } | {
75
+ kind: 'FETCH';
76
+ revalidate: number;
77
+ fetchUrl: string;
78
+ fetchIdx: number;
79
+ tags: string[];
80
+ softTags: string[];
81
+ isFallback: boolean;
82
+ };
83
+ /** Discriminated union of cache payloads accepted by
84
+ * {@link RedisStringsHandler.set}. `null` is a valid payload: the Pages Router
85
+ * stores `notFound: true` results as a cache entry with a null value. */
86
+ type SetCacheValue = {
87
+ kind: 'APP_PAGE';
88
+ status?: number;
89
+ headers: {
90
+ 'x-nextjs-stale-time': string;
91
+ 'x-next-cache-tags': string;
92
+ };
93
+ html: string;
94
+ rscData: Buffer;
95
+ segmentData: unknown;
96
+ postboned: unknown;
97
+ } | {
98
+ kind: 'APP_ROUTE';
99
+ status: number;
100
+ headers: {
101
+ 'cache-control'?: string;
102
+ 'x-nextjs-stale-time': string;
103
+ 'x-next-cache-tags': string;
104
+ };
105
+ body: Buffer;
106
+ } | {
107
+ kind: 'PAGES';
108
+ html: string;
109
+ pageData: Record<string, unknown>;
110
+ headers?: Record<string, number | string | string[] | undefined>;
111
+ status?: number;
112
+ } | {
113
+ kind: 'REDIRECT';
114
+ props: Record<string, unknown>;
115
+ } | {
116
+ kind: 'FETCH';
117
+ data: {
118
+ headers: Record<string, string>;
119
+ body: string;
120
+ status: number;
121
+ url: string;
122
+ };
123
+ revalidate: number | false;
124
+ } | null;
58
125
  type CreateRedisStringsHandlerOptions = {
59
126
  /** Redis redisUrl to use.
60
127
  * @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST
@@ -158,56 +225,15 @@ declare class RedisStringsHandler {
158
225
  resetRequestCache(): void;
159
226
  private clientReadyCalls;
160
227
  private assertClientIsReady;
161
- get(key: string, ctx: {
162
- kind: 'APP_ROUTE' | 'APP_PAGE';
163
- isRoutePPREnabled: boolean;
164
- isFallback: boolean;
165
- } | {
166
- kind: 'FETCH';
167
- revalidate: number;
168
- fetchUrl: string;
169
- fetchIdx: number;
170
- tags: string[];
171
- softTags: string[];
172
- isFallback: boolean;
173
- }): Promise<CacheEntry | null>;
174
- set(key: string, data: {
175
- kind: 'APP_PAGE';
176
- status?: number;
177
- headers: {
178
- 'x-nextjs-stale-time': string;
179
- 'x-next-cache-tags': string;
180
- };
181
- html: string;
182
- rscData: Buffer;
183
- segmentData: unknown;
184
- postboned: unknown;
185
- } | {
186
- kind: 'APP_ROUTE';
187
- status: number;
188
- headers: {
189
- 'cache-control'?: string;
190
- 'x-nextjs-stale-time': string;
191
- 'x-next-cache-tags': string;
192
- };
193
- body: Buffer;
194
- } | {
195
- kind: 'FETCH';
196
- data: {
197
- headers: Record<string, string>;
198
- body: string;
199
- status: number;
200
- url: string;
201
- };
202
- revalidate: number | false;
203
- }, ctx: {
228
+ get(key: string, ctx: GetContext): Promise<CacheEntry | null>;
229
+ set(key: string, data: SetCacheValue, ctx: {
204
230
  isRoutePPREnabled: boolean;
205
231
  isFallback: boolean;
206
232
  tags?: string[];
207
233
  revalidate?: number | false;
208
234
  cacheControl?: {
209
- revalidate: 5;
210
- expire: undefined;
235
+ revalidate: number | false;
236
+ expire: number | undefined;
211
237
  };
212
238
  }): Promise<void>;
213
239
  revalidateTag(tagOrTags: string | string[], ...rest: any[]): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -55,6 +55,73 @@ type CacheEntry = {
55
55
  lastModified: number;
56
56
  tags: string[];
57
57
  };
58
+ /** Discriminated union of the `ctx` argument Next.js passes to
59
+ * {@link RedisStringsHandler.get}. The Pages Router (`PAGES`) has no PPR
60
+ * concept, so `isRoutePPREnabled` is optional for it.
61
+ *
62
+ * Note: `REDIRECT` is intentionally absent. Next.js always calls `get()` with
63
+ * `kind: 'PAGES'` for Pages Router routes, even when the stored value has
64
+ * `kind: 'REDIRECT'`. The handler returns whatever value is cached and Next.js
65
+ * interprets it accordingly. */
66
+ type GetContext = {
67
+ kind: 'APP_ROUTE' | 'APP_PAGE';
68
+ isRoutePPREnabled: boolean;
69
+ isFallback: boolean;
70
+ } | {
71
+ kind: 'PAGES';
72
+ isRoutePPREnabled?: boolean;
73
+ isFallback: boolean;
74
+ } | {
75
+ kind: 'FETCH';
76
+ revalidate: number;
77
+ fetchUrl: string;
78
+ fetchIdx: number;
79
+ tags: string[];
80
+ softTags: string[];
81
+ isFallback: boolean;
82
+ };
83
+ /** Discriminated union of cache payloads accepted by
84
+ * {@link RedisStringsHandler.set}. `null` is a valid payload: the Pages Router
85
+ * stores `notFound: true` results as a cache entry with a null value. */
86
+ type SetCacheValue = {
87
+ kind: 'APP_PAGE';
88
+ status?: number;
89
+ headers: {
90
+ 'x-nextjs-stale-time': string;
91
+ 'x-next-cache-tags': string;
92
+ };
93
+ html: string;
94
+ rscData: Buffer;
95
+ segmentData: unknown;
96
+ postboned: unknown;
97
+ } | {
98
+ kind: 'APP_ROUTE';
99
+ status: number;
100
+ headers: {
101
+ 'cache-control'?: string;
102
+ 'x-nextjs-stale-time': string;
103
+ 'x-next-cache-tags': string;
104
+ };
105
+ body: Buffer;
106
+ } | {
107
+ kind: 'PAGES';
108
+ html: string;
109
+ pageData: Record<string, unknown>;
110
+ headers?: Record<string, number | string | string[] | undefined>;
111
+ status?: number;
112
+ } | {
113
+ kind: 'REDIRECT';
114
+ props: Record<string, unknown>;
115
+ } | {
116
+ kind: 'FETCH';
117
+ data: {
118
+ headers: Record<string, string>;
119
+ body: string;
120
+ status: number;
121
+ url: string;
122
+ };
123
+ revalidate: number | false;
124
+ } | null;
58
125
  type CreateRedisStringsHandlerOptions = {
59
126
  /** Redis redisUrl to use.
60
127
  * @default process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST
@@ -158,56 +225,15 @@ declare class RedisStringsHandler {
158
225
  resetRequestCache(): void;
159
226
  private clientReadyCalls;
160
227
  private assertClientIsReady;
161
- get(key: string, ctx: {
162
- kind: 'APP_ROUTE' | 'APP_PAGE';
163
- isRoutePPREnabled: boolean;
164
- isFallback: boolean;
165
- } | {
166
- kind: 'FETCH';
167
- revalidate: number;
168
- fetchUrl: string;
169
- fetchIdx: number;
170
- tags: string[];
171
- softTags: string[];
172
- isFallback: boolean;
173
- }): Promise<CacheEntry | null>;
174
- set(key: string, data: {
175
- kind: 'APP_PAGE';
176
- status?: number;
177
- headers: {
178
- 'x-nextjs-stale-time': string;
179
- 'x-next-cache-tags': string;
180
- };
181
- html: string;
182
- rscData: Buffer;
183
- segmentData: unknown;
184
- postboned: unknown;
185
- } | {
186
- kind: 'APP_ROUTE';
187
- status: number;
188
- headers: {
189
- 'cache-control'?: string;
190
- 'x-nextjs-stale-time': string;
191
- 'x-next-cache-tags': string;
192
- };
193
- body: Buffer;
194
- } | {
195
- kind: 'FETCH';
196
- data: {
197
- headers: Record<string, string>;
198
- body: string;
199
- status: number;
200
- url: string;
201
- };
202
- revalidate: number | false;
203
- }, ctx: {
228
+ get(key: string, ctx: GetContext): Promise<CacheEntry | null>;
229
+ set(key: string, data: SetCacheValue, ctx: {
204
230
  isRoutePPREnabled: boolean;
205
231
  isFallback: boolean;
206
232
  tags?: string[];
207
233
  revalidate?: number | false;
208
234
  cacheControl?: {
209
- revalidate: 5;
210
- expire: undefined;
235
+ revalidate: number | false;
236
+ expire: number | undefined;
211
237
  };
212
238
  }): Promise<void>;
213
239
  revalidateTag(tagOrTags: string | string[], ...rest: any[]): Promise<void>;
package/dist/index.js CHANGED
@@ -566,6 +566,19 @@ if (process.env.DEBUG_CACHE_HANDLER) {
566
566
  }
567
567
  var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
568
568
  var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
569
+ var SUPPORTED_GET_KINDS = [
570
+ "APP_ROUTE",
571
+ "APP_PAGE",
572
+ "PAGES",
573
+ "FETCH"
574
+ ];
575
+ var SUPPORTED_SET_KINDS = [
576
+ "APP_ROUTE",
577
+ "APP_PAGE",
578
+ "PAGES",
579
+ "REDIRECT",
580
+ "FETCH"
581
+ ];
569
582
  var killContainerOnErrorCount = 0;
570
583
  var RedisStringsHandler = class {
571
584
  constructor({
@@ -731,12 +744,12 @@ var RedisStringsHandler = class {
731
744
  }
732
745
  async get(key, ctx) {
733
746
  try {
734
- if (ctx.kind !== "APP_ROUTE" && ctx.kind !== "APP_PAGE" && ctx.kind !== "FETCH") {
747
+ if (!SUPPORTED_GET_KINDS.includes(ctx.kind)) {
735
748
  console.warn(
736
749
  "RedisStringsHandler.get() called with",
737
750
  key,
738
751
  ctx,
739
- " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
752
+ `this cache handler is only designed and tested for kinds ${SUPPORTED_GET_KINDS.join(", ")} and not for kind`,
740
753
  ctx?.kind
741
754
  );
742
755
  }
@@ -791,7 +804,7 @@ var RedisStringsHandler = class {
791
804
  "cacheEntry is mall formed (missing tags)"
792
805
  );
793
806
  }
794
- if (!cacheEntry?.value) {
807
+ if (cacheEntry?.value === void 0) {
795
808
  console.warn(
796
809
  "RedisStringsHandler.get() called with",
797
810
  key,
@@ -864,21 +877,27 @@ var RedisStringsHandler = class {
864
877
  }
865
878
  async set(key, data, ctx) {
866
879
  try {
867
- if (data.kind !== "APP_ROUTE" && data.kind !== "APP_PAGE" && data.kind !== "FETCH") {
880
+ if (data !== null && !SUPPORTED_SET_KINDS.includes(data.kind)) {
868
881
  console.warn(
869
882
  "RedisStringsHandler.set() called with",
870
883
  key,
871
884
  ctx,
872
885
  data,
873
- " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
886
+ `this cache handler is only designed and tested for kinds ${SUPPORTED_SET_KINDS.join(", ")} and not for kind`,
874
887
  data?.kind
875
888
  );
876
889
  }
877
890
  await this.assertClientIsReady();
878
- if (data.kind === "APP_PAGE" || data.kind === "APP_ROUTE") {
891
+ if (data?.kind === "APP_PAGE" || data?.kind === "APP_ROUTE") {
879
892
  const tags = data.headers["x-next-cache-tags"]?.split(",");
880
893
  ctx.tags = [...ctx.tags || [], ...tags || []];
881
894
  }
895
+ if (data === null || data.kind === "PAGES" || data.kind === "REDIRECT") {
896
+ const implicitTag = NEXT_CACHE_IMPLICIT_TAG_ID + key;
897
+ if (!ctx.tags?.includes(implicitTag)) {
898
+ ctx.tags = [...ctx.tags || [], implicitTag];
899
+ }
900
+ }
882
901
  const cacheEntry = {
883
902
  lastModified: Date.now(),
884
903
  tags: ctx?.tags || [],
@@ -893,7 +912,10 @@ var RedisStringsHandler = class {
893
912
  }
894
913
  const revalidate = (
895
914
  // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used
896
- data.kind === "FETCH" && data.revalidate || ctx.revalidate || ctx.cacheControl?.revalidate || data?.revalidate
915
+ data?.kind === "FETCH" && data.revalidate || ctx.revalidate || ctx.cacheControl?.revalidate || // Legacy fallback: older Next.js versions attached `revalidate` directly
916
+ // to the data payload. FETCH is already handled above, so this only
917
+ // matters for those older, non-discriminated shapes.
918
+ data?.revalidate
897
919
  );
898
920
  const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
899
921
  const setOperation = redisErrorHandler(
@@ -1446,7 +1468,22 @@ function getRedisCacheComponentsHandler(options = {}) {
1446
1468
  }
1447
1469
  return singletonHandler;
1448
1470
  }
1449
- var redisCacheHandler = getRedisCacheComponentsHandler();
1471
+ var resolvedHandler;
1472
+ var redisCacheHandler = new Proxy(
1473
+ {},
1474
+ {
1475
+ get(_target, prop) {
1476
+ if (!resolvedHandler) {
1477
+ resolvedHandler = getRedisCacheComponentsHandler();
1478
+ }
1479
+ const value = resolvedHandler[prop];
1480
+ return typeof value === "function" ? value.bind(resolvedHandler) : value;
1481
+ },
1482
+ has(_target, prop) {
1483
+ return prop in RedisCacheComponentsHandler.prototype;
1484
+ }
1485
+ }
1486
+ );
1450
1487
 
1451
1488
  // src/index.ts
1452
1489
  var index_default = CachedHandler;