> ## Documentation Index
> Fetch the complete documentation index at: https://docs.convertly.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up the image CDN on your site

> Install @convertly-sh/image and serve every image on your site through the Convertly CDN with one configuration step.

The Convertly image CDN serves on-the-fly transformed **images and video** from a global edge cache. Use it for responsive images, poster frames, trimmed MP4/GIF clips, and smart cropping. This guide walks through getting it live on your site end-to-end: choose where the source image lives, use a CDN endpoint, **register an origin or upload to storage**, install `@convertly-sh/image`, wire it into your framework, and verify the result.

<div className="convertly-callout">
  Every CDN URL needs a CDN endpoint plus either an origin source or a storage file UUID/slug. The SDK rewrites URLs; Convertly still fetches the source from your deployed site or storage.
</div>

<Warning>
  **Origin before loader.** If your images live in a Next.js `public/` folder (or any deployed static path), you must create an **origin source** in the dashboard **before** CDN URLs will work. Installing the SDK alone only changes URL strings — Convertly still needs a public HTTPS URL to fetch each source file from. Skip origin setup only if every image is already in Convertly Storage (file UUID).
</Warning>

You have two source options:

* **Source URLs / origin sources**: images that already live behind a public HTTPS URL. This includes deployed framework app public/static assets, plus public S3/R2/GCS/Azure bucket endpoints, custom CDN domains, and web folders. Convertly fetches these deployed URLs on demand; it does not read local project folders.
* **Convertly Storage**: images uploaded to Convertly when you want managed files and file IDs.

For most framework sites, start with an origin source. You do not need to upload repo images to Convertly Storage just to optimize them, but the deployed app must expose those images over public HTTPS.

If you'd rather skim the reference, jump to [Image CDN docs](/docs/image-cdn).

