Gemini API Caching Best Practices

VerifiedSafe

Best practices for caching Gemini API responses using versioned keys and entity-stable identifiers to reduce costs and latency.

Sby Skills Guide Bot
DevelopmentIntermediate
8/9/2026
Claude CodeCursorWindsurfCopilotCodex
#caching#gemini-api#localstorage#cache-invalidation#typescript

Recommended for

Automated summary

Describes a multi-layered caching strategy for Gemini API calls built on a global cache version, entity-stable keys, and several invalidation mechanisms.

Strengths

  • Uses cache keys prefixed with a version constant, so the entire cache can be invalidated by bumping one value
  • Recommends keys based on entity IDs rather than prompt text, preventing needless regeneration after minor prompt tweaks
  • Provides complete TypeScript examples for cache reads, writes, and clearing
  • Covers three cache-busting approaches: a forceRebuild flag, targeted deletion, and prefix-based clearing

Limitations

  • The document is truncated, leaving some sections (bulk clearing, quota handling) incomplete
  • Relies entirely on localStorage and does not discuss size limits, expiry, or shared server-side caching
  • Examples reference WescoBar-specific services and types (geminiService, Character) that are not portable
When to use it

Reach for it when adding or changing a feature that calls the Gemini API and you want to avoid regenerating content that already exists.

When not to use it

Skip it if your app does not use localStorage or if generated content must always be fresh on every request.

Security analysis

Safe
Quality score92/100

The skill contains only instructional content for implementing client-side caching using localStorage and TypeScript. It does not instruct or perform any destructive, exfiltrating, or obfuscated actions, nor does it use shell, network, or other powerful tools. The code examples are straightforward and safe.

No concerns found

How this analysis is made

Examples

Add portrait caching
Add caching for the character portrait generation calls using the Gemini API caching skill: versioned keys, entity-stable IDs, and a forceRebuild option.
Invalidate cache after prompt change
I updated the world-scene prompt template. Show me how to bump the cache version so all cached world images are invalidated at once.
Fix over-eager cache misses
Our Gemini image cache keeps missing after small prompt edits. Refactor the cache keys to be entity-stable instead of prompt-based.

name: gemini-api-caching description: Best practices for Gemini API caching strategy including cache versioning, entity-stable keys, and cache busting for WescoBar

Gemini API Caching

Purpose

Implement robust caching strategies for Gemini API calls to reduce cost, improve latency, and provide better user experience while supporting cache invalidation when needed.

When to Use

  • Implementing any Gemini API feature
  • Generating character portraits or world images
  • Caching AI-generated content
  • Reducing API quota usage
  • Improving app performance

Caching Strategy

From AGENTS.md Section 3: "A robust, multi-layered local storage cache"

1. Cache Versioning

2. Entity-Stable Keys

3. Cache Busting

Instructions

Step 1: Define Cache Version

// Global cache version constant
const CACHE_VERSION = 'v2';

// Prepend to all cache keys
const getCacheKey = (entity: string, id: string) => {
  return `${CACHE_VERSION}-${entity}:${id}`;
};

// Example
const portraitKey = getCacheKey('character-portrait', 'core-pablo');
// Result: "v2-character-portrait:core-pablo"

Purpose: Instant global cache invalidation by bumping version

When to bump:

  • Prompt template changes
  • Image generation model updates
  • Quality improvements needed
  • Global regeneration required

Step 2: Use Entity-Stable Keys

// ✅ CORRECT: Entity-based key (stable)
const cacheKey = `${CACHE_VERSION}-character-portrait:${character.id}`;

// ❌ WRONG: Prompt-based key (invalidates on prompt changes)
const cacheKey = `${CACHE_VERSION}-${fullPromptText}`;

Why entity-stable:

  • Same character = same cache key
  • Minor prompt tweaks don't invalidate cache
  • Consistent across sessions
  • Reduces unnecessary regeneration

Entity Types:

  • character-portrait:<id> - Character portraits
  • world-scene:<id> - World/location images
  • story-illustration:<id> - Story illustrations
  • ui-element:<name> - UI-generated images

