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
| Goal | Best approach | Convertly tool |
|---|
| Same image, pick the right file size for the viewport | srcset + 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, gravity | URL builder per <source> |
| Next.js app, minimal markup changes | next/image custom loader | createConvertlyLoader |
Rule of thumb: if only the pixel width changes, use <ConvertlyImage>. If the composition changes, use <picture>.
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> 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
| Prop | Purpose |
|---|
namespace | Your public delivery namespace (e.g. assets-acme). Required unless you pass namespace on every image. |
baseUrl | CDN host. Defaults to https://cdn.convertly.sh. Set to your custom domain when live. |
includeNamespace | Whether 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.
<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.
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:
| Mode | Use when |
|---|
gravity=auto | Default saliency crop — good baseline |
gravity=smart | Off-centre subjects, busy scenes |
gravity=face | Portraits and team photos |
gravity=entropy | Textures and detail-heavy frames |
fp=x,y | You know the focal point (from CMS or analyze API) |
<!-- 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.
Quick reference
| Component / API | Renders | Best for |
|---|
<ConvertlyCdnProvider> | (context only) | Shared namespace + CDN client for React/Solid trees |
<ConvertlyImage> | <img> + optional srcset | Resolution switching, lazy loading, transform props |
createConvertlyCdn() | URL strings | Astro, Svelte, <picture>, non-React stacks |
createConvertlyLoader() | Next.js loader | Existing next/image markup |
<picture> + URL builder | <picture> | Art direction across breakpoints |