Skip to content

Commit 0a1dccb

Browse files
authored
Merge pull request #17 from trieb-work/feat-implement-nextjs-15-3-2
Fix: implement fix for nextjs 15.3.2 (change of revalidate argument of cachehandler set function)
2 parents 53b2c36 + 225fc49 commit 0a1dccb

File tree

75 files changed

+11470
-70
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+11470
-70
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ permissions:
1616
statuses: write
1717
issues: write
1818
actions: write
19+
discussions: write
1920

2021
jobs:
2122
build:
@@ -58,10 +59,10 @@ jobs:
5859
run: redis-cli config set notify-keyspace-events Exe
5960

6061
- name: Install Integration Test Project
61-
run: cd test/integration/next-app && pnpm install
62+
run: cd test/integration/next-app-15-3-2 && pnpm install
6263

6364
- name: Build Integration Test Project
64-
run: cd test/integration/next-app && pnpm build
65+
run: cd test/integration/next-app-15-3-2 && pnpm build
6566

6667
- name: Run tests
6768
run: pnpm test

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ permissions:
1414
statuses: write
1515
issues: write
1616
actions: write
17+
discussions: write
1718

1819
jobs:
1920
release:

README.md

Lines changed: 7 additions & 2 deletions

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
},
88
"scripts": {
99
"dev": "pnpm test",
10-
"run-dev-server": "cd ./test/integration/next-app && pnpm dev",
10+
"run-dev-server": "pnpm run-dev-server-15-3-2",
11+
"run-dev-server-15-3-2": "cd ./test/integration/next-app-15-3-2 && pnpm dev",
12+
"run-dev-server-15-0-3": "cd ./test/integration/next-app-15-0-3 && pnpm dev",
1113
"build": "tsup",
1214
"lint": "eslint -c eslint.config.mjs --fix",
1315
"fmt": "prettier --write 'src/**/*.ts' 'src/*.ts'",

src/RedisStringsHandler.ts

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export default class RedisStringsHandler {
195195
this.redisDeduplicationHandler.deduplicatedFunction;
196196
}
197197

198-
resetRequestCache(): void { }
198+
resetRequestCache(): void {}
199199

200200
private async assertClientIsReady(): Promise<void> {
201201
await Promise.all([
@@ -211,19 +211,19 @@ export default class RedisStringsHandler {
211211
key: string,
212212
ctx:
213213
| {
214-
kind: 'APP_ROUTE' | 'APP_PAGE';
215-
isRoutePPREnabled: boolean;
216-
isFallback: boolean;
217-
}
214+
kind: 'APP_ROUTE' | 'APP_PAGE';
215+
isRoutePPREnabled: boolean;
216+
isFallback: boolean;
217+
}
218218
| {
219-
kind: 'FETCH';
220-
revalidate: number;
221-
fetchUrl: string;
222-
fetchIdx: number;
223-
tags: string[];
224-
softTags: string[];
225-
isFallback: boolean;
226-
},
219+
kind: 'FETCH';
220+
revalidate: number;
221+
fetchUrl: string;
222+
fetchIdx: number;
223+
tags: string[];
224+
softTags: string[];
225+
isFallback: boolean;
226+
},
227227
): Promise<CacheEntry | null> {
228228
if (
229229
ctx.kind !== 'APP_ROUTE' &&
@@ -329,15 +329,12 @@ export default class RedisStringsHandler {
329329
this.client
330330
.unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey)
331331
.catch((err) => {
332-
// If the first unlink fails, log the error and try one more time
332+
// If the first unlink fails, only log the error
333+
// Never implement a retry here as the cache entry will be updated directly after this get request
333334
console.error(
334-
'Error occurred while unlinking stale data. Retrying now. Error was:',
335+
'Error occurred while unlinking stale data. Error was:',
335336
err,
336337
);
337-
this.client.unlink(
338-
getTimeoutRedisCommandOptions(this.timeoutMs),
339-
redisKey,
340-
);
341338
})
342339
.finally(async () => {
343340
// Clean up our tag tracking maps after the Redis key is removed
@@ -366,42 +363,44 @@ export default class RedisStringsHandler {
366363
key: string,
367364
data:
368365
| {
369-
kind: 'APP_PAGE';
370-
status: number;
371-
headers: {
372-
'x-nextjs-stale-time': string; // timestamp in ms
373-
'x-next-cache-tags': string; // comma separated paths (tags)
374-
};
375-
html: string;
376-
rscData: Buffer;
377-
segmentData: unknown;
378-
postboned: unknown;
379-
}
380-
| {
381-
kind: 'APP_ROUTE';
382-
status: number;
383-
headers: {
384-
'cache-control'?: string;
385-
'x-nextjs-stale-time': string; // timestamp in ms
386-
'x-next-cache-tags': string; // comma separated paths (tags)
387-
};
388-
body: Buffer;
389-
}
366+
kind: 'APP_PAGE';
367+
status: number;
368+
headers: {
369+
'x-nextjs-stale-time': string; // timestamp in ms
370+
'x-next-cache-tags': string; // comma separated paths (tags)
371+
};
372+
html: string;
373+
rscData: Buffer;
374+
segmentData: unknown;
375+
postboned: unknown;
376+
}
390377
| {
391-
kind: 'FETCH';
392-
data: {
393-
headers: Record<string, string>;
394-
body: string; // base64 encoded
378+
kind: 'APP_ROUTE';
395379
status: number;
396-
url: string;
397-
};
398-
revalidate: number | false;
399-
},
380+
headers: {
381+
'cache-control'?: string;
382+
'x-nextjs-stale-time': string; // timestamp in ms
383+
'x-next-cache-tags': string; // comma separated paths (tags)
384+
};
385+
body: Buffer;
386+
}
387+
| {
388+
kind: 'FETCH';
389+
data: {
390+
headers: Record<string, string>;
391+
body: string; // base64 encoded
392+
status: number;
393+
url: string;
394+
};
395+
revalidate: number | false;
396+
},
400397
ctx: {
401-
revalidate: number | false;
402398
isRoutePPREnabled: boolean;
403399
isFallback: boolean;
404400
tags?: string[];
401+
// Different versions of Next.js use different arguments for the same functionality
402+
revalidate?: number | false; // Version 15.0.3
403+
cacheControl?: { revalidate: 5; expire: undefined }; // Version 15.0.3
405404
},
406405
) {
407406
if (
@@ -443,12 +442,12 @@ export default class RedisStringsHandler {
443442
);
444443
}
445444

445+
// TODO: implement expiration based on cacheControl.expire argument, -> probably relevant for cacheLife and "use cache" etc.: https://nextjs.org/docs/app/api-reference/functions/cacheLife
446446
// Constructing the expire time for the cache entry
447+
const revalidate = ctx.revalidate || ctx.cacheControl?.revalidate;
447448
const expireAt =
448-
ctx.revalidate &&
449-
Number.isSafeInteger(ctx.revalidate) &&
450-
ctx.revalidate > 0
451-
? this.estimateExpireAge(ctx.revalidate)
449+
revalidate && Number.isSafeInteger(revalidate) && revalidate > 0
450+
? this.estimateExpireAge(revalidate)
452451
: this.estimateExpireAge(this.defaultStaleAge);
453452

454453
// Setting the cache entry in redis
@@ -521,7 +520,7 @@ export default class RedisStringsHandler {
521520
// revalidateTag is called for the page tag (_N_T_...) and the fetch request needs to be invalidated as well
522521
// unfortunately this is not possible since the revalidateTag is not called with any data that would allow us to find the cache entry of the fetch request
523522
// in case of a fetch request get method call, the get method of the cache handler is called with some information about the pages/routes the fetch request is inside
524-
// therefore we only mark the page/route as stale here (with help of the revalidatedTagsMap)
523+
// therefore we only mark the page/route as stale here (with help of the revalidatedTagsMap)
525524
// and delete the cache entry of the fetch request on the next request to the get function
526525
if (tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID)) {
527526
const now = Date.now();

src/ZodHandler.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* This handler is used to validate the arguments of the get, set, revalidateTag methods against a zod schema.
3+
* This is mainly used to test new nextjs versions and to ensure that the arguments are still valid. As we
4+
* saw in the past, that arguments against the cache handler changed between different nextjs versions very often (even between patch versions).
5+
*/
6+
7+
import RedisStringsHandler, {
8+
CreateRedisStringsHandlerOptions,
9+
} from './RedisStringsHandler';
10+
import { debugVerbose } from './utils/debug';
11+
12+
let cachedHandler: RedisStringsHandler;
13+
14+
export default class CachedHandler {
15+
constructor(options: CreateRedisStringsHandlerOptions) {
16+
if (!cachedHandler) {
17+
console.log('created development cached handler');
18+
cachedHandler = new RedisStringsHandler(options);
19+
}
20+
}
21+
get(
22+
...args: Parameters<RedisStringsHandler['get']>
23+
): ReturnType<RedisStringsHandler['get']> {
24+
debugVerbose('CachedHandler.get called with', args);
25+
return cachedHandler.get(...args);
26+
}
27+
set(
28+
...args: Parameters<RedisStringsHandler['set']>
29+
): ReturnType<RedisStringsHandler['set']> {
30+
debugVerbose('CachedHandler.set called with', args);
31+
return cachedHandler.set(...args);
32+
}
33+
revalidateTag(
34+
...args: Parameters<RedisStringsHandler['revalidateTag']>
35+
): ReturnType<RedisStringsHandler['revalidateTag']> {
36+
debugVerbose('CachedHandler.revalidateTag called with', args);
37+
return cachedHandler.revalidateTag(...args);
38+
}
39+
resetRequestCache(
40+
...args: Parameters<RedisStringsHandler['resetRequestCache']>
41+
): ReturnType<RedisStringsHandler['resetRequestCache']> {
42+
// debug("CachedHandler.resetRequestCache called with", args);
43+
return cachedHandler.resetRequestCache(...args);
44+
}
45+
}
File renamed without changes.

0 commit comments

Comments
 (0)