Step 3: Implement Cache Read

async function getCharacterPortrait(
  character: Character,
  options?: { forceRebuild?: boolean }
): Promise<string> {
  // Check force rebuild flag
  if (options?.forceRebuild) {
    return await generateNewPortrait(character);
  }

  // Build cache key
  const cacheKey = `${CACHE_VERSION}-character-portrait:${character.id}`;

  // Check localStorage cache
  const cached = localStorage.getItem(cacheKey);

  if (cached) {
    console.log(`✅ Cache hit: ${cacheKey}`);
    return cached; // Return cached URL
  }

  console.log(`⚠️ Cache miss: ${cacheKey}`);

  // Generate new image
  const imageUrl = await generateNewPortrait(character);

  // Store in cache
  localStorage.setItem(cacheKey, imageUrl);

  return imageUrl;
}

Step 4: Implement Cache Write

async function generateAndCachePortrait(
  character: Character
): Promise<string> {
  // Generate image via Gemini API
  const imageUrl = await geminiService.generateImage({
    prompt: buildCharacterPrompt(character),
    // ... other options
  });

  // Cache the result
  const cacheKey = `${CACHE_VERSION}-character-portrait:${character.id}`;
  localStorage.setItem(cacheKey, imageUrl);

  console.log(`💾 Cached: ${cacheKey}`);

  return imageUrl;
}

Step 5: Implement Cache Busting

// Option 1: Force rebuild via flag
async function regeneratePortrait(character: Character) {
  const imageUrl = await getCharacterPortrait(character, {
    forceRebuild: true // Bypass cache
  });
  return imageUrl;
}

// Option 2: Clear specific cache entry
function clearPortraitCache(character: Character) {
  const cacheKey = `${CACHE_VERSION}-character-portrait:${character.id}`;
  localStorage.removeItem(cacheKey);
  console.log(`🗑️ Cleared cache: ${cacheKey}`);
}

// Option 3: Clear all portraits
function clearAllPortraits() {
  const prefix = `${CACHE_VERSION}-character-portrait:`;

  Object.keys(localStorage)
    .filter(key => key.startsWith(prefix))
    .forEach(key => localStorage.removeItem(key));

  console.log(`🗑️ Cleared all portrait cache`);
}

// Option 4: Bump global version
// Change CACHE_VERSION = 'v2' → 'v3'
// All old caches become stale automatically

Step 6: Handle Cache Expiration (Optional)

interface CachedImage {
  url: string;
  timestamp: number;
  version: string;
}

function getCachedImage(key: string, maxAge: number = 7 * 24 * 60 * 60 * 1000): string | null {
  const cached = localStorage.getItem(key);

  if (!cached) return null;

  try {
    const data: CachedImage = JSON.parse(cached);

    // Check version
    if (data.version !== CACHE_VERSION) {
      localStorage.removeItem(key);
      return null;
    }

    // Check age
    const age = Date.now() - data.timestamp;
    if (age > maxAge) {
      localStorage.removeItem(key);
      return null;
    }

    return data.url;
  } catch {
    // Invalid format - clear cache
    localStorage.removeItem(key);
    return null;
  }
}

function setCachedImage(key: string, url: string) {
  const data: CachedImage = {
    url,
    timestamp: Date.now(),
    version: CACHE_VERSION
  };

  localStorage.setItem(key, JSON.stringify(data));
}

Integration Patterns

With Rate Limiting Skill

Combine with gemini-api-rate-limiting skill:

