Skip to main content

Caching

This document covers all caching strategies used in the Hydrogen storefront.

Cache Levelsโ€‹

The application defines 6 cache levels in app/lib/cache.ts:

LevelmaxAgestaleWhileRevalidateUse Case
faster1s9sNear real-time content (recommendation trays)
fast1 min4 minFrequently changing content (collections)
medium1 min14 minModerate freshness (PDP content, navigation)
slow5 min55 minPeriodically changing (footer, settings, feature flags)
slower15 min4 hrsLong-lived content (PLPs, Yotpo reviews, rollup maps)
none--No caching (account pages, authenticated data)

All cacheable levels use public cache mode except none which uses no-store.

Storefront API Cachingโ€‹

The Shopify Storefront API client is configured with Cloudflare Worker caching in app/lib/context.ts:

const hydrogenCacheKey = `birdygrey-${DEPLOY_RUN_ID}`;
const cache = await caches.open(hydrogenCacheKey);

Key points:

  • Each deployment gets an isolated cache namespace via DEPLOY_RUN_ID
  • Uses Hydrogen's createWithCache() wrapper for HTTP caching
  • All GraphQL queries go through this cached client by default

Redirect Cachingโ€‹

URL redirects use Hydrogen's CacheLong() strategy, which implements a 1-day cache duration per Hydrogen's defaults:

// app/lib/redirect/helper.server.ts
const redirectResponse = await storefront.query(URL_REDIRECT_QUERY, {
variables,
cache: storefront.CacheLong(),
});

Product Pricing on PDPsโ€‹

Product pricing data on PDPs uses Hydrogen's default cache strategy (CacheShort()) - no explicit cache configuration is applied at the application layer. CacheShort() provides 1 second of freshness with 9 seconds of stale-while-revalidate.

DataCacheFreshStale Revalidate
Product pricing (priceRange, compareAtPriceRange)CacheShort (default)1s9s
Sibling product IDs (COLOR/FABRIC groups)slow5 min55 min

The product query fetches pricing via storefront.query() without an explicit cache option:

// app/lib/api/storefront/common.ts
return storefront.query<T>(query, { variables }); // Uses Hydrogen default

This means:

  • Price changes in Shopify will reflect on PDPs within seconds (1s fresh + 9s stale-while-revalidate)
  • During the stale-while-revalidate window, users may see slightly outdated prices while fresh data is fetched in the background
  • Each deployment invalidates the cache, so prices update immediately after deploy

Route Cachingโ€‹

Routes can append headers either via the headers export, or using the data wrapper in loaders and actions. To set a standardized cache control header using one of our predefined caching strategies, use the generateCacheControlHeader() function.

To preserve the same cache headers when both client side navigating and navigating on a full document load, use the routeHeaders helper export.

import { CacheStrategy, routeHeaders } from '~/lib/cache';
import { generateCacheControlHeader } from '@shopify/hydrogen';
import { data } from 'react-router';

export const headers = routeHeaders;

export const loader = async ({ context }: LoaderFunctionArgs) => {
const headers = new Headers();
headers.append('Cache-Control', generateCacheControlHeader(CacheStrategy.fast));
return data({ ... }, { headers });
};

Route Cache Levelsโ€‹

RouteCache LevelDuration
Collection pagesfast1 min
Product pages(default)Storefront API default
Account pagesnoneNo caching
Predictive searchdynamic60s with term, 1hr without

API Route Cachingโ€‹

Internal API routes use withCache.run() to cache the result of an entire async operation (as opposed to withCache.fetch() which caches a single HTTP response):

// app/routes/api.yotpoReviews.tsx
const reviews = await context.withCache.run(
{
cacheKey: request.url,
cacheStrategy: CacheStrategy.slower,
shouldCacheResult: (result) => Boolean(result),
},
() => getProductReviews(...)
);

API Cache Levelsโ€‹

API RouteCache LevelDuration
/api/yotpoReviewsslower15 min
/api/yotpoQuestionsslower15 min
/api/yotpoMediaReviewsslower15 min
/api/yotpoUgc.$albumslower15 min
/api/predictive-searchdynamic60s-1hr
/api/admin/siblings.$style(none)Cached upstream

External Service Cachingโ€‹

Yotpoโ€‹

Yotpo API calls use withCache.fetch() in app/lib/yotpo/yotpo.ts:

  • Reviews/Questions: slower (15 min)
  • Access Token: slow (5 min)

GrowthBook Feature Flagsโ€‹

Feature flag fetches use slow cache (5 min) in app/lib/growthbook/helper.server.ts:

const features = await withCache.fetch(url, options, {
cacheStrategy: CacheStrategy.slow,
shouldCacheResponse: (body) => body.status === 200,
});

Collection Rollup Mapsโ€‹

Rollup maps fetched from external service use slower cache (15 min) in app/lib/plp/loader.helper.server.ts.

Builder.io CMS Cachingโ€‹

For detailed Builder.io caching documentation, see the CMS Environment & Caching Guide.

Summaryโ€‹

Builder content caching varies by content type:

Content TypeCache LevelFreshStale Revalidate
Recommendation traysfaster1s9s
Homepage, announcements, headerfast/medium1 min4-14 min
PDP content, PLP navigationmedium1 min14 min
Footer, breadcrumbs, settingsslow5 min55 min
PLPs, empty cartslower15 min4 hrs

Cache Bustingโ€‹

Hydrogen wraps all loadSingleContent content retrievals in a withCache.run() call, which wraps the result of the entire retrieval call in Cloudflare cache. To disable Cloudlare caching, flip the h2_enable-builder-cachebust feature gate to true. Note that enabling the cachebust feature gate will cachebust universally across all models.

By default, we will always disable the cache in Builder preview mode.

Static Fallbacksโ€‹

Critical UI components have static JSON fallbacks bundled at build time (app/lib/builder/static.server.ts):

  • Announcements (Core/Suits)
  • Header (Core/Suits)
  • Footer (Core/Suits)
  • Settings

These provide immediate fallback if Builder API is unavailable.

Cache Invalidationโ€‹

Deploy-based Invalidationโ€‹

Each deployment creates a new cache namespace via DEPLOY_RUN_ID, effectively invalidating all cached content:

const hydrogenCacheKey = `birdygrey-${DEPLOY_RUN_ID}`;

Stale-While-Revalidateโ€‹

All cache levels (except none) use stale-while-revalidate, allowing:

  • Immediate response with potentially stale content
  • Background revalidation for fresh content
  • Graceful degradation during origin issues

Quick Referenceโ€‹

ContentCache LevelFreshStale
URL RedirectsCacheLong1 day-
Collection Pagesfast1 min4 min
PDP Contentmedium1 min14 min
PLPsslower15 min4 hrs
Yotpo Reviewsslower15 min4 hrs
Feature Flagsslow5 min55 min
Rec Traysfaster1s9s
Account Pagesnone--