Skip to main content
Responsive images usually solve one of two problems: resolution switching (same composition, different pixel widths) or art direction (different crop, aspect ratio, or focal point per viewport). Convertly handles both from a single source file — you change URL parameters, not maintain separate exports.
Prerequisites: a delivery namespace and either Convertly Storage file IDs or a configured origin source. Install @convertly-sh/image before using the React examples below.

Resolution switching vs art direction

GoalBest approachConvertly tool
Same image, pick the right file size for the viewportsrcset + sizes<ConvertlyImage> or cdn.srcset()
Different crop or aspect ratio at each breakpoint<picture> with <source media="…">createConvertlyCdn() + manual <picture> markup
Subject moves or reframes on mobile vs desktop<picture> + different w, h, fit, gravityURL builder per <source>
Next.js app, minimal markup changesnext/image custom loadercreateConvertlyLoader
Rule of thumb: if only the pixel width changes, use <ConvertlyImage>. If the composition changes, use <picture>.
Art-directed picture element crops at desktop, tablet, phablet, and mobile breakpoints
Resize your browser or use DevTools device mode to see how <picture> picks the matching <source>. Chrome may fetch twice in the inspector — that is a tooling quirk, not double-loading in production.

<ConvertlyCdnProvider> — configure once

<ConvertlyCdnProvider> is a React context wrapper that creates one shared CDN client (createConvertlyCdn) for every <ConvertlyImage> and <ConvertlyVideo> underneath it.
import { ConvertlyCdnProvider, ConvertlyImage } from "@convertly-sh/image/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ConvertlyCdnProvider
      namespace={process.env.NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE!}
      baseUrl="https://cdn.convertly.sh" // optional; default shown
    >
      {children}
    </ConvertlyCdnProvider>
  );
}
What it stores
PropPurpose
namespaceYour public delivery namespace (e.g. assets-acme). Required unless you pass namespace on every image.
baseUrlCDN host. Defaults to https://cdn.convertly.sh. Set to your custom domain when live.
includeNamespaceWhether URLs include /{namespace}/ in the path. Auto-detected from baseUrl; override only if you know you need to.
Why use it
  • DRY config — set namespace once in your root layout instead of on every image.
  • Single CDN instance — avoids recreating the URL builder on every render.
  • Composable — child components call <ConvertlyImage src="…" /> without knowing env vars.
When you can skip it Pass namespace directly on <ConvertlyImage>, or use the framework-agnostic createConvertlyCdn() in Astro, Svelte, or plain HTML. Vue uses provideConvertlyCdn() for the same pattern; Solid uses <ConvertlyCdnProvider> identically to React.
Use NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE (your public delivery namespace) — never a secret cvly_… API key. Namespace values are safe in client bundles; API keys are not.

Resolution switching with <ConvertlyImage>

<ConvertlyImage> renders a real <img> with Convertly CDN URLs. When you pass width and sizes, it automatically builds a responsive srcSet using defaultWidths() and sets format=auto + gravity=auto by default.
320 pixel wide vs 1200 pixel wide responsive srcset candidates side by side
<ConvertlyImage
  src="hero-summer"
  width={1200}
  height={800}
  fit="cover"
  alt="Summer collection hero"
  sizes="(min-width: 1280px) 1200px, (min-width: 768px) 80vw, 100vw"
/>
That produces markup equivalent to:
<img
  src="https://cdn.convertly.sh/assets-acme/hero-summer?w=1200&h=800&fit=cover&format=auto&gravity=auto"
  srcset="
    https://cdn.convertly.sh/assets-acme/hero-summer?w=320&h=213&fit=cover&format=auto&gravity=auto 320w,
    https://cdn.convertly.sh/assets-acme/hero-summer?w=480&h=320&fit=cover&format=auto&gravity=auto 480w,

    https://cdn.convertly.sh/assets-acme/hero-summer?w=2400&h=1600&fit=cover&format=auto&gravity=auto 2400w
  "
  sizes="(min-width: 1280px) 1200px, (min-width: 768px) 80vw, 100vw"
  width="1200"
  height="800"
  loading="lazy"
  decoding="async"
  alt="Summer collection hero"
/>

Origin-backed images

For assets on a registered origin source (e.g. deployed public/ folder):
<ConvertlyImage
  origin="site"
  src="/hero.jpg"
  width={1200}
  sizes="100vw"
  alt="Homepage hero"
/>

Custom width lists

Override the default breakpoint ladder when your layout needs specific steps:
<ConvertlyImage
  src="hero-summer"
  width={800}
  widths={[400, 600, 800, 1200, 1600]}
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="Product card"
/>

Next.js: global loader instead of per-component markup

If you use next/image everywhere, wire createConvertlyLoader once — every <Image> gets CDN URLs without switching to <ConvertlyImage>. Use <ConvertlyImage> when you need transform props (preset, text overlays, gravity=face) that next/image does not pass through.

Art direction with <picture>