async function generateImagesSequentially(characters: Character[]) {
  for (const character of characters) {
    // Check cache first (from this skill)
    const cached = await getCharacterPortrait(character);

    if (cached) {
      updateCharacterImage(character.id, cached);
      continue; // Skip API call
    }

    // Cache miss - generate with rate limiting
    // (from gemini-api-rate-limiting skill)
    try {
      const imageUrl = await generateWithTimeout(character, 30000);
      updateCharacterImage(character.id, imageUrl);
    } catch (error) {
      handleGenerationError(character.id, error);
    }

    // Delay between calls (rate limiting)
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}

UI Integration

// In React component
function CharacterCard({ character }: { character: Character }) {
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const [isRegenerating, setIsRegenerating] = useState(false);

  useEffect(() => {
    // Load from cache or generate
    getCharacterPortrait(character).then(setImageUrl);
  }, [character.id]);

  const handleRegenerate = async () => {
    setIsRegenerating(true);

    // Force rebuild (cache busting)
    const newUrl = await regeneratePortrait(character);
    setImageUrl(newUrl);

    setIsRegenerating(false);
  };

  return (
    <div>
      <img src={imageUrl || placeholderImage} alt={character.name} />
      <button onClick={handleRegenerate} disabled={isRegenerating}>
        {isRegenerating ? 'Regenerating...' : 'Regenerate Portrait'}
      </button>
    </div>
  );
}

Cache Management UI

// Admin/settings panel for cache management
function CacheManagement() {
  const [stats, setStats] = useState({ portraits: 0, scenes: 0, total: 0 });

  useEffect(() => {
    // Calculate cache stats
    const portraitKeys = Object.keys(localStorage)
      .filter(key => key.includes('character-portrait'));

    setStats({
      portraits: portraitKeys.length,
      scenes: 0, // Add scene counting
      total: localStorage.length
    });
  }, []);

  const clearAllCache = () => {
    const confirmed = window.confirm('Clear all cached images? This will regenerate on next load.');

    if (confirmed) {
      Object.keys(localStorage)
        .filter(key => key.startsWith(CACHE_VERSION))
        .forEach(key => localStorage.removeItem(key));

      alert('Cache cleared!');
      window.location.reload();
    }
  };

  return (
    <div>
      <h3>Gemini API Cache</h3>
      <p>Cached portraits: {stats.portraits}</p>
      <p>Total cached items: {stats.total}</p>
      <button onClick={clearAllCache}>Clear All Cache</button>
    </div>
  );
}

Best Practices

  1. Always check cache first - Reduce API calls
  2. Use entity-stable keys - Don't invalidate on prompt changes
  3. Version globally - Easy bulk invalidation
  4. Provide regenerate option - Let users force refresh
  5. Consider expiration - For time-sensitive content
  6. Monitor cache size - localStorage has limits (~5-10MB)
  7. Handle cache errors - Fallback to generation

Related Skills

  • gemini-api-rate-limiting - Combine cache with rate limiting
  • gemini-api-error-handling - Handle cache failures

Storage Limits

localStorage Limits

  • Size: ~5-10MB depending on browser
  • Monitor usage: Track total cache size
  • Cleanup strategy: Remove oldest entries when full
function getCacheSize(): number {
  let total = 0;

  for (const key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += localStorage[key].length + key.length;
    }
  }

  return total; // bytes
}

// If approaching limit, clear old entries
if (getCacheSize() > 5 * 1024 * 1024) { // 5MB
  clearOldestEntries();
}

Notes

  • Caching reduces API costs significantly
  • Entity-stable keys prevent unnecessary cache invalidation
  • Global version bumping enables instant cache refresh
  • Force rebuild option gives users control
  • Combine with rate limiting for complete API management

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/gemini-api-caching-best-practices-msl9cevl' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/gemini-api-caching-best-practices-msl9cevl/download' --output '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md.part' && mv '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md.part' '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && test -s '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/gemini-api-caching' && cp -R '.skill-downloads/gemini-api-caching-best-practices-msl9cevl' '.claude/skills/gemini-api-caching'

Cursor
test -f '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && test -s '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/gemini-api-caching' && cp -R '.skill-downloads/gemini-api-caching-best-practices-msl9cevl' '.cursor/skills/gemini-api-caching'

Codex
test -f '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && test -s '.skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/gemini-api-caching' && cp -R '.skill-downloads/gemini-api-caching-best-practices-msl9cevl' '.agents/skills/gemini-api-caching'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/gemini-api-caching-best-practices-msl9cevl'

openskills read 'gemini-api-caching-best-practices-msl9cevl'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/gemini-api-caching-best-practices-msl9cevl/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills