NEWWorld's first AI visibility audit tool for Web3 is live.Run free audit →
PLAYBOOK Technical Last reviewed

Cache IPFS Images to a Fast CDN for OpenGraph and SEO Crawlers

Public IPFS gateways timeout for SEO crawlers about 40% of the time. Google's crawler waits ~3 seconds; IPFS often takes 5-8. Cached CDN versions fix og:image discards, NFT thumbnail breakage and slow LCP.

Time
1-2 hours for initial setup, automated thereafter
Difficulty
Intermediate
Impact
High

Why this matters

Before state (what bad looks like)

<!-- og:image points directly to IPFS gateway -->
<meta property="og:image"
      content="https://ipfs.io/ipfs/QmXyZ.../nft-artwork.png">

<!-- Result: ~40% of crawlers timeout, og:image discarded -->

Step-by-step

Step 1: Pick a CDN: Cloudflare R2, Cloudinary or Vercel

Cloudflare R2: free tier (10GB storage), S3-compatible API, fast global CDN. Best for high volume. Cloudinary: free tier (25k transformations/month), built-in image transformations. Best if you need on-the-fly resizing. Vercel Image Optimization: bundled with Vercel hosting, automatic WebP conversion. Best if already on Vercel. For most NFT projects, pick Cloudflare R2.

Step 2: Set up CDN bucket and configure access

Create an R2 bucket with public read access enabled. Configure a custom domain (e.g., cdn.example.com) pointing to it. Generate API credentials for write access. Document credentials securely; you'll need them for the upload pipeline.

# Cloudflare R2 setup via wrangler CLI
wrangler r2 bucket create my-og-cache
# Then in dashboard: enable public access, add custom domain

Step 3: Build an upload script that fetches from IPFS and stores on CDN

For each IPFS-hosted asset that needs to be in og:image, fetch from IPFS gateway, resize to 1200x630, upload to CDN. Run on collection mint, page publish or as a batch backfill for existing pages.

const sharp = require('sharp');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');

async function cacheIpfsToOg(ipfsHash, slug) {
  // Fetch from IPFS via reliable pinning service
  const response = await fetch(`https://gateway.pinata.cloud/ipfs/${ipfsHash}`);
  const buffer = await response.arrayBuffer();

  // Resize to 1200x630
  const resized = await sharp(Buffer.from(buffer))
    .resize(1200, 630, { fit: 'cover' })
    .png()
    .toBuffer();

  // Upload to R2
  const s3 = new S3Client({
    region: 'auto',
    endpoint: 'https://accountid.r2.cloudflarestorage.com',
    credentials: { accessKeyId: process.env.R2_ACCESS, secretAccessKey: process.env.R2_SECRET }
  });

  await s3.send(new PutObjectCommand({
    Bucket: 'my-og-cache',
    Key: `og/${slug}.png`,
    Body: resized,
    ContentType: 'image/png',
    CacheControl: 'public, max-age=31536000',
  }));

  return `https://cdn.example.com/og/${slug}.png`;
}

Step 4: Update og:image meta tags to point to CDN URL

Replace IPFS gateway URLs in og:image, twitter:image and structured data ImageObject with CDN URLs. Keep IPFS hash references in your contracts and on-chain metadata; those stay for permanence.

<meta property="og:image" content="https://cdn.example.com/og/nft-collection-1.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="twitter:image" content="https://cdn.example.com/og/nft-collection-1.png">

Step 5: Configure aggressive cache headers

Set Cache-Control: public, max-age=31536000, immutable for the cached images. Since you're using content-addressed CDN paths (the slug typically includes a hash), images never need to be invalidated. Browsers and CDNs cache forever.

Cache-Control: public, max-age=31536000, immutable

Step 6: Test with social platform validators

Twitter Card Validator (cards-dev.twitter.com/validator), LinkedIn Post Inspector (linkedin.com/post-inspector), Facebook Sharing Debugger (developers.facebook.com/tools/debug). All three should fetch og:image successfully within 1-2 seconds. If any timeout, your CDN setup needs investigation.

