Skip to content

Commit e5f0176

Browse files
jorgemoyaclaude
andcommitted
feat(core): wrap product page guest queries in unstable_cache
Wrap product details, pricing, inventory, variant inventory, related products, inventory settings, and reviews queries in unstable_cache for guest visitors. Authenticated requests bypass the cache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bad2bf4 commit e5f0176

4 files changed

Lines changed: 213 additions & 51 deletions

File tree

.changeset/guest-cache-product.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bigcommerce/catalyst-core": minor
3+
---
4+
5+
Wrap product page guest queries in `unstable_cache` with configurable revalidation. Includes product details, pricing, inventory, reviews, and related products. Authenticated requests continue to bypass the cache.

core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';
2+
import { unstable_cache } from 'next/cache';
23
import { getFormatter, getTranslations } from 'next-intl/server';
34
import { createLoader, parseAsString, SearchParams } from 'nuqs/server';
45
import { cache } from 'react';
@@ -64,14 +65,22 @@ const ReviewsQuery = graphql(
6465
[ProductReviewSchemaFragment, PaginationFragment],
6566
);
6667

67-
const getReviews = cache(async (productId: number, paginationArgs: object) => {
68-
const { data } = await client.fetch({
69-
document: ReviewsQuery,
70-
variables: { ...paginationArgs, entityId: productId },
71-
fetchOptions: { next: { revalidate } },
72-
});
68+
const getCachedReviews = unstable_cache(
69+
async (productId: number, paginationArgs: object) => {
70+
const { data } = await client.fetch({
71+
document: ReviewsQuery,
72+
variables: { ...paginationArgs, entityId: productId },
73+
fetchOptions: { cache: 'no-store' },
74+
});
7375

74-
return data.site.product;
76+
return data.site.product;
77+
},
78+
['product-reviews'],
79+
{ revalidate },
80+
);
81+
82+
const getReviews = cache(async (productId: number, paginationArgs: object) => {
83+
return getCachedReviews(productId, paginationArgs);
7584
});
7685

7786
interface Props {

core/app/[locale]/(default)/product/[slug]/page-data.ts

Lines changed: 181 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { unstable_cache } from 'next/cache';
12
import { cache } from 'react';
23

34
import { client } from '~/client';
@@ -154,17 +155,37 @@ const ProductPageMetadataQuery = graphql(`
154155
}
155156
`);
156157

157-
export const getProductPageMetadata = cache(
158-
async (entityId: number, customerAccessToken?: string) => {
158+
const getCachedProductPageMetadata = unstable_cache(
159+
async (locale: string, entityId: number) => {
159160
const { data } = await client.fetch({
160161
document: ProductPageMetadataQuery,
161162
variables: { entityId },
162-
customerAccessToken,
163-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
163+
locale,
164+
fetchOptions: { cache: 'no-store' },
164165
});
165166

166167
return data.site.product;
167168
},
169+
['product-page-metadata'],
170+
{ revalidate },
171+
);
172+
173+
export const getProductPageMetadata = cache(
174+
async (locale: string, entityId: number, customerAccessToken?: string) => {
175+
if (customerAccessToken) {
176+
const { data } = await client.fetch({
177+
document: ProductPageMetadataQuery,
178+
variables: { entityId },
179+
customerAccessToken,
180+
locale,
181+
fetchOptions: { cache: 'no-store' },
182+
});
183+
184+
return data.site.product;
185+
}
186+
187+
return getCachedProductPageMetadata(locale, entityId);
188+
},
168189
);
169190

170191
const ProductQuery = graphql(
@@ -200,16 +221,38 @@ const ProductQuery = graphql(
200221
[ProductOptionsFragment],
201222
);
202223

203-
export const getProduct = cache(async (entityId: number, customerAccessToken?: string) => {
204-
const { data } = await client.fetch({
205-
document: ProductQuery,
206-
variables: { entityId },
207-
customerAccessToken,
208-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
209-
});
224+
const getCachedProduct = unstable_cache(
225+
async (locale: string, entityId: number) => {
226+
const { data } = await client.fetch({
227+
document: ProductQuery,
228+
variables: { entityId },
229+
locale,
230+
fetchOptions: { cache: 'no-store' },
231+
});
210232

211-
return data.site;
212-
});
233+
return data.site;
234+
},
235+
['product-data'],
236+
{ revalidate },
237+
);
238+
239+
export const getProduct = cache(
240+
async (locale: string, entityId: number, customerAccessToken?: string) => {
241+
if (customerAccessToken) {
242+
const { data } = await client.fetch({
243+
document: ProductQuery,
244+
variables: { entityId },
245+
customerAccessToken,
246+
locale,
247+
fetchOptions: { cache: 'no-store' },
248+
});
249+
250+
return data.site;
251+
}
252+
253+
return getCachedProduct(locale, entityId);
254+
},
255+
);
213256

214257
const StreamableProductVariantInventoryBySkuQuery = graphql(`
215258
query ProductVariantBySkuQuery($productId: Int!, $sku: String!) {
@@ -249,17 +292,37 @@ const StreamableProductVariantInventoryBySkuQuery = graphql(`
249292

250293
type VariantInventoryVariables = VariablesOf<typeof StreamableProductVariantInventoryBySkuQuery>;
251294

252-
export const getStreamableProductVariantInventory = cache(
253-
async (variables: VariantInventoryVariables, customerAccessToken?: string) => {
295+
const getCachedStreamableProductVariantInventory = unstable_cache(
296+
async (locale: string, variables: VariantInventoryVariables) => {
254297
const { data } = await client.fetch({
255298
document: StreamableProductVariantInventoryBySkuQuery,
256299
variables,
257-
customerAccessToken,
258-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 60 } },
300+
locale,
301+
fetchOptions: { cache: 'no-store' },
259302
});
260303

261304
return data.site.product?.variants;
262305
},
306+
['product-variant-inventory'],
307+
{ revalidate: 60 },
308+
);
309+
310+
export const getStreamableProductVariantInventory = cache(
311+
async (locale: string, variables: VariantInventoryVariables, customerAccessToken?: string) => {
312+
if (customerAccessToken) {
313+
const { data } = await client.fetch({
314+
document: StreamableProductVariantInventoryBySkuQuery,
315+
variables,
316+
customerAccessToken,
317+
locale,
318+
fetchOptions: { cache: 'no-store' },
319+
});
320+
321+
return data.site.product?.variants;
322+
}
323+
324+
return getCachedStreamableProductVariantInventory(locale, variables);
325+
},
263326
);
264327

265328
const StreamableProductQuery = graphql(
@@ -322,17 +385,37 @@ const StreamableProductQuery = graphql(
322385

323386
type Variables = VariablesOf<typeof StreamableProductQuery>;
324387

325-
export const getStreamableProduct = cache(
326-
async (variables: Variables, customerAccessToken?: string) => {
388+
const getCachedStreamableProduct = unstable_cache(
389+
async (locale: string, variables: Variables) => {
327390
const { data } = await client.fetch({
328391
document: StreamableProductQuery,
329392
variables,
330-
customerAccessToken,
331-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
393+
locale,
394+
fetchOptions: { cache: 'no-store' },
332395
});
333396

334397
return data.site.product;
335398
},
399+
['streamable-product'],
400+
{ revalidate },
401+
);
402+
403+
export const getStreamableProduct = cache(
404+
async (locale: string, variables: Variables, customerAccessToken?: string) => {
405+
if (customerAccessToken) {
406+
const { data } = await client.fetch({
407+
document: StreamableProductQuery,
408+
variables,
409+
customerAccessToken,
410+
locale,
411+
fetchOptions: { cache: 'no-store' },
412+
});
413+
414+
return data.site.product;
415+
}
416+
417+
return getCachedStreamableProduct(locale, variables);
418+
},
336419
);
337420

338421
const StreamableProductInventoryQuery = graphql(
@@ -365,17 +448,37 @@ const StreamableProductInventoryQuery = graphql(
365448

366449
type ProductInventoryVariables = VariablesOf<typeof StreamableProductQuery>;
367450

368-
export const getStreamableProductInventory = cache(
369-
async (variables: ProductInventoryVariables, customerAccessToken?: string) => {
451+
const getCachedStreamableProductInventory = unstable_cache(
452+
async (locale: string, variables: ProductInventoryVariables) => {
370453
const { data } = await client.fetch({
371454
document: StreamableProductInventoryQuery,
372455
variables,
373-
customerAccessToken,
374-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 60 } },
456+
locale,
457+
fetchOptions: { cache: 'no-store' },
375458
});
376459

377460
return data.site.product;
378461
},
462+
['streamable-product-inventory'],
463+
{ revalidate: 60 },
464+
);
465+
466+
export const getStreamableProductInventory = cache(
467+
async (locale: string, variables: ProductInventoryVariables, customerAccessToken?: string) => {
468+
if (customerAccessToken) {
469+
const { data } = await client.fetch({
470+
document: StreamableProductInventoryQuery,
471+
variables,
472+
customerAccessToken,
473+
locale,
474+
fetchOptions: { cache: 'no-store' },
475+
});
476+
477+
return data.site.product;
478+
}
479+
480+
return getCachedStreamableProductInventory(locale, variables);
481+
},
379482
);
380483

381484
// Fields that require currencyCode as a query variable
@@ -409,17 +512,37 @@ const ProductPricingAndRelatedProductsQuery = graphql(
409512
[PricingFragment, FeaturedProductsCarouselFragment],
410513
);
411514

412-
export const getProductPricingAndRelatedProducts = cache(
413-
async (variables: Variables, customerAccessToken?: string) => {
515+
const getCachedProductPricingAndRelatedProducts = unstable_cache(
516+
async (locale: string, variables: Variables) => {
414517
const { data } = await client.fetch({
415518
document: ProductPricingAndRelatedProductsQuery,
416519
variables,
417-
customerAccessToken,
418-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
520+
locale,
521+
fetchOptions: { cache: 'no-store' },
419522
});
420523

421524
return data.site.product;
422525
},
526+
['product-pricing-related'],
527+
{ revalidate },
528+
);
529+
530+
export const getProductPricingAndRelatedProducts = cache(
531+
async (locale: string, variables: Variables, customerAccessToken?: string) => {
532+
if (customerAccessToken) {
533+
const { data } = await client.fetch({
534+
document: ProductPricingAndRelatedProductsQuery,
535+
variables,
536+
customerAccessToken,
537+
locale,
538+
fetchOptions: { cache: 'no-store' },
539+
});
540+
541+
return data.site.product;
542+
}
543+
544+
return getCachedProductPricingAndRelatedProducts(locale, variables);
545+
},
423546
);
424547

425548
const InventorySettingsQuery = graphql(`
@@ -440,12 +563,33 @@ const InventorySettingsQuery = graphql(`
440563
}
441564
`);
442565

443-
export const getStreamableInventorySettingsQuery = cache(async (customerAccessToken?: string) => {
444-
const { data } = await client.fetch({
445-
document: InventorySettingsQuery,
446-
customerAccessToken,
447-
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
448-
});
566+
const getCachedInventorySettings = unstable_cache(
567+
async (locale: string) => {
568+
const { data } = await client.fetch({
569+
document: InventorySettingsQuery,
570+
locale,
571+
fetchOptions: { cache: 'no-store' },
572+
});
449573

450-
return data.site.settings?.inventory;
451-
});
574+
return data.site.settings?.inventory;
575+
},
576+
['inventory-settings'],
577+
{ revalidate },
578+
);
579+
580+
export const getStreamableInventorySettingsQuery = cache(
581+
async (locale: string, customerAccessToken?: string) => {
582+
if (customerAccessToken) {
583+
const { data } = await client.fetch({
584+
document: InventorySettingsQuery,
585+
customerAccessToken,
586+
locale,
587+
fetchOptions: { cache: 'no-store' },
588+
});
589+
590+
return data.site.settings?.inventory;
591+
}
592+
593+
return getCachedInventorySettings(locale);
594+
},
595+
);

0 commit comments

Comments
 (0)