Skip to content

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 value

Cache TTL

The cacheMs option controls how long flags stay fresh:

// 1 minute (default)
cacheMs: 60000;
// 5 minutes
cacheMs: 300000;
// 30 seconds
cacheMs: 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 value
const enabled = await featsync.isEnabled('my-flag');
// Returns cached value, not an error

This 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 API
const 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 delay
const 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 format
localStorage.getItem('featsync:flags'); // All flags
localStorage.getItem('featsync:timestamp'); // Last fetch time

Clear Browser Cache

featsync.clearCache();
// Also clears localStorage

Private/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 time
const 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:

// Good
cacheMs: 30000; // Minimum recommended
// Bad
cacheMs: 0; // No cache, risky

3. 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);
});

Next Steps