DEV Community

Cover image for Free On-the-Fly Image Optimization for IPFS Content (no node, no auth)
Nacho Coll
Nacho Coll

Posted on

Free On-the-Fly Image Optimization for IPFS Content (no node, no auth)

Hi everyone — Nacho here from the BWS (Blockchain Web Services) team. We just shipped IPFS.NINJA, a managed IPFS pinning service, and I want to walk through one of the features I think is genuinely under-discussed in the IPFS world: on-the-fly image optimization served straight from a public IPFS gateway.

Full disclosure: I work on this product. This is a transparent walkthrough from the team that built it.

The problem

IPFS is great at serving immutable content. But if you've ever tried to use IPFS for the image-heavy parts of a real product — NFT galleries, dApp avatars, marketplace thumbnails, blog covers — you've hit the same wall:

  • Your CID is the original 4 MB PNG. There is no ?w=400 to ask for a thumbnail.
  • You either pre-generate every variant at upload time (and now you're managing N CIDs per asset), or you serve the full-size file every time and watch your bandwidth quota burn.
  • Modern formats like AVIF and WebP? You'd need to encode them yourself before uploading.

This is why most production dApps end up putting a Cloudinary or Imgix in front of IPFS, which kind of defeats the point.

What we built

IPFS.NINJA exposes a public, no-auth image transform endpoint:

GET /image/:cid
Enter fullscreen mode Exit fullscreen mode

Query parameters:

Param Default Description
w Output width in pixels (max 4096)
h Output height in pixels (max 4096)
format webp, jpeg, png, or avif
quality 80 Compression quality 1-100 (only when format is set)
fit cover cover, contain, fill, inside, or outside

It's served from api.ipfs.ninja/image/:cid, available on all plans including the free Dharma tier (no API key required for the transform itself), and the responses ship with immutable cache headers — same CID + same params = same bytes forever, perfect for any CDN in front.

Quick examples

Resize to 400px wide as WebP:

curl "https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=400&format=webp"
Enter fullscreen mode Exit fullscreen mode

200x200 JPEG thumbnail at 60% quality:

curl "https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=200&h=200&format=jpeg&quality=60&fit=cover"
Enter fullscreen mode Exit fullscreen mode

Drop it into HTML

The whole point is that you can use it like any other image CDN. Here's a responsive <img>:

<img
  srcset="
    https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=400&format=webp 400w,
    https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=800&format=webp 800w,
    https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=1200&format=webp 1200w
  "
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  src="https://api.ipfs.ninja/image/QmXmCX9S6ANV...?w=800&format=webp"
  alt="Responsive IPFS image"
/>
Enter fullscreen mode Exit fullscreen mode

You store one CID. The browser asks for the size and format it actually needs. The bytes that come back are content-addressed back to your original via the CID in the URL.

Why this matters for NFTs

If you mint with the original artwork's CID as the canonical reference (and you should — that's what gives the asset provenance), you can still render efficient gallery views, thumbnails, and previews:

const tokenImage = `ipfs://${cid}`; // canonical, immutable
const thumb = `https://api.ipfs.ninja/image/${cid}?w=240&h=240&format=webp&quality=70`;
const preview = `https://api.ipfs.ninja/image/${cid}?w=800&format=avif`;
Enter fullscreen mode Exit fullscreen mode

The original artwork stays untouched on IPFS. You just get cheap, on-demand variants for your UI.

A note on framework integration

Next.js <Image>, Nuxt <NuxtImg>, Astro <Image>, and most other framework image components let you plug in a custom loader. You can write a one-liner that maps (src, width, quality)https://api.ipfs.ninja/image/${cid}?w=${width}&format=webp&quality=${quality} and you've effectively connected your framework's responsive image pipeline to IPFS.

Caching

Responses include immutable cache headers. Because IPFS content is content-addressed, the same CID with the same query parameters always produces the same output. Browsers and CDNs (Cloudflare, Fastly, your own Varnish) can cache these responses indefinitely — no cache busting needed, ever.

Limits and honesty

A few things worth knowing:

  • Max output dimensions: 4096px on either axis.
  • The transform endpoint is public; if you don't want certain CIDs hot-reachable, don't pin them publicly (or use Nirvana's dedicated gateway with token-based access).
  • If you don't pass any params, the request just redirects to the original file.

Try it

Questions or edge cases you'd like to see supported (focal point cropping, smart blur for placeholders, etc.) — drop them below. We're actively iterating.

— Nacho, BWS team

Top comments (0)