Use <picture> when the crop or aspect ratio should change at a breakpoint — for example a wide landscape hero on desktop and a tall portrait crop on mobile. Convertly does not ship a <ConvertlyPicture> component today. Build <picture> with the URL builder; each <source> gets its own transform params.

Plain HTML

Replace {namespace} and {fileIdOrSlug} with your values:
<picture>
  <source
    media="(min-width: 1280px)"
    srcset="https://cdn.convertly.sh/{namespace}/{fileIdOrSlug}?w=1280&h=720&fit=cover&gravity=smart&format=auto"
  />
  <source
    media="(min-width: 768px)"
    srcset="https://cdn.convertly.sh/{namespace}/{fileIdOrSlug}?w=768&h=1024&fit=cover&gravity=face&format=auto"
  />
  <source
    media="(min-width: 480px)"
    srcset="https://cdn.convertly.sh/{namespace}/{fileIdOrSlug}?w=568&h=320&fit=cover&gravity=smart&format=auto"
  />
  <img
    src="https://cdn.convertly.sh/{namespace}/{fileIdOrSlug}?w=320&h=568&fit=cover&gravity=face&format=auto"
    alt="Campaign hero"
    loading="lazy"
    decoding="async"
  />
</picture>
One origin image. Four CDN URLs. Each variant is generated on demand and cached at the edge.
Wide hero source image before art-directed cropsFour art-directed crops for picture element breakpoints

React with the URL builder

import { createConvertlyCdn } from "@convertly-sh/image";

const cdn = createConvertlyCdn({
  namespace: process.env.NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE!,
});

const fileId = "hero-summer";

export function HeroPicture() {
  return (
    <picture>
      <source
        media="(min-width: 1280px)"
        srcSet={cdn.url(fileId, { w: 1280, h: 720, fit: "cover", gravity: "smart" })}
      />
      <source
        media="(min-width: 768px)"
        srcSet={cdn.url(fileId, { w: 768, h: 1024, fit: "cover", gravity: "face" })}
      />
      <img
        src={cdn.url(fileId, { w: 320, h: 568, fit: "cover", gravity: "face" })}
        alt="Campaign hero"
        loading="lazy"
        decoding="async"
      />
    </picture>
  );
}
If you already use <ConvertlyCdnProvider>, call useConvertlyCdn() inside child components instead of creating a second client:
import { useConvertlyCdn } from "@convertly-sh/image/react";

function HeroPicture() {
  const cdn = useConvertlyCdn();
  // … same <picture> markup as above
}

Adding srcset per breakpoint

For art-directed sources that also need multiple widths within a breakpoint, combine cdn.srcset() on each <source>:
<picture>
  <source
    media="(min-width: 768px)"
    srcSet={cdn.srcset(fileId, {
      w: 768,
      h: 1024,
      fit: "cover",
      gravity: "face",
      widths: [640, 768, 1024, 1280],
    })}
    sizes="80vw"
  />
  <img
    src={cdn.url(fileId, { w: 480, h: 640, fit: "cover", gravity: "face" })}
    srcSet={cdn.srcset(fileId, {
      w: 480,
      h: 640,
      fit: "cover",
      gravity: "face",
      widths: [320, 480, 640],
    })}
    sizes="100vw"
    alt="Campaign hero"
  />
</picture>

Automated cropping for art direction

When the subject is not centred, pick a gravity mode per breakpoint instead of pre-cropping assets:
ModeUse when
gravity=autoDefault saliency crop — good baseline
gravity=smartOff-centre subjects, busy scenes
gravity=facePortraits and team photos
gravity=entropyTextures and detail-heavy frames
fp=x,yYou know the focal point (from CMS or analyze API)
Center crop vs face-aware crop on a team photo
<!-- Portrait crop that locks onto the nearest face -->
<img src="https://cdn.convertly.sh/{namespace}/{fileIdOrSlug}?w=400&h=400&fit=cover&gravity=face" alt="Team headshot" />
See Smart cropping & image analysis for focal points, the analyze endpoint, and before/after examples.

Retina and high-DPI screens

Convertly uses width descriptors in srcset (320w, 640w, …). The browser picks a URL whose width matches the rendered size × device pixel ratio. You do not need separate 1x / 2x density descriptors — include widths up to ~2× your layout width (which defaultWidths() does automatically). Alternative: fixed layout width + dpr=2 on the URL. See Device pixel ratio and Client Hints when the slot width is fluid. Example: an image displayed at 600px CSS width on a 2× screen may request the 1200w candidate from the set.
Small and large srcset width candidates for retina displays

Quick reference

Component / APIRendersBest for
<ConvertlyCdnProvider>(context only)Shared namespace + CDN client for React/Solid trees
<ConvertlyImage><img> + optional srcsetResolution switching, lazy loading, transform props
createConvertlyCdn()URL stringsAstro, Svelte, <picture>, non-React stacks
createConvertlyLoader()Next.js loaderExisting next/image markup
<picture> + URL builder<picture>Art direction across breakpoints