Appearance
Caching Strategies
Optimize performance and reduce API calls with smart caching.
Server-Side Caching
Built-in Cache Headers
All generated images include cache headers:
Cache-Control: public, max-age=86400This allows CDNs and browsers to cache images for 24 hours.
CDN Caching
Cloudflare
javascript
// Cloudflare Worker
export default {
async fetch(request, env) {
const cacheKey = new Request(request.url, request);
const cache = caches.default;
// Check cache first
let response = await cache.match(cacheKey);
if (response) return response;
// Generate new image
response = await generateOGImage(request, env);
// Cache for 7 days
response = new Response(response.body, response);
response.headers.set('Cache-Control', 'public, max-age=604800');
// Store in cache
event.waitUntil(cache.put(cacheKey, response.clone()));
return response;
}
};Vercel Edge
javascript
// Next.js API route
export const config = {
runtime: 'edge',
};
export default async function handler(request) {
// ...generate image
return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=604800'
}
});
}Redis Caching
javascript
// Node.js with Redis
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const CACHE_TTL = 86400; // 24 hours
async function getOGImage(params) {
const cacheKey = `og:${JSON.stringify(params)}`;
// Check cache
const cached = await redis.getBuffer(cacheKey);
if (cached) return cached;
// Generate new image
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify(params)
});
const buffer = Buffer.from(await response.arrayBuffer());
// Cache result
await redis.setex(cacheKey, CACHE_TTL, buffer);
return buffer;
}File System Caching
Build-Time Generation
javascript
// Generate once at build time
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
async function generateWithCache(params) {
const hash = crypto
.createHash('md5')
.update(JSON.stringify(params))
.digest('hex');
const cachePath = path.join('./cache/og', `${hash}.png`);
// Return cached if exists
if (fs.existsSync(cachePath)) {
return fs.readFileSync(cachePath);
}
// Generate new
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify(params)
});
const buffer = Buffer.from(await response.arrayBuffer());
// Save to cache
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
fs.writeFileSync(cachePath, buffer);
return buffer;
}Client-Side Caching
Browser Cache
Images are automatically cached by browsers. Use versioned URLs for cache busting:
html
<!-- Version in URL for cache control -->
<meta property="og:image" content="/api/og?title=Hello&v=2">Service Worker
javascript
// sw.js
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/og')) {
event.respondWith(
caches.open('og-images').then((cache) => {
return cache.match(event.request).then((response) => {
if (response) return response;
return fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
});
})
);
}
});Cache Invalidation
Content-Based Keys
javascript
function getCacheKey(content) {
const hash = crypto
.createHash('md5')
.update(content.title + content.updatedAt)
.digest('hex');
return `og-${hash}`;
}Time-Based Invalidation
javascript
// Add timestamp to URL
const imageUrl = `/api/og?title=${title}&t=${Math.floor(Date.now() / 86400000)}`;Best Practices
| Strategy | When to Use |
|---|---|
| CDN caching | Production, high traffic |
| Redis | Dynamic content, multi-server |
| File system | Build-time, static sites |
| Signed URLs | Content that rarely changes |
Cache Duration Guidelines
| Content Type | Recommended TTL |
|---|---|
| Static pages | 7 days |
| Blog posts | 24 hours |
| Products | 1-6 hours |
| User content | 1 hour |
| Dynamic data | No cache or very short |