OG Image CDN Caching: Why Your Image Won't Update and How to Fix It
Social platforms cache your og:image aggressively. Here's why CDN caching causes stale OG previews and how to bust the cache across Twitter, LinkedIn, Facebook, Slack, and Discord.
Why social platforms cache OG images
When a user shares a URL, Twitter, LinkedIn, Slack, and Discord's scrapers fetch your page HTML and then separately fetch the image at og:image. They cache the image on their own CDN — sometimes for hours, sometimes for weeks — so they can serve link previews fast without hammering your server.
The problem: if you update the image at the same URL, the cached version stays live on the platform's CDN until their TTL expires or you manually purge it.
Platform-by-platform cache behavior
| Platform | Typical Cache TTL | Purge Method |
|---|---|---|
| Twitter / X | ~7 days | cards.twitter.com/validator |
| ~7 days | LinkedIn Post Inspector | |
| ~30 days | Facebook Sharing Debugger | |
| Slack | ~24 hours | No manual purge — wait or change URL |
| Discord | ~24 hours | No manual purge — use URL versioning |
Fix 1: Change the image URL (most reliable)
Every social platform treats a different URL as a new image. The fastest way to bust the cache is to change the og:image URL:
<!-- Before --> <meta property="og:image" content="https://mysite.com/images/og-home.png" /> <!-- After (add a version query param or change the filename) --> <meta property="og:image" content="https://mysite.com/images/og-home.png?v=2" /> <!-- OR --> <meta property="og:image" content="https://mysite.com/images/og-home-v2.png" />
For dynamic OG images generated on-demand, append the content's updated_at timestamp as a query param:
<meta property="og:image" content="https://mysite.com/api/og?slug=my-post&v=1711234567" />
Fix 2: Set correct Cache-Control headers on your image
If your images are served from your own server or CDN, set a reasonable TTL so scrapers can revalidate after updates:
# For static OG images that change infrequently Cache-Control: public, max-age=86400, s-maxage=86400 # For dynamic OG images that may change often Cache-Control: public, max-age=3600, s-maxage=3600 # For versioned OG images (use long TTL since URL changes on update) Cache-Control: public, max-age=31536000, immutable
Note: these headers control your CDN's behavior, not the social platform's internal cache. Once Twitter/LinkedIn have cached the image, you can't evict it remotely with headers alone.
Fix 3: Use platform debuggers to force re-scrape
Most platforms provide a tool to manually re-scrape a URL and refresh their cached preview:
- Twitter/X: cards-dev.twitter.com/validator — paste your URL and click "Preview card"
- LinkedIn: linkedin.com/post-inspector — paste and click "Inspect"
- Facebook: developers.facebook.com/tools/debug — paste and click "Scrape Again"
Fix 4: Vercel / Next.js OG image caching
If you generate OG images with next/og (ImageResponse), Vercel caches them at the edge. To control this, set cache headers in your route handler:
// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
return new ImageResponse(
<div style={{ background: '#0f0f0f', width: '1200px', height: '630px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h1 style={{ color: 'white', fontSize: 60, padding: '0 60px' }}>{title}</h1>
</div>,
{
width: 1200,
height: 630,
headers: {
// Cache for 1 hour at edge, 1 day in browsers
'Cache-Control': 'public, s-maxage=3600, max-age=86400',
},
}
);
}Verify your OG image is refreshed
After making changes, paste your URL into OGFixer to see the live image that scrapers will fetch — not the cached version your browser shows. OGFixer always fetches fresh from the server, so you get ground truth.