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:
| Level | maxAge | staleWhileRevalidate | Use Case |
|---|---|---|---|
| faster | 1s | 9s | Near real-time content (recommendation trays) |
| fast | 1 min | 4 min | Frequently changing content (collections) |
| medium | 1 min | 14 min | Moderate freshness (PDP content, navigation) |
| slow | 5 min | 55 min | Periodically changing (footer, settings, feature flags) |
| slower | 15 min | 4 hrs | Long-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.
| Data | Cache | Fresh | Stale Revalidate |
|---|---|---|---|
| Product pricing (priceRange, compareAtPriceRange) | CacheShort (default) | 1s | 9s |
| Sibling product IDs (COLOR/FABRIC groups) | slow | 5 min | 55 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โ
| Route | Cache Level | Duration |
|---|---|---|
| Collection pages | fast | 1 min |
| Product pages | (default) | Storefront API default |
| Account pages | none | No caching |
| Predictive search | dynamic | 60s 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 Route | Cache Level | Duration |
|---|---|---|
/api/yotpoReviews | slower | 15 min |
/api/yotpoQuestions | slower | 15 min |
/api/yotpoMediaReviews | slower | 15 min |
/api/yotpoUgc.$album | slower | 15 min |
/api/predictive-search | dynamic | 60s-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 Type | Cache Level | Fresh | Stale Revalidate |
|---|---|---|---|
| Recommendation trays | faster | 1s | 9s |
| Homepage, announcements, header | fast/medium | 1 min | 4-14 min |
| PDP content, PLP navigation | medium | 1 min | 14 min |
| Footer, breadcrumbs, settings | slow | 5 min | 55 min |
| PLPs, empty cart | slower | 15 min | 4 hrs |
Cache Bustingโ
Builder cache can be disabled via BUILDER_CACHEBUST environment variable:
'true'or'all': Disable cache for all models- Comma-separated list (e.g.,
'page,pdp,plp'): Disable specific models - Builder preview mode always disables cache
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โ
| Content | Cache Level | Fresh | Stale |
|---|---|---|---|
| URL Redirects | CacheLong | 1 day | - |
| Collection Pages | fast | 1 min | 4 min |
| PDP Content | medium | 1 min | 14 min |
| PLPs | slower | 15 min | 4 hrs |
| Yotpo Reviews | slower | 15 min | 4 hrs |
| Feature Flags | slow | 5 min | 55 min |
| Rec Trays | faster | 1s | 9s |
| Account Pages | none | - | - |