@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.
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +9 -1
- package/CHANGELOG.md +28 -0
- package/README.md +13 -2
- package/dist/index.d.mts +71 -45
- package/dist/index.d.ts +71 -45
- package/dist/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -8
- package/dist/index.mjs.map +1 -1
- package/docs/index.html +7 -6
- package/package.json +2 -1
- package/src/CacheComponentsHandler.ts +35 -2
- package/src/RedisStringsHandler.ts +125 -66
- package/test/README.md +21 -6
- package/test/nextjs-test-projects/next-pages-16-2-6/README.md +16 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +18 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +7 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/package.json +26 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +3896 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +24 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +11 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +49 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +20 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +34 -0
- package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +6 -0
- package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +3 -5
- package/test/vitest/integration/pages-router.integration.test.ts +420 -0
- package/test/vitest/unit/index.test.ts +49 -2
- package/test/vitest/unit/pages-router-kinds.test.ts +292 -0
- package/test/vitest/unit/reconnect-socket-already-opened.test.ts +56 -0
|
@@ -530,5 +530,38 @@ export function getRedisCacheComponentsHandler(
|
|
|
530
530
|
return singletonHandler;
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
533
|
+
// Lazily resolve the default Cache Components handler.
|
|
534
|
+
//
|
|
535
|
+
// Constructing a RedisCacheComponentsHandler opens a Redis connection in its
|
|
536
|
+
// constructor. Building the singleton at module-eval time therefore means that simply
|
|
537
|
+
// *importing this package* — e.g. only for `RedisStringsHandler` (the legacy
|
|
538
|
+
// `cacheHandler`), with Cache Components never enabled — eagerly connects to Redis,
|
|
539
|
+
// defaulting to `redis://localhost:6379` when neither `REDIS_URL` nor `REDISHOST` is
|
|
540
|
+
// set. In a deployment whose Redis is not on localhost that yields a non-stop
|
|
541
|
+
// `RedisCacheComponentsHandler client error ECONNREFUSED 127.0.0.1:6379` reconnect
|
|
542
|
+
// loop, and it also makes a consumer's later `getRedisCacheComponentsHandler(options)`
|
|
543
|
+
// a no-op, because the singleton was already built with defaults (see #84).
|
|
544
|
+
//
|
|
545
|
+
// Defer construction to first use via a Proxy: importing the package never connects, a
|
|
546
|
+
// consumer that configures the handler via `getRedisCacheComponentsHandler(options)`
|
|
547
|
+
// before it is first used has that configuration honored, and a consumer that never
|
|
548
|
+
// touches Cache Components never opens a Redis connection at all.
|
|
549
|
+
let resolvedHandler: CacheComponentsHandler | undefined;
|
|
550
|
+
|
|
551
|
+
export const redisCacheHandler: CacheComponentsHandler = new Proxy(
|
|
552
|
+
{} as CacheComponentsHandler,
|
|
553
|
+
{
|
|
554
|
+
get(_target, prop) {
|
|
555
|
+
if (!resolvedHandler) {
|
|
556
|
+
resolvedHandler = getRedisCacheComponentsHandler();
|
|
557
|
+
}
|
|
558
|
+
const value = resolvedHandler[prop as keyof CacheComponentsHandler];
|
|
559
|
+
return typeof value === 'function'
|
|
560
|
+
? (value as (...args: unknown[]) => unknown).bind(resolvedHandler)
|
|
561
|
+
: value;
|
|
562
|
+
},
|
|
563
|
+
has(_target, prop) {
|
|
564
|
+
return prop in RedisCacheComponentsHandler.prototype;
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
);
|
|
@@ -13,6 +13,84 @@ export type CacheEntry = {
|
|
|
13
13
|
tags: string[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/** Discriminated union of the `ctx` argument Next.js passes to
|
|
17
|
+
* {@link RedisStringsHandler.get}. The Pages Router (`PAGES`) has no PPR
|
|
18
|
+
* concept, so `isRoutePPREnabled` is optional for it.
|
|
19
|
+
*
|
|
20
|
+
* Note: `REDIRECT` is intentionally absent. Next.js always calls `get()` with
|
|
21
|
+
* `kind: 'PAGES'` for Pages Router routes, even when the stored value has
|
|
22
|
+
* `kind: 'REDIRECT'`. The handler returns whatever value is cached and Next.js
|
|
23
|
+
* interprets it accordingly. */
|
|
24
|
+
export type GetContext =
|
|
25
|
+
| {
|
|
26
|
+
kind: 'APP_ROUTE' | 'APP_PAGE';
|
|
27
|
+
isRoutePPREnabled: boolean;
|
|
28
|
+
isFallback: boolean;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
kind: 'PAGES';
|
|
32
|
+
isRoutePPREnabled?: boolean;
|
|
33
|
+
isFallback: boolean;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
kind: 'FETCH';
|
|
37
|
+
revalidate: number;
|
|
38
|
+
fetchUrl: string;
|
|
39
|
+
fetchIdx: number;
|
|
40
|
+
tags: string[];
|
|
41
|
+
softTags: string[];
|
|
42
|
+
isFallback: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Discriminated union of cache payloads accepted by
|
|
46
|
+
* {@link RedisStringsHandler.set}. `null` is a valid payload: the Pages Router
|
|
47
|
+
* stores `notFound: true` results as a cache entry with a null value. */
|
|
48
|
+
export type SetCacheValue =
|
|
49
|
+
| {
|
|
50
|
+
kind: 'APP_PAGE';
|
|
51
|
+
status?: number;
|
|
52
|
+
headers: {
|
|
53
|
+
'x-nextjs-stale-time': string; // timestamp in ms
|
|
54
|
+
'x-next-cache-tags': string; // comma separated paths (tags)
|
|
55
|
+
};
|
|
56
|
+
html: string;
|
|
57
|
+
rscData: Buffer;
|
|
58
|
+
segmentData: unknown;
|
|
59
|
+
postboned: unknown;
|
|
60
|
+
}
|
|
61
|
+
| {
|
|
62
|
+
kind: 'APP_ROUTE';
|
|
63
|
+
status: number;
|
|
64
|
+
headers: {
|
|
65
|
+
'cache-control'?: string;
|
|
66
|
+
'x-nextjs-stale-time': string; // timestamp in ms
|
|
67
|
+
'x-next-cache-tags': string; // comma separated paths (tags)
|
|
68
|
+
};
|
|
69
|
+
body: Buffer;
|
|
70
|
+
}
|
|
71
|
+
| {
|
|
72
|
+
kind: 'PAGES';
|
|
73
|
+
html: string;
|
|
74
|
+
pageData: Record<string, unknown>;
|
|
75
|
+
headers?: Record<string, number | string | string[] | undefined>;
|
|
76
|
+
status?: number;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
kind: 'REDIRECT';
|
|
80
|
+
props: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
| {
|
|
83
|
+
kind: 'FETCH';
|
|
84
|
+
data: {
|
|
85
|
+
headers: Record<string, string>;
|
|
86
|
+
body: string; // base64 encoded
|
|
87
|
+
status: number;
|
|
88
|
+
url: string;
|
|
89
|
+
};
|
|
90
|
+
revalidate: number | false;
|
|
91
|
+
}
|
|
92
|
+
| null;
|
|
93
|
+
|
|
16
94
|
export function redisErrorHandler<T extends Promise<unknown>>(
|
|
17
95
|
debugInfo: string,
|
|
18
96
|
redisCommandResult: T,
|
|
@@ -144,6 +222,24 @@ const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_';
|
|
|
144
222
|
// This helps track when specific tags were last invalidated
|
|
145
223
|
const REVALIDATED_TAGS_KEY = '__revalidated_tags__';
|
|
146
224
|
|
|
225
|
+
// Cache kinds this handler is designed and tested for. Kept as the single
|
|
226
|
+
// source of truth for both payload validation and the warning message, so
|
|
227
|
+
// adding a kind is a one-line change here instead of edits scattered across
|
|
228
|
+
// the type union, the guard chain, and the log string.
|
|
229
|
+
const SUPPORTED_GET_KINDS = [
|
|
230
|
+
'APP_ROUTE',
|
|
231
|
+
'APP_PAGE',
|
|
232
|
+
'PAGES',
|
|
233
|
+
'FETCH',
|
|
234
|
+
] as const;
|
|
235
|
+
const SUPPORTED_SET_KINDS = [
|
|
236
|
+
'APP_ROUTE',
|
|
237
|
+
'APP_PAGE',
|
|
238
|
+
'PAGES',
|
|
239
|
+
'REDIRECT',
|
|
240
|
+
'FETCH',
|
|
241
|
+
] as const;
|
|
242
|
+
|
|
147
243
|
let killContainerOnErrorCount: number = 0;
|
|
148
244
|
export default class RedisStringsHandler {
|
|
149
245
|
private client: Client;
|
|
@@ -371,35 +467,14 @@ export default class RedisStringsHandler {
|
|
|
371
467
|
}
|
|
372
468
|
}
|
|
373
469
|
|
|
374
|
-
public async get(
|
|
375
|
-
key: string,
|
|
376
|
-
ctx:
|
|
377
|
-
| {
|
|
378
|
-
kind: 'APP_ROUTE' | 'APP_PAGE';
|
|
379
|
-
isRoutePPREnabled: boolean;
|
|
380
|
-
isFallback: boolean;
|
|
381
|
-
}
|
|
382
|
-
| {
|
|
383
|
-
kind: 'FETCH';
|
|
384
|
-
revalidate: number;
|
|
385
|
-
fetchUrl: string;
|
|
386
|
-
fetchIdx: number;
|
|
387
|
-
tags: string[];
|
|
388
|
-
softTags: string[];
|
|
389
|
-
isFallback: boolean;
|
|
390
|
-
},
|
|
391
|
-
): Promise<CacheEntry | null> {
|
|
470
|
+
public async get(key: string, ctx: GetContext): Promise<CacheEntry | null> {
|
|
392
471
|
try {
|
|
393
|
-
if (
|
|
394
|
-
ctx.kind !== 'APP_ROUTE' &&
|
|
395
|
-
ctx.kind !== 'APP_PAGE' &&
|
|
396
|
-
ctx.kind !== 'FETCH'
|
|
397
|
-
) {
|
|
472
|
+
if (!(SUPPORTED_GET_KINDS as readonly string[]).includes(ctx.kind)) {
|
|
398
473
|
console.warn(
|
|
399
474
|
'RedisStringsHandler.get() called with',
|
|
400
475
|
key,
|
|
401
476
|
ctx,
|
|
402
|
-
|
|
477
|
+
`this cache handler is only designed and tested for kinds ${SUPPORTED_GET_KINDS.join(', ')} and not for kind`,
|
|
403
478
|
(ctx as { kind: string })?.kind,
|
|
404
479
|
);
|
|
405
480
|
}
|
|
@@ -479,7 +554,10 @@ export default class RedisStringsHandler {
|
|
|
479
554
|
'cacheEntry is mall formed (missing tags)',
|
|
480
555
|
);
|
|
481
556
|
}
|
|
482
|
-
|
|
557
|
+
// value === null is a legitimate entry: the Pages Router stores
|
|
558
|
+
// `notFound: true` results as a cache entry with a null value.
|
|
559
|
+
// Only an absent value indicates a malformed entry.
|
|
560
|
+
if (cacheEntry?.value === undefined) {
|
|
483
561
|
console.warn(
|
|
484
562
|
'RedisStringsHandler.get() called with',
|
|
485
563
|
key,
|
|
@@ -589,71 +667,49 @@ export default class RedisStringsHandler {
|
|
|
589
667
|
}
|
|
590
668
|
public async set(
|
|
591
669
|
key: string,
|
|
592
|
-
data:
|
|
593
|
-
| {
|
|
594
|
-
kind: 'APP_PAGE';
|
|
595
|
-
status?: number;
|
|
596
|
-
headers: {
|
|
597
|
-
'x-nextjs-stale-time': string; // timestamp in ms
|
|
598
|
-
'x-next-cache-tags': string; // comma separated paths (tags)
|
|
599
|
-
};
|
|
600
|
-
html: string;
|
|
601
|
-
rscData: Buffer;
|
|
602
|
-
segmentData: unknown;
|
|
603
|
-
postboned: unknown;
|
|
604
|
-
}
|
|
605
|
-
| {
|
|
606
|
-
kind: 'APP_ROUTE';
|
|
607
|
-
status: number;
|
|
608
|
-
headers: {
|
|
609
|
-
'cache-control'?: string;
|
|
610
|
-
'x-nextjs-stale-time': string; // timestamp in ms
|
|
611
|
-
'x-next-cache-tags': string; // comma separated paths (tags)
|
|
612
|
-
};
|
|
613
|
-
body: Buffer;
|
|
614
|
-
}
|
|
615
|
-
| {
|
|
616
|
-
kind: 'FETCH';
|
|
617
|
-
data: {
|
|
618
|
-
headers: Record<string, string>;
|
|
619
|
-
body: string; // base64 encoded
|
|
620
|
-
status: number;
|
|
621
|
-
url: string;
|
|
622
|
-
};
|
|
623
|
-
revalidate: number | false;
|
|
624
|
-
},
|
|
670
|
+
data: SetCacheValue,
|
|
625
671
|
ctx: {
|
|
626
672
|
isRoutePPREnabled: boolean;
|
|
627
673
|
isFallback: boolean;
|
|
628
674
|
tags?: string[];
|
|
629
675
|
// Different versions of Next.js use different arguments for the same functionality
|
|
630
676
|
revalidate?: number | false; // Version 15.0.3
|
|
631
|
-
cacheControl?: { revalidate:
|
|
677
|
+
cacheControl?: { revalidate: number | false; expire: number | undefined }; // Version 15.0.3+
|
|
632
678
|
},
|
|
633
679
|
) {
|
|
634
680
|
try {
|
|
635
681
|
if (
|
|
636
|
-
data
|
|
637
|
-
data.kind
|
|
638
|
-
data.kind !== 'FETCH'
|
|
682
|
+
data !== null &&
|
|
683
|
+
!(SUPPORTED_SET_KINDS as readonly string[]).includes(data.kind)
|
|
639
684
|
) {
|
|
640
685
|
console.warn(
|
|
641
686
|
'RedisStringsHandler.set() called with',
|
|
642
687
|
key,
|
|
643
688
|
ctx,
|
|
644
689
|
data,
|
|
645
|
-
|
|
690
|
+
`this cache handler is only designed and tested for kinds ${SUPPORTED_SET_KINDS.join(', ')} and not for kind`,
|
|
646
691
|
(data as { kind: string })?.kind,
|
|
647
692
|
);
|
|
648
693
|
}
|
|
649
694
|
|
|
650
695
|
await this.assertClientIsReady();
|
|
651
696
|
|
|
652
|
-
if (data
|
|
697
|
+
if (data?.kind === 'APP_PAGE' || data?.kind === 'APP_ROUTE') {
|
|
653
698
|
const tags = data.headers['x-next-cache-tags']?.split(',');
|
|
654
699
|
ctx.tags = [...(ctx.tags || []), ...(tags || [])];
|
|
655
700
|
}
|
|
656
701
|
|
|
702
|
+
// Pages Router entries (PAGES, REDIRECT and null/notFound results) do not
|
|
703
|
+
// carry an x-next-cache-tags header. Attach the implicit path tag
|
|
704
|
+
// (_N_T_/<path>) so that revalidatePath()/revalidateTag('_N_T_/<path>')
|
|
705
|
+
// invalidates them the same way as App Router entries.
|
|
706
|
+
if (data === null || data.kind === 'PAGES' || data.kind === 'REDIRECT') {
|
|
707
|
+
const implicitTag = NEXT_CACHE_IMPLICIT_TAG_ID + key;
|
|
708
|
+
if (!ctx.tags?.includes(implicitTag)) {
|
|
709
|
+
ctx.tags = [...(ctx.tags || []), implicitTag];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
657
713
|
// Constructing and serializing the value for storing it in redis
|
|
658
714
|
const cacheEntry: CacheEntry = {
|
|
659
715
|
lastModified: Date.now(),
|
|
@@ -676,10 +732,13 @@ export default class RedisStringsHandler {
|
|
|
676
732
|
// Constructing the expire time for the cache entry
|
|
677
733
|
const revalidate =
|
|
678
734
|
// 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
|
|
679
|
-
(data
|
|
735
|
+
(data?.kind === 'FETCH' && data.revalidate) ||
|
|
680
736
|
ctx.revalidate ||
|
|
681
737
|
ctx.cacheControl?.revalidate ||
|
|
682
|
-
|
|
738
|
+
// Legacy fallback: older Next.js versions attached `revalidate` directly
|
|
739
|
+
// to the data payload. FETCH is already handled above, so this only
|
|
740
|
+
// matters for those older, non-discriminated shapes.
|
|
741
|
+
(data as { revalidate?: number | false } | null)?.revalidate;
|
|
683
742
|
const expireAt =
|
|
684
743
|
revalidate && Number.isSafeInteger(revalidate) && revalidate > 0
|
|
685
744
|
? this.estimateExpireAge(revalidate)
|
package/test/README.md
CHANGED
|
@@ -28,12 +28,13 @@ test/
|
|
|
28
28
|
|
|
29
29
|
Fast tests with no external dependencies. Mocks are used where needed.
|
|
30
30
|
|
|
31
|
-
| File | What it tests
|
|
32
|
-
| ----------------------------------------- |
|
|
33
|
-
| `serializer.test.ts` | `CacheValueSerializer` interface, JSON round-trips, singleton stability
|
|
34
|
-
| `index.test.ts` | `RedisStringsHandler` constructor options, default behaviors
|
|
35
|
-
| `utils/prefix.test.ts` | `resolveKeyPrefix` logic (BUILD_ID fallback, env var precedence)
|
|
36
|
-
| `reconnect-socket-already-opened.test.ts` | Regression: reconnect logic doesn't call `connect()` when socket is already open
|
|
31
|
+
| File | What it tests |
|
|
32
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `serializer.test.ts` | `CacheValueSerializer` interface, JSON round-trips, singleton stability |
|
|
34
|
+
| `index.test.ts` | `RedisStringsHandler` constructor options, default behaviors |
|
|
35
|
+
| `utils/prefix.test.ts` | `resolveKeyPrefix` logic (BUILD_ID fallback, env var precedence) |
|
|
36
|
+
| `reconnect-socket-already-opened.test.ts` | Regression: reconnect logic doesn't call `connect()` when socket is already open |
|
|
37
|
+
| `pages-router-kinds.test.ts` | Pages Router cache kinds (`PAGES`, `REDIRECT`, `null`/notFound), implicit tags, TTL derivation |
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
pnpm test:unit # single run
|
|
@@ -60,6 +61,16 @@ Full cache lifecycle: static pages, fetch caching, revalidation, tag invalidatio
|
|
|
60
61
|
pnpm test:integration
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
### Pages Router (`pages-router.integration.test.ts`)
|
|
65
|
+
|
|
66
|
+
Pages Router cache lifecycle against the `next-pages-16-2-6` app: `PAGES` entry format, TTL derivation from `getStaticProps` `revalidate`, `fallback: 'blocking'` first hits, `notFound: true` (null cache entries), `redirect:` results, and `revalidate: false` TTL fallback.
|
|
67
|
+
|
|
68
|
+
Starts **two** `next start` instances of the same build sharing one Redis and proves that on-demand revalidation (`res.revalidate(path)`) triggered on instance A is served fresh by instance B (HTML and `/_next/data` pageData JSON) — the multi-instance ISR scenario behind a load balancer.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm test:integration:pages
|
|
72
|
+
```
|
|
73
|
+
|
|
63
74
|
### BUILD_ID Prefix (`build-id-prefix.integration.test.ts`)
|
|
64
75
|
|
|
65
76
|
Verifies that when neither `KEY_PREFIX` nor `VERCEL_URL` is set, the handler falls back to `.next/BUILD_ID` as the Redis key prefix. Runs in its own CI job because it needs a clean environment without those env vars.
|
|
@@ -136,6 +147,7 @@ Minimal Next.js applications used as fixtures. They are not test runners — the
|
|
|
136
147
|
| `next-app-15-4-11` | 15.4.11 | Integration (matrix, default for local), build-id-prefix |
|
|
137
148
|
| `next-app-16-0-11` | 16.0.11 | Integration (matrix) |
|
|
138
149
|
| `next-app-16-2-6` | 16.2.6 | Integration (matrix) |
|
|
150
|
+
| `next-pages-16-2-6` | 16.2.6 | Integration (Pages Router, two-instance revalidation) |
|
|
139
151
|
| `next-app-16-0-11-cache-components` | 16.0.11 | Integration (cache-components matrix), E2E (Playwright matrix) |
|
|
140
152
|
| `next-app-16-2-6-cache-components` | 16.2.6 | Integration (cache-components matrix), E2E (Playwright matrix) |
|
|
141
153
|
| `next-app-customized` | — | Example of custom config (referenced in project README) |
|
|
@@ -149,6 +161,7 @@ The CI workflow (`.github/workflows/ci.yml`) is structured as:
|
|
|
149
161
|
```
|
|
150
162
|
lint-and-unit → Lint + Unit Tests + Coverage
|
|
151
163
|
├── integration → Matrix: 3 Next.js versions (15.4–16.2)
|
|
164
|
+
├── integration-pages → Pages Router (two-instance revalidation)
|
|
152
165
|
├── integration-build-id-prefix → Isolated BUILD_ID prefix test
|
|
153
166
|
├── integration-cache-components → Matrix: 16.0.11 + 16.2.6 cache-components
|
|
154
167
|
└── e2e → Matrix: Playwright against 16.0.11 + 16.2.6
|
|
@@ -160,6 +173,7 @@ lint-and-unit → Lint + Unit Tests + Coverage
|
|
|
160
173
|
| ------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------- |
|
|
161
174
|
| `lint-and-unit` | — | `pnpm lint` + `pnpm test:unit:coverage` |
|
|
162
175
|
| `integration` | `next-app-15-4-11`, `next-app-16-0-11`, `next-app-16-2-6` | `pnpm test:integration` (per matrix entry) |
|
|
176
|
+
| `integration-pages` | `next-pages-16-2-6` | `pnpm test:integration:pages` |
|
|
163
177
|
| `integration-build-id-prefix` | `next-app-15-4-11` | `pnpm test:integration:build-id-prefix` |
|
|
164
178
|
| `integration-cache-components` | `next-app-16-{0-3,2-3}-cache-components` | `pnpm test:integration:cache-components` + Redis kill/reconnect |
|
|
165
179
|
| `e2e` | `next-app-16-{0-3,2-3}-cache-components` | `pnpm test:e2e` (Playwright) |
|
|
@@ -171,6 +185,7 @@ lint-and-unit → Lint + Unit Tests + Coverage
|
|
|
171
185
|
| Variable | Used by | Description |
|
|
172
186
|
| ----------------------- | ------------------------------ | ------------------------------------------------------------------------- |
|
|
173
187
|
| `NEXT_TEST_APP` | Integration | Which test app to use (default: `next-app-15-4-11`) |
|
|
188
|
+
| `NEXT_PAGES_TEST_APP` | Integration (Pages Router) | Which Pages Router test app to use (default: `next-pages-16-2-6`) |
|
|
174
189
|
| `CACHE_COMPONENTS_APP` | Integration (cache-components) | Which cache-components app (default: `next-app-16-2-6-cache-components`) |
|
|
175
190
|
| `PLAYWRIGHT_TEST_APP` | E2E | Which app Playwright starts (default: `next-app-16-2-6-cache-components`) |
|
|
176
191
|
| `PLAYWRIGHT_BASE_URL` | E2E | Override base URL (skips `webServer` auto-start) |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# next-pages-16-2-6
|
|
2
|
+
|
|
3
|
+
Next.js 16.2.6 **Pages Router** fixture for integration testing the Redis
|
|
4
|
+
cache handler with Pages Router cache entry kinds:
|
|
5
|
+
|
|
6
|
+
- `PAGES` — ISR pages via `getStaticProps` + `revalidate` and
|
|
7
|
+
`getStaticPaths` with `fallback: 'blocking'` (`/isr/[slug]`)
|
|
8
|
+
- `REDIRECT` — `getStaticProps` returning `redirect:` (`/isr/redirect`)
|
|
9
|
+
- `null` value entries — `getStaticProps` returning `notFound: true`
|
|
10
|
+
(`/isr/not-found`)
|
|
11
|
+
- `revalidate: false` TTL fallback (`/static-forever`)
|
|
12
|
+
- On-demand revalidation via `res.revalidate(path)`
|
|
13
|
+
(`/api/revalidate?path=...`), used by the two-instance cross-server
|
|
14
|
+
revalidation test
|
|
15
|
+
|
|
16
|
+
Used by `test/vitest/integration/pages-router.integration.test.ts`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-pages-16-2-6",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"next": "16.2.6",
|
|
13
|
+
"react": "19.2.0",
|
|
14
|
+
"react-dom": "19.2.0",
|
|
15
|
+
"redis": "4.7.0",
|
|
16
|
+
"@trieb.work/nextjs-turbo-redis-cache": "file:../../../"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20",
|
|
20
|
+
"@types/react": "^19",
|
|
21
|
+
"@types/react-dom": "^19",
|
|
22
|
+
"eslint": "^9",
|
|
23
|
+
"eslint-config-next": "16.2.6",
|
|
24
|
+
"typescript": "^5"
|
|
25
|
+
}
|
|
26
|
+
}
|