<Steps>
  <Step title="Use your CDN endpoint">
    A CDN endpoint is the durable delivery identity in CDN URLs. Its URL namespace is safe to embed in HTML and ship to the browser because it is not a credential.

    Go to **Image CDN → Delivery** and copy your default endpoint, or create a site-specific endpoint with a URL namespace such as `marketing` or `site`. URL signing keys are only needed when the endpoint requires signed URLs or a protected transform requires a signature.
  </Step>

  <Step title="Choose a source">
    The CDN can optimize either a source URL from a configured public HTTPS origin or a file in Convertly Storage. Pick the source that matches where your images already live.

    * **Repo public/static images**: deploy your site, then point an origin source at that public site URL. If `public/hero.jpg`, `static/hero.jpg`, or another framework-served asset resolves as `https://example.com/hero.jpg`, Convertly can fetch that deployed URL and optimize it as `/cdn/v1/{endpointNamespace}/o/site/hero.jpg`.
    * **Remote assets**: point an origin source at an existing public HTTPS bucket, CDN, or web folder, such as `https://assets.example.com`.
    * **Convertly Storage**: upload or import files to Convertly and use their generated CDN slugs or file IDs, such as `/cdn/v1/{endpointNamespace}/{fileIdOrSlug}`.
  </Step>

  <Step title="Attach an origin source">
    **Required for repo / bucket / external-host images.** Skip this step only if every image uses Convertly Storage UUIDs.

    If your assets already live on HTTPS or a public bucket, go to **Image CDN → Sources** and add a source.

    **Public HTTPS** — bucket, CDN, or deployed site with a public read URL:

    For framework repo assets, use the deployed app URL:

    * **Name**: `Site public assets`
    * **Slug**: `site`
    * **Access**: Public HTTPS
    * **Base URL**: `https://example.com`
    * **Path prefix**: leave blank

    **Private bucket** — S3, R2, GCS, or Azure without public access:

    * Choose your provider and select **Private bucket**
    * Enter bucket name, region/account details, and read credentials
    * Convertly encrypts credentials at rest; they are never shown again after save

    With a public `site` origin, these paths map directly:

    | Repo or origin file                                | Public source URL                       | Convertly CDN URL                                                        |
    | -------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------ |
    | `public/hero.jpg` or `static/hero.jpg`             | `https://example.com/hero.jpg`          | `/cdn/v1/{endpointNamespace}/o/site/hero.jpg?w=1200&format=auto`         |
    | `public/products/card.png` or framework equivalent | `https://example.com/products/card.png` | `/cdn/v1/{endpointNamespace}/o/site/products/card.png?w=800&format=auto` |

    For an asset bucket or existing CDN, use the same pattern with your asset host:

    * **Slug**: a short URL-safe name, e.g. `products`.
    * **Base URL**: the public HTTPS origin, e.g. `https://assets.example.com`.
    * **Path prefix**: optional folder prefix, e.g. `catalog`.

    Then request images with `/cdn/v1/{endpointNamespace}/o/{originSlug}/{path}`:

    ```html theme={"system"}
    <img src="https://cdn.convertly.sh/cdn/v1/assets-acme/o/products/hero.jpg?w=1200&format=auto" alt="Hero" />
    ```
  </Step>

  <Step title="Use Convertly Storage instead">
    If you want Convertly to host and manage the source image, upload it to Convertly Storage instead of creating an origin source.

    * **Manual upload**: open the Files tab in the dashboard and drag-and-drop. The file's id (UUID) is shown in the details panel; the menu has a **Copy file ID** shortcut.
    * **Programmatic upload**: `POST /api/uploads` from your backend with the file bytes; the response includes the file id.
    * **WordPress media**: install the [Convertly WordPress plugin](/guides/wordpress-media-optimization) so media library files sync automatically.

    Storage-backed images use `https://cdn.convertly.sh/{endpointNamespace}/{fileIdOrSlug}?w=1200&format=auto`. New uploads get readable [CDN slugs](/docs/image-cdn/url-structure#cdn-slugs) automatically and UUID URLs keep working. Origin-backed images use `https://cdn.convertly.sh/{endpointNamespace}/o/{originSlug}/{path}?w=1200&format=auto`.
  </Step>

  <Step title="Install the SDK">
    Do this **after** your CDN endpoint exists and your source is configured (origin source or Convertly Storage upload).

    ```bash theme={"system"}
    npm install @convertly-sh/image
    ```

    The package ships React, Next.js, Vue, Astro, Solid, and Svelte adapters. Pick the one that matches your stack.
  </Step>

  <Step title="Wire it into your framework">
    Choose your path:

    <Tabs>
      <Tab title="Next.js">
        Set it once globally so every existing `<Image>` in your app routes through Convertly with no per-component changes.

        ```ts lib/convertly-loader.ts theme={"system"}
        import { createConvertlyLoader } from "@convertly-sh/image/nextjs";

        export default createConvertlyLoader({
          namespace: process.env.NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE!,
          origin: "site",
          localPassthrough: process.env.NODE_ENV === "development",
        });
        ```

        ```ts next.config.ts theme={"system"}
        const nextConfig = {
          images: {
            loader: "custom",
            loaderFile: "./lib/convertly-loader.ts",
          },
        };
        export default nextConfig;
        ```

        Add `NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE=assets-acme` to your `.env.local`. Create `site` as an origin source for your deployed app's public HTTPS URL. In production, `<Image src="/hero.jpg" width={1200} />` is rewritten to a Convertly URL for the deployed public asset: `/cdn/v1/{endpointNamespace}/o/site/hero.jpg?w=1200...`. During `next dev`, `localPassthrough` renders `/hero.jpg` directly because the public CDN cannot fetch `localhost`; use a preview deployment or HTTPS tunnel when you want to test the real optimized CDN URL before launch.
      </Tab>

      <Tab title="React">
        ```tsx theme={"system"}
        import { ConvertlyCdnProvider, ConvertlyImage } from "@convertly-sh/image/react";

        export default function App() {
          return (
            <ConvertlyCdnProvider namespace={process.env.NEXT_PUBLIC_CONVERTLY_CDN_NAMESPACE!}>
              <ConvertlyImage
                src="7c3f2a91-8b64-4d5e-a2f7-91c06b48de35"
                width={1200}
                height={800}
                alt="Hero image"
                sizes="(min-width: 768px) 50vw, 100vw"
              />
            </ConvertlyCdnProvider>
          );
        }
        ```
      </Tab>

      <Tab title="Vue">
        ```vue theme={"system"}
        <script setup>
        import { provideConvertlyCdn, ConvertlyImage } from "@convertly-sh/image/vue";

        provideConvertlyCdn({
          namespace: import.meta.env.PUBLIC_CONVERTLY_CDN_NAMESPACE,
        });
        </script>

        <template>
          <ConvertlyImage
            src="7c3f2a91-8b64-4d5e-a2f7-91c06b48de35"
            :width="1200"
            sizes="(min-width: 768px) 50vw, 100vw"
            alt="Hero image"
          />
        </template>
        ```
      </Tab>

      <Tab title="Astro">
        ```astro components/CdnImage.astro theme={"system"}
        ---
        import { createConvertlyCdn } from "@convertly-sh/image";

        const cdn = createConvertlyCdn({
          namespace: import.meta.env.PUBLIC_CONVERTLY_CDN_NAMESPACE,
        });

        const { fileId, width, sizes, alt } = Astro.props;
        const src = cdn.url(fileId, { w: width });
        const srcset = cdn.srcset(fileId, { widths: [400, 800, 1200, 1600] });
        ---

        <img {src} {srcset} {sizes} {alt} loading="lazy" decoding="async" />
        ```
      </Tab>

      <Tab title="Plain HTML">
        Origin-source image:

        ```html theme={"system"}
        <img
          src="https://cdn.convertly.sh/cdn/v1/assets-acme/o/site/hero.jpg?w=1200&format=auto"
          sizes="(min-width: 768px) 50vw, 100vw"
          alt="..."
          loading="lazy"
        />
        ```

        Convertly Storage image:

        ```html theme={"system"}
        <img
          src="https://cdn.convertly.sh/cdn/v1/assets-acme/{fileIdOrSlug}?w=1200&format=auto"
          sizes="(min-width: 768px) 50vw, 100vw"
          alt="..."
          loading="lazy"
        />
        ```
      </Tab>
    </Tabs>

    All adapters default to `format=auto` (AVIF/WebP/JPEG negotiation), `gravity=auto` (smart cropping), `q=auto` (content-aware quality), and `loading="lazy"`. You can override any of them per call.
  </Step>

  <Step title="Verify it's working">
    Load a deployed page that renders one of your CDN-backed images. In the browser DevTools Network tab, look for a request to `convertly.sh/cdn/v1/...`. If you are using `localPassthrough` in `next dev`, localhost will show the original `/hero.jpg` path; that is expected and is not the optimized CDN path because Convertly cannot fetch local project files. The deployed or tunneled page response should:

    * Be 200 OK
    * Have `Content-Type: image/webp` (or `image/avif` if your browser supports it)
    * Have `Cache-Control: public, max-age=31536000, immutable, stale-while-revalidate=86400`
    * Have an `X-Convertly-CDN-Used-Month` header showing your delivery counter (first requests that reach Convertly for that URL variant)

    Refresh the page. The image should now come from `disk cache` or `memory cache` in DevTools — no second network request.
  </Step>

  <Step title="(Optional) Add design-system presets">
    If you find yourself writing the same transform many times — say, every hero is 1920×720 — save it as a named preset and reference it by name:

    ```bash theme={"system"}
    curl -X POST "https://convertly.sh/api/cdn-presets" \
      -H "Authorization: Bearer $CONVERTLY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "hero",
        "params": { "w": 1920, "h": 720, "fit": "cover", "gravity": "auto", "q": "auto" }
      }'
    ```

    Now any URL with `?preset=hero` (or path `/p/hero`) gets those defaults. Override individual params on a per-URL basis when you need to — e.g. `?preset=hero&w=800`.

    You can also manage presets visually in **Settings → Image CDN → Transform presets**.
  </Step>
</Steps>

## What happens on the first request

When a browser fetches a CDN URL for the first time:

1. The request hits Convertly's edge cache. On a miss, Convertly loads the source (storage file, origin URL, or private bucket object).
2. Convertly applies the requested transforms (resize, format, smart crop, quality).
3. The result is cached with long-lived immutable headers. Popular variants are retained so repeat requests — including from other regions — often skip re-encoding.

A typical 7 MB source PNG becomes a \~200 KB WebP or AVIF. The first request for that URL may take around a second to encode; repeats are typically \~20 ms from edge cache.

## Video clips and poster frames

Stored video files use the same CDN endpoint and SDK. Add poster or transcode params to the URL; no separate video package is required:

```
https://cdn.convertly.sh/{endpointNamespace}/{videoId}?t=3.5&w=800&format=webp
https://cdn.convertly.sh/{endpointNamespace}/{videoId}?format=mp4&w=1280&so=0&du=30
```

See [Video transforms](/docs/image-cdn/transforms/video) for every video parameter.

## Troubleshooting

| Symptom                                      | Likely cause                                               | Fix                                                                                 |
| -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| CDN URL returns 404 / 502                    | No origin source, wrong slug, or file not public on origin | Add **Sources** entry; verify `https://your-site.com/hero.jpg` returns 200          |
| Next.js still shows `/hero.jpg` in dev       | `localPassthrough` is working as designed                  | Expected in `next dev`; test on preview deploy or disable passthrough with a tunnel |
| Installed SDK, nothing changed in production | Loader not wired, or origin slug mismatch                  | Check `next.config.ts` loader + `origin: "site"` matches dashboard slug             |
| UUID images work, `/public` paths don't      | Storage works; origin missing                              | Create origin for deployed site URL                                                 |
| Random external URL fails                    | Host not registered                                        | Add an origin source for that HTTPS host — no open fetch                            |

## Next steps

<Columns cols={2}>
  <Card title="Use your own hostname" icon="globe" href="/docs/image-cdn/custom-domain">
    Serve from `cdn.yourdomain.com` instead of `cdn.convertly.sh`. One CNAME — we handle SSL.
  </Card>

  <Card title="Every URL parameter" icon="crop" href="/docs/image-cdn/transforms">
    Width, height, quality, fit, gravity, format, text overlay, save-data quality.
  </Card>

  <Card title="Presets and signed URLs" icon="key" href="/docs/image-cdn/presets-and-signing">
    Named transformation bundles and HMAC-signed URLs for gated content.
  </Card>

  <Card title="Delivery, purging, security" icon="shield-halved" href="/docs/image-cdn/operations">
    Manage CDN endpoints and URL signing keys, invalidate cached URLs, replace files.
  </Card>

  <Card title="Framework SDK reference" icon="box" href="/docs/image-cdn/sdks">
    Full prop tables for React, Next.js, Vue, Astro, Solid, and Svelte.
  </Card>
</Columns>
