Skip to content

Commit 2526507

Browse files
[DEV-3615] Fix Metadata Cache to Support Locale (#2025)
* Refactor metadata caching to support locale and improve cache management * Refactor metadata caching to use generic types and improve locale support * Add changeset * Fix metadata cache to support locale matching in fetchMetadataWithCache * Fix metadata cache logic to correctly handle locale filtering * Refactor generateMetadata and getGuidePage to improve locale handling * Remove import
1 parent ae2f4bc commit 2526507

File tree

4 files changed

+94
-82
lines changed

4 files changed

+94
-82
lines changed

.changeset/big-bottles-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nextjs-website": patch
3+
---
4+
5+
Refactor metadata caching to use generic types and improve locale support

apps/nextjs-website/src/app/[locale]/[productSlug]/guides/[...productGuidePage]/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ export type ProductGuidePageProps = {
5151
export async function generateMetadata(props: {
5252
params: Promise<Params>;
5353
}): Promise<Metadata> {
54-
const params = await props.params;
54+
const { locale, productGuidePage, productSlug } = await props.params;
5555
const guidePageProps = await getGuidePage(
56-
params?.productGuidePage ?? [''],
57-
params?.locale,
58-
params?.productSlug
56+
productGuidePage ?? [''],
57+
locale,
58+
productSlug
5959
);
6060

6161
if (guidePageProps?.seo) {

apps/nextjs-website/src/helpers/s3Metadata.helpers.ts

Lines changed: 84 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -205,47 +205,72 @@ const S3_SOAP_API_METADATA_JSON_PATH =
205205
process.env.S3_SOAP_API_METADATA_JSON_PATH ||
206206
'soap-api/soap-api-metadata.json';
207207

208-
let guidesMetadataCache: readonly JsonMetadata[] | null = null;
209-
let solutionsMetadataCache: readonly JsonMetadata[] | null = null;
210-
let releaseNotesMetadataCache: readonly JsonMetadata[] | null = null;
211-
let soapApiMetadataCache: readonly SoapApiJsonMetadata[] | null = null;
212-
213-
// Add timestamp-based cache invalidation
214-
// eslint-disable-next-line functional/no-let
215-
let guidesMetadataCacheTime = 0;
216-
217-
// eslint-disable-next-line functional/no-let
218-
let solutionsMetadataCacheTime = 0;
208+
type MetadataCacheItem<T> = {
209+
readonly category: string;
210+
readonly locale: string;
211+
readonly data: readonly T[] | null;
212+
readonly refreshTime: number;
213+
};
219214

220-
// eslint-disable-next-line functional/no-let
221-
let releaseNotesMetadataCacheTime = 0;
215+
let metadataCache: readonly MetadataCacheItem<Record<string, unknown>>[] = [];
222216

223217
const METADATA_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
224218

225-
export const getGuidesMetadata = async (locale: string, dirName?: string) => {
219+
async function fetchMetadataWithCache<T extends { readonly dirName: string }>(
220+
locale: string,
221+
metadataCategory: string,
222+
fetchFunction: () => Promise<readonly T[] | null>,
223+
dirName?: string
224+
): Promise<MetadataCacheItem<T>> {
226225
const now = Date.now();
227-
228-
if (
229-
guidesMetadataCache &&
230-
now - guidesMetadataCacheTime < METADATA_CACHE_TTL &&
231-
(!dirName || guidesMetadataCache.some((m) => m.dirName === dirName))
232-
) {
233-
return guidesMetadataCache;
226+
const cacheResult = metadataCache.find((item) => {
227+
const categoryMatch = item.category === metadataCategory;
228+
const localeMatch = item.locale === locale;
229+
const timeMatch = item.data && now - item.refreshTime < METADATA_CACHE_TTL;
230+
const dirNameMatch =
231+
!dirName ||
232+
(Array.isArray(item.data) &&
233+
item.data.length > 0 &&
234+
'dirName' in item.data[0] &&
235+
item.data.some((m: Record<string, unknown>) => m.dirName === dirName));
236+
return categoryMatch && localeMatch && timeMatch && dirNameMatch;
237+
}) as MetadataCacheItem<T> | undefined;
238+
239+
if (cacheResult) {
240+
return cacheResult;
234241
}
235242

236-
guidesMetadataCache = await fetchMetadataFromCDN<JsonMetadata>(
243+
const fetchMetadataResult = await fetchFunction();
244+
245+
const newCacheItem: MetadataCacheItem<T> = {
246+
category: metadataCategory,
247+
locale,
248+
data: fetchMetadataResult,
249+
refreshTime: now,
250+
};
251+
252+
metadataCache = [
253+
...metadataCache.filter(
254+
(item) => !(item.category === metadataCategory && item.locale === locale)
255+
),
256+
newCacheItem,
257+
];
258+
259+
return newCacheItem;
260+
}
261+
262+
export const getGuidesMetadata = async (locale: string, dirName?: string) => {
263+
const fetchFromCdnPath = dirName
264+
? path.join(locale, S3_PATH_TO_GITBOOK_DOCS, dirName, S3_METADATA_JSON_PATH)
265+
: `${locale}/${S3_GUIDES_METADATA_JSON_PATH}`;
266+
const cacheResult = await fetchMetadataWithCache<JsonMetadata>(
267+
locale,
268+
'guides',
269+
() => fetchMetadataFromCDN<JsonMetadata>(fetchFromCdnPath),
237270
dirName
238-
? path.join(
239-
locale,
240-
S3_PATH_TO_GITBOOK_DOCS,
241-
dirName,
242-
S3_METADATA_JSON_PATH
243-
)
244-
: `${locale}/${S3_GUIDES_METADATA_JSON_PATH}`
245271
);
246-
guidesMetadataCacheTime = now;
247272

248-
return guidesMetadataCache || [];
273+
return cacheResult.data || [];
249274
};
250275

251276
const removeTrailingSlash = (value: string) => value.replace(/\/+$/, '');
@@ -324,29 +349,18 @@ export const getSolutionsMetadata = async (
324349
locale: string,
325350
dirName?: string
326351
) => {
327-
const now = Date.now();
328-
329-
if (
330-
solutionsMetadataCache &&
331-
now - solutionsMetadataCacheTime < METADATA_CACHE_TTL &&
332-
(!dirName || solutionsMetadataCache.some((m) => m.dirName === dirName))
333-
) {
334-
return solutionsMetadataCache;
335-
}
336-
337-
solutionsMetadataCache = await fetchMetadataFromCDN<JsonMetadata>(
352+
const fetchFromCdnPath = dirName
353+
? path.join(locale, S3_PATH_TO_GITBOOK_DOCS, dirName, S3_METADATA_JSON_PATH)
354+
: `${locale}/${S3_SOLUTIONS_METADATA_JSON_PATH}`;
355+
356+
const cacheResult = await fetchMetadataWithCache<JsonMetadata>(
357+
locale,
358+
'solutions',
359+
() => fetchMetadataFromCDN<JsonMetadata>(fetchFromCdnPath),
338360
dirName
339-
? path.join(
340-
locale,
341-
S3_PATH_TO_GITBOOK_DOCS,
342-
dirName,
343-
S3_METADATA_JSON_PATH
344-
)
345-
: `${locale}/${S3_SOLUTIONS_METADATA_JSON_PATH}`
346361
);
347-
solutionsMetadataCacheTime = now;
348362

349-
return solutionsMetadataCache || [];
363+
return cacheResult.data || [];
350364
};
351365

352366
export const getReleaseNotesMetadataByDirNames = async (
@@ -368,36 +382,29 @@ export const getReleaseNotesMetadata = async (
368382
locale: string,
369383
dirName?: string
370384
) => {
371-
const now = Date.now();
372-
373-
if (
374-
releaseNotesMetadataCache &&
375-
now - releaseNotesMetadataCacheTime < METADATA_CACHE_TTL &&
376-
(!dirName || releaseNotesMetadataCache.some((m) => m.dirName === dirName))
377-
) {
378-
return releaseNotesMetadataCache;
379-
}
380-
381-
releaseNotesMetadataCache = await fetchMetadataFromCDN<JsonMetadata>(
385+
const fetchFromCdnPath = dirName
386+
? path.join(locale, S3_PATH_TO_GITBOOK_DOCS, dirName, S3_METADATA_JSON_PATH)
387+
: `${locale}/${S3_RELEASE_NOTES_METADATA_JSON_PATH}`;
388+
389+
const cacheResult = await fetchMetadataWithCache<JsonMetadata>(
390+
locale,
391+
'releaseNotes',
392+
() => fetchMetadataFromCDN<JsonMetadata>(fetchFromCdnPath),
382393
dirName
383-
? path.join(
384-
locale,
385-
S3_PATH_TO_GITBOOK_DOCS,
386-
dirName,
387-
S3_METADATA_JSON_PATH
388-
)
389-
: `${locale}/${S3_RELEASE_NOTES_METADATA_JSON_PATH}`
390394
);
391-
releaseNotesMetadataCacheTime = now;
392395

393-
return releaseNotesMetadataCache || [];
396+
return cacheResult.data || [];
394397
};
395398

396399
export const getSoapApiMetadata = async (locale: string) => {
397-
if (!soapApiMetadataCache) {
398-
soapApiMetadataCache = await fetchMetadataFromCDN<SoapApiJsonMetadata>(
399-
`${locale}/${S3_SOAP_API_METADATA_JSON_PATH}`
400-
);
401-
}
402-
return soapApiMetadataCache || [];
400+
const cacheResult = await fetchMetadataWithCache<SoapApiJsonMetadata>(
401+
locale,
402+
'soapApi',
403+
() =>
404+
fetchMetadataFromCDN<SoapApiJsonMetadata>(
405+
`${locale}/${S3_SOAP_API_METADATA_JSON_PATH}`
406+
)
407+
);
408+
409+
return cacheResult.data || [];
403410
};

apps/nextjs-website/src/lib/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export async function getGuidePage(
6666
locale,
6767
productSlug
6868
),
69-
getGuidesMetadata(locale),
7069
]);
7170

7271
// Path construction
@@ -82,6 +81,7 @@ export async function getGuidePage(
8281
});
8382

8483
const guidesMetadata = await getGuidesMetadata(
84+
locale,
8585
guideToFind ? guideToFind.dirName : ''
8686
);
8787
return manageUndefined(

0 commit comments

Comments
 (0)