Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2a15c5a

Browse files
committed
feat(isr): add allowed query params options #1743
1 parent bca4374 commit 2a15c5a

File tree

5 files changed

+88
-16
lines changed

5 files changed

+88
-16
lines changed

libs/isr/models/src/isr-handler-config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ export interface ISRHandlerConfig {
9797
* ],
9898
*/
9999
variants?: RenderVariant[];
100+
101+
/**
102+
* This array of query params will be allowed to be part of the cache key.
103+
* If not provided, which is null, all query params will be part of the cache key.
104+
* If provided as an empty array, no query params will be part of the cache key.
105+
*/
106+
allowedQueryParams?: string[];
100107
}
101108

102109
export interface ServeFromCacheConfig {

libs/isr/server/src/cache-regeneration.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ export class CacheRegeneration {
3434
): Promise<void> {
3535
const { url } = req;
3636
const variant = getVariant(req, this.isrConfig);
37-
const cacheKey = getCacheKey(url, variant);
37+
const cacheKey = getCacheKey(
38+
url,
39+
this.isrConfig.allowedQueryParams,
40+
variant,
41+
);
3842

3943
if (this.urlsOnHold.includes(cacheKey)) {
4044
logger.log('Another regeneration is on-going for this url...');

libs/isr/server/src/isr-handler.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { renderUrl, RenderUrlConfig } from './utils/render-url';
1919
export class ISRHandler {
2020
protected cache!: CacheHandler;
2121
protected cacheRegeneration!: CacheRegeneration;
22-
protected logger = new ISRLogger(this.config?.enableLogging || false);
22+
protected logger: ISRLogger;
2323

2424
constructor(protected config: ISRHandlerConfig) {
2525
if (!config) {
2626
throw new Error('Provide ISRHandlerConfig!');
2727
}
2828

29+
this.logger = new ISRLogger(this.config?.enableLogging || false);
2930
// if skipCachingOnHttpError is not provided it will default to true
3031
config.skipCachingOnHttpError = config.skipCachingOnHttpError !== false;
3132
// if buildId is not provided it will default to null
@@ -55,7 +56,7 @@ export class ISRHandler {
5556
req: Request,
5657
res: Response,
5758
config?: InvalidateConfig,
58-
): Promise<any> {
59+
): Promise<Response> {
5960
const { token, urlsToInvalidate } = extractDataFromBody(req);
6061
const { indexHtml } = this.config;
6162

@@ -74,7 +75,7 @@ export class ISRHandler {
7475
}
7576

7677
const notInCache: string[] = [];
77-
const urlWithErrors: Record<string, any> = {};
78+
const urlWithErrors: Record<string, string[]> = {};
7879

7980
// Include all possible variants in the list of URLs to be invalidated including
8081
// their modified request to regenerate the pages
@@ -120,7 +121,7 @@ export class ISRHandler {
120121
};
121122
await this.cache.add(cacheKey, html, cacheConfig);
122123
} catch (err) {
123-
urlWithErrors[cacheKey] = err;
124+
urlWithErrors[cacheKey] = err as string[];
124125
}
125126
}
126127

@@ -169,7 +170,7 @@ export class ISRHandler {
169170
for (const variant of variants) {
170171
result.push({
171172
url,
172-
cacheKey: getCacheKey(url, variant),
173+
cacheKey: getCacheKey(url, this.config.allowedQueryParams, variant),
173174
reqSimulator: variant.simulateVariant
174175
? variant.simulateVariant
175176
: defaultVariant,
@@ -185,11 +186,12 @@ export class ISRHandler {
185186
res: Response,
186187
next: NextFunction,
187188
config?: ServeFromCacheConfig,
188-
): Promise<any> {
189+
): Promise<Response | void> {
189190
try {
190191
const variant = this.getVariant(req);
191-
192-
const cacheData = await this.cache.get(getCacheKey(req.url, variant));
192+
const cacheData = await this.cache.get(
193+
getCacheKey(req.url, this.config.allowedQueryParams, variant),
194+
);
193195
const { html, options: cacheConfig, createdAt } = cacheData;
194196

195197
const cacheHasBuildId =
@@ -229,7 +231,7 @@ export class ISRHandler {
229231
// Cache exists. Send it.
230232
this.logger.log(
231233
`Page was retrieved from cache: `,
232-
getCacheKey(req.url, variant),
234+
getCacheKey(req.url, this.config.allowedQueryParams, variant),
233235
);
234236
return res.send(finalHtml);
235237
} catch (error) {
@@ -288,10 +290,14 @@ export class ISRHandler {
288290
const variant = this.getVariant(req);
289291

290292
// Cache the rendered `html` for this request url to use for subsequent requests
291-
await this.cache.add(getCacheKey(req.url, variant), finalHtml, {
292-
revalidate,
293-
buildId: this.config.buildId,
294-
});
293+
await this.cache.add(
294+
getCacheKey(req.url, this.config.allowedQueryParams, variant),
295+
finalHtml,
296+
{
297+
revalidate,
298+
buildId: this.config.buildId,
299+
},
300+
);
295301
return res.send(finalHtml);
296302
});
297303
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { RenderVariant } from '../../../models/src';
2+
import { getCacheKey } from './cache-utils';
3+
4+
describe('getCacheKey', () => {
5+
it('should return the URL without query parameters when none are allowed', () => {
6+
const url = '/page?param1=value1&param2=value2';
7+
const result = getCacheKey(url, [], null);
8+
expect(result).toBe('/page');
9+
});
10+
11+
it('should return the URL with query parameters when it is null or undefined', () => {
12+
const url = '/page?param1=value1&param2=value2';
13+
const result = getCacheKey(url, null, null);
14+
expect(result).toBe('/page?param1=value1&param2=value2');
15+
});
16+
17+
it('should include only allowed query parameters in the result', () => {
18+
const url = '/page?allowed=value&disallowed=value';
19+
const result = getCacheKey(url, ['allowed'], null);
20+
expect(result).toBe('/page?allowed=value');
21+
});
22+
23+
it('should exclude disallowed query parameters', () => {
24+
const url = '/page?allowed=value&disallowed=value';
25+
const result = getCacheKey(url, ['allowed'], null);
26+
expect(result).not.toContain('disallowed=value');
27+
});
28+
29+
it('should append the variant identifier when a variant is provided', () => {
30+
const url = '/page?param=value';
31+
const variant: RenderVariant = {
32+
identifier: 'variant123',
33+
detectVariant: () => true,
34+
};
35+
const result = getCacheKey(url, ['param'], variant);
36+
expect(result).toBe('/page?param=value<variantId:variant123>');
37+
});
38+
});

libs/isr/server/src/utils/cache-utils.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,27 @@ import { Request } from 'express';
33

44
export const getCacheKey = (
55
url: string,
6+
allowedQueryParams: string[] | null | undefined,
67
variant: RenderVariant | null,
78
): string => {
8-
if (!variant) return url;
9-
return `${url}<variantId:${variant.identifier}>`;
9+
let normalizedUrl = url;
10+
if (allowedQueryParams) {
11+
// Normalize the URL by removing disallowed query parameters
12+
// using http://localhost as the base URL to parse the URL
13+
// since the URL constructor requires a base URL to parse relative URLs
14+
// it will not be used in the final cache key
15+
const urlObj = new URL(url, 'http://localhost');
16+
const searchParams = urlObj.searchParams;
17+
const filteredSearchParams = new URLSearchParams();
18+
searchParams.forEach((value, key) => {
19+
if (allowedQueryParams.includes(key)) {
20+
filteredSearchParams.append(key, value);
21+
}
22+
});
23+
normalizedUrl = `${urlObj.pathname}${filteredSearchParams.toString() ? '?' + filteredSearchParams.toString() : ''}`;
24+
}
25+
if (!variant) return normalizedUrl;
26+
return `${normalizedUrl}<variantId:${variant.identifier}>`;
1027
};
1128

1229
export const getVariant = (

0 commit comments

Comments
 (0)