Caching
The Featsync SDK uses multi-layer caching to ensure fast, reliable flag evaluations.
Cache Layers
1. Memory Cache
Flags are stored in memory for quick access.
const featsync = new Featsync({ apiKey: 'fs_...', cacheMs: 60000, // Cache for 60 seconds (default)});2. Persistent Cache
By default, flags are also saved to localStorage (browser) or a file cache (Node.js).
const featsync = new Featsync({ apiKey: 'fs_...', persist: true, // Enable persistent cache (default)});Cache Flow
┌─────────────────────────────────────────────────────┐│ isEnabled('my-flag') │└─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ Memory Cache Fresh? │ └─────────────────────────┘ │Yes │No ▼ ▼ Return cached ┌─────────────────┐ value │ Fetch from API │ └─────────────────┘ │Success │Error ▼ ▼ Update cache Return stale Return value cached valueCache TTL
The cacheMs option controls how long flags stay fresh:
// 1 minute (default)cacheMs: 60000;
// 5 minutescacheMs: 300000;
// 30 secondscacheMs: 30000;
// Disable cache (not recommended)cacheMs: 0;Stale-While-Revalidate
If the API is down or slow, the SDK uses stale cached data:
// If API fails, returns last known valueconst enabled = await featsync.isEnabled('my-flag');// Returns cached value, not an errorThis ensures your app never breaks due to Featsync being unavailable.
Manual Cache Control
Clear Cache
Force the next call to fetch fresh data:
featsync.clearCache();
// Next call fetches from APIconst enabled = await featsync.isEnabled('my-flag');Force Refresh
Clear cache before checking:
function refreshFlags() { featsync.clearCache(); return featsync.getAllFlags();}Prefetching
Load all flags immediately on initialization:
const featsync = new Featsync({ apiKey: 'fs_...', prefetch: true, // Fetch all flags on init});
// Flags are already loaded, no delayconst enabled = await featsync.isEnabled('my-flag');Use prefetch: true when you want flags available synchronously after init.
Serverless Considerations
In serverless environments (Lambda, Cloudflare Workers), instances are short-lived. Use these strategies:
Singleton Pattern
let featsync = null;
async function getFeatsync() { if (!featsync) { featsync = new Featsync({ apiKey: process.env.FEATSYNC_API_KEY, prefetch: true, }); } return featsync;}
export default async function handler(req) { const fs = await getFeatsync(); // ...}Longer Cache
const featsync = new Featsync({ apiKey: 'fs_...', cacheMs: 300000, // 5 minutes for serverless});Browser Considerations
In browsers, the persistent cache uses localStorage:
// Cache key formatlocalStorage.getItem('featsync:flags'); // All flagslocalStorage.getItem('featsync:timestamp'); // Last fetch timeClear Browser Cache
featsync.clearCache();// Also clears localStoragePrivate/Incognito Mode
In private mode, localStorage may be restricted. The SDK handles this gracefully and falls back to memory-only cache.
Disabling Persistence
For privacy or testing:
const featsync = new Featsync({ apiKey: 'fs_...', persist: false, // Memory-only cache});Cache and Percentage Rollouts
Percentage rollout calculations are done locally, not cached:
// Flag state is cached (enabled: true, percentage: 25)// But hash calculation happens fresh each timeconst enabled = await featsync.isEnabledForUser('flag', userId);This means:
- Flag percentage is cached
- User bucketing is calculated fresh
- Same user always gets the same result (consistent hashing)
Best Practices
1. Use Default Settings
The defaults are optimized for most use cases:
cacheMs: 60000(1 minute)persist: true
2. Don’t Disable Cache
Always have some caching for resilience:
// GoodcacheMs: 30000; // Minimum recommended
// BadcacheMs: 0; // No cache, risky3. Prefetch for Critical Flags
If flags are needed immediately on page load:
const featsync = new Featsync({ apiKey: 'fs_...', prefetch: true,});4. Clear Cache After Updates
If you have a webhook for flag changes:
app.post('/webhooks/featsync', (req, res) => { featsync.clearCache(); res.sendStatus(200);});