Step 7: Monitor CDN cache hit rate

Cloudflare Analytics or your CDN's dashboard shows cache hit rate. Aim for 95%+. Lower hit rates indicate either too-low TTL or too many unique URLs. Optimize by extending TTL or consolidating image variants.

FREE WEB3 AUDIT

See where this playbook applies to your site.

Run a free Crawlux audit before you start the playbook. It tells you which fixes are most urgent.

Free first audit · No signup · 60 seconds · Full PDF report

After state (what good looks like)

<!-- og:image points to CDN-cached version -->
<meta property="og:image"
      content="https://cdn.example.com/og/nft-XyZ.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/png">

<!-- IPFS hash still referenced from contract for permanence -->
<!-- CDN serves crawlers; IPFS serves permanence -->

How to validate the fix

Common pitfalls

Pitfall

Forgetting og:image:width and og:image:height

Twitter and LinkedIn use these for layout decisions. Without them, images may be skipped or rendered incorrectly. Always set both to 1200 and 630.

Pitfall

Using IPFS gateway URL for og:image directly

Even if IPFS happens to be fast for one fetch, it's unreliable across crawlers. Always cache to CDN before referencing in og:image.

Pitfall

Public IPFS gateways instead of pinning service

Public gateways (ipfs.io, cf-ipfs.com) are heavily rate-limited and unreliable. Use Pinata, Filebase or your own gateway for the source fetch in your caching script.

Pitfall

Mismatched image format and Content-Type

Serving a JPEG with image/png Content-Type breaks some crawlers. Always match the file extension and Content-Type. Use sharp to convert to a known format consistently.

Pitfall

Skipping the cache hit rate monitoring

If hit rate is low, your CDN is making extra requests to origin. That's slower and costs more. Monitor and tune.

If something breaks: rollback

Update og:image meta tags back to IPFS gateway URLs. Crawl behavior reverts within hours. Keep CDN bucket; the cached images don't hurt anything left in place.

Run a free Crawlux audit on this fix

Crawlux validates the schema, technical and AEO fixes from this playbook automatically. Free tier on one domain.

Run free audit →

FAQ

Does this work for fully on-chain NFTs?

Yes. Even fully on-chain NFT artwork (stored as base64 in the contract) needs CDN caching for og:image purposes. Fetch from your contract's tokenURI endpoint, decode base64, resize, cache. The on-chain version stays canonical for verification; the CDN version serves crawlers.

Is dual-hosting a centralization compromise?

Slight one yes. Pure decentralization purists may object. Practical answer: IPFS is the source of truth for permanence; CDN is a performance layer. If your CDN goes down, the asset still exists on IPFS. The compromise is worth it for SEO impact.

How do I handle dynamic OG images?

For dynamic OG (e.g., per-NFT generated thumbnails with title overlay), use a service like Vercel OG, Cloudinary on-the-fly transforms or your own image generation API. Cache the result aggressively (CDN edge caching). Same pattern: dynamic generation, cached output.

What about Arweave instead of IPFS?

Same problem, same fix. Arweave gateways are slightly faster than IPFS gateways but still unreliable for crawlers. Cache Arweave assets to CDN for og:image. The Arweave hash stays as canonical reference.

How much does this cost?

Cloudflare R2 free tier (10GB storage, 1M requests/month) covers most NFT projects. Beyond that, R2 is $0.015/GB/month for storage, $0 egress. Cheap. The cost is negligible compared to lost share traffic from broken og:image.

Related playbooks

Pillar guides

Audit modules

RUN YOUR FIRST AUDIT

Run the playbook against a real audit.

Get a free Crawlux audit report and use it as the baseline for the work in this playbook.

Free first audit · No signup · 60 seconds · Full PDF report

Audit this fix → Free audit