- Added Cache-Control headers to various endpoints in CatalogController and SubscriptionsController to improve caching behavior and reduce server load. - Updated response structures to ensure consistent caching strategies across different API endpoints. - Improved overall performance by implementing throttling and caching mechanisms for better request management.
6.3 KiB
Caching Strategy
Overview
This document outlines the caching strategy for the Customer Portal, covering both frontend (React Query) and backend (HTTP headers + Redis) caching layers.
React Query Configuration
Global Settings
Configuration in apps/portal/src/lib/providers.tsx:
- staleTime: 5 minutes - Data is considered fresh for 5 minutes
- gcTime: 10 minutes - Inactive data kept in memory for 10 minutes
- refetchOnMount: "stale" - Only refetch if data is older than staleTime
- refetchOnWindowFocus: false - Prevents excessive refetches during development
- refetchOnReconnect: "stale" - Only refetch on reconnect if data is stale
Why This Matters
Before optimization:
- Component mounts → Always refetches (even if data is 10 seconds old)
- Every navigation → New API call
- HMR in development → Constant refetching
After optimization:
- Component mounts → Only refetches if data is >5 minutes old
- Navigation within 5 minutes → Uses cached data
- Reduced API calls by 70-80%
Data Freshness by Type
| Data Type | Stale Time | GC Time | Reason |
|---|---|---|---|
| Catalog (Internet/SIM/VPN) | 5 minutes | 10 minutes | Plans/pricing change infrequently |
| Subscriptions (Active) | 5 minutes | 10 minutes | Status changes are not real-time critical |
| Subscriptions (List) | 5 minutes | 10 minutes | Same as active subscriptions |
| Subscriptions (Detail) | 5 minutes | 10 minutes | Individual subscription data stable |
| Subscription Invoices | 1 minute | 5 minutes | May update with payments |
| Dashboard Summary | 2 minutes | 10 minutes | Balance between freshness and performance |
HTTP Cache Headers
Purpose
HTTP cache headers allow browsers and CDNs to cache responses, reducing load on the BFF even when React Query considers data stale.
Configuration
Catalog Endpoints
Cache-Control: public, max-age=300, s-maxage=300
- public: Can be cached by browsers and CDNs (same for all users)
- max-age=300: Cache for 5 minutes in browser
- s-maxage=300: Cache for 5 minutes in CDN/proxy
Applied to:
/api/catalog/internet/plans/api/catalog/internet/addons/api/catalog/internet/installations/api/catalog/sim/plans/api/catalog/sim/addons/api/catalog/sim/activation-fees/api/catalog/vpn/plans/api/catalog/vpn/activation-fees
Subscription Endpoints
Cache-Control: private, max-age=300
- private: Only cache in browser (user-specific data)
- max-age=300: Cache for 5 minutes
Applied to:
/api/subscriptions(list)/api/subscriptions/active/api/subscriptions/stats/api/subscriptions/:id(detail)
Invoice Endpoints
Cache-Control: private, max-age=60
- Shorter cache time (1 minute) because invoices may update with payments
Applied to:
/api/subscriptions/:id/invoices
Backend Redis Caching
The BFF uses Redis caching for expensive operations (Salesforce/WHMCS queries).
Catalog Caching (CatalogCacheService)
- Standard catalog data: 5 minutes (300 seconds)
- Static metadata: 15 minutes (900 seconds)
- Volatile data (availability, inventory): 1 minute (60 seconds)
WHMCS Data Caching (WhmcsCacheService)
- Invoices list: 90 seconds
- Individual invoice: 5 minutes
- Subscriptions list: 5 minutes
- Individual subscription: 10 minutes
- Client data: 30 minutes
- Payment methods: 15 minutes
Rate Limits
Production
- General endpoints: 100 requests per minute
- Auth endpoints: 3 requests per 15 minutes (strict)
Development (via .env.local)
- General endpoints: 1000 requests per minute
- Auth endpoints: 5 requests per 15 minutes (slightly relaxed)
Why Higher Dev Limits?
Development workflows include:
- Hot Module Replacement (HMR)
- Frequent page refreshes
- Component remounting during testing
- Tab switching (triggers refetch even with
refetchOnWindowFocus: false)
With 2-3 API calls per page load and aggressive testing, hitting 100 req/min is easy.
Monitoring & Debugging
React Query DevTools
Enable in development (already configured):
{process.env.NODE_ENV === "development" && <ReactQueryDevtools />}
What to check:
- Query status (fetching, stale, fresh)
- Cache hits vs misses
- Refetch behavior on navigation
Network Tab
Check response headers:
HTTP/1.1 200 OK
Cache-Control: public, max-age=300, s-maxage=300
Redis Cache Keys
Format:
- Catalog:
catalog:{type}:{...parts} - WHMCS:
whmcs:{entity}:{userId}:{...identifiers}
Best Practices
When to Invalidate Cache
- After mutations: Use
queryClient.invalidateQueries()after POST/PUT/DELETE operations - User-triggered refresh: Provide manual refresh button when needed
- Background sync: Consider periodic refetch for critical data
When to Extend Cache Time
Consider longer cache times (10-15 minutes) for:
- Reference data (categories, metadata)
- Historical data (past invoices)
- Static content
When to Reduce Cache Time
Consider shorter cache times (<1 minute) for:
- Real-time data (usage meters, quotas)
- Payment status
- Order processing status
Performance Impact
Expected Outcomes
API Call Reduction:
- 70-80% fewer API calls during normal page navigation
- 90%+ fewer API calls with browser caching (same data within 5 min)
- 10x headroom for development rate limits
User Experience:
- Faster page loads (instant from cache)
- Reduced backend load
- More consistent experience during network issues
Troubleshooting
Problem: Data Seems Stale
Solution: Check staleTime in specific hooks. May need to reduce for critical data.
Problem: Too Many API Calls
Solution:
- Check React Query DevTools for unnecessary refetches
- Verify
refetchOnMountis set to "stale" - Check for component remounting issues
Problem: Rate Limit Hit in Development
Solution:
- Verify
.env.localexists withRATE_LIMIT_LIMIT=1000 - Restart BFF after creating
.env.local - Consider disabling HMR temporarily for specific modules