# \[Guide\] How to use responsive images with Cloudinary and Casper 3

**URL:** <https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592>\
**Category:** Using Ghost\
**Created:** [July 3, 2020, 4:50pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592 "2020-07-03T16:50:59Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![robhawkes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/robhawkes/32/8619_2.png) [@robhawkes](https://forum.ghost.org/u/robhawkes)\
**Post date:** [July 3, 2020, 4:50pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592/1 "2020-07-03T16:50:59Z")

</div>

Hey!

So I’ve been setting up my new Ghost-powered site (not public yet) and something I struggled with recently is how to keep responsive images while using [Cloudinary as a storage adapter](https://github.com/eexit/ghost-storage-cloudinary).

The main issue is that the storage adapter returns only a single URL for the image, which means you can only define a static set of transformations to be applied by Cloudinary (eg. `w_200`, or a named transform like `t_my_transform`). This means that you lose the ability to dynamically change the resolution of the image in your template through the Handlebars tags and `srcset` (eg. `{{img_url feature_image size="xl"}}` would return the same URL as `{{img_url feature_image size="s"}}`).

So how to work around this? Well you have two main options.

## Option 1: Use the Cloudinary Fetch API

The simplest option is to prepend your `{{img_url ...}}` tags with the Cloudinary Fetch API and whatever dynamic transforms you require.

```auto
https://res.cloudinary.com/demo/image/fetch/w_200/{{img_url feature_image}}

```

This works great, but it’s messy and you give up some functionality and caching becomes a little abstract.

## Option 2a: Create a custom Handlebars helper for Cloudinary images

Ideally, the best option would be to create a custom Handlebars helper for processing the Cloudinary image URL and automatically injecting dynamic transformation options.

```auto
{{cloudinary_img_url feature_image width="200"}}

```

Unfortunately, it’s currently not possible to extend Handlebars within Ghost without modifying the core Ghost codebase in a variety of places.

## Option 2b: Modify the `{{img_url ...}}` helper

While still requiring modification of the core files, this approach only requires changing a single file and doesn’t require much to be added. It also means you get to keep using `{{img_url ...}}` and it won’t impact any images that aren’t hosted on Cloudinary.

The first step is to modify [`/core/frontend/helpers/img_url.js`](https://github.com/TryGhost/Ghost/blob/master/core/frontend/helpers/img_url.js) and generally add the following:

```auto
// Override the external image check in imgUrl() and call a new function
const isInternalImage = detectInternalImage(requestedImageUrl);
if (!isInternalImage) {
  return getExternalImage(requestedImageUrl, options);
}

```

Add the new functions to check for Cloudinary and inject dynamic transforms:

```auto
function getExternalImage(requestedImageUrl, options) {
    if (detectCloudinaryImage) {
        return injectCloudinaryParameters(requestedImageUrl, options);
    }

    return requestedImageUrl;
}

function detectCloudinaryImage(requestedImageUrl) {
    return /cloudinary.com\/YOUR_CLOUDINARY_SITE/.test(requestedImageUrl);
}

function injectCloudinaryParameters(requestedImageUrl, options) {
    const parameters = options && options.hash && options.hash.cloudinary;

    if (!parameters) {
        return requestedImageUrl;
    }

    const defaultParam = 'YOUR_DEFAULT_CLOUDINARY_TRANSFORM';
    const newParams = [defaultParam, parameters].join('/');

    return requestedImageUrl.replace(defaultParam, newParams);
}

```

[Here’s an example Gist of the entire modified `img_url.js` file for reference](https://gist.github.com/robhawkes/80fd4c5f1b44985f8c209f5844ef1402).

Make sure to change `YOUR_CLOUDINARY_SITE` to whatever site/slug you use in your Cloudinary URLs. Also, you’ll need to change `YOUR_DEFAULT_CLOUDINARY_TRANSFORM` to whatever default transform you set [in the storage adapter](https://github.com/eexit/ghost-storage-cloudinary). For me I set `fetch.transformation` to a named transform (so paste that named transform, `t_something`), but by default you might be using `fetch.quality` set to `auto` so paste in `q_auto`.

After this you need to restart Ghost.

The last step is to update the Casper templates to use the new parameters for Cloudinary images:

```auto
<img
  srcset="{{img_url feature_image cloudinary="w_300"}} 300w,
          {{img_url feature_image cloudinary="w_600"}} 600w,
          {{img_url feature_image cloudinary="w_1000"}} 1000w,
          {{img_url feature_image cloudinary="w_2000"}} 2000w"
  sizes="(max-width: 800px) 400px,
      (max-width: 1170px) 1170px,
          2000px"
  src="{{img_url feature_image cloudinary="w_2000"}}"
  alt="{{title}}"
/>

```

And that’s it! It’s not simple by any means, but it works. 👌

---

<div class="post-metadata">

**Author:** ![robhawkes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/robhawkes/32/8619_2.png) [@robhawkes](https://forum.ghost.org/u/robhawkes)\
**Post date:** [July 4, 2020, 10:13pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592/2 "2020-07-04T22:13:31Z")

</div>

Since posting this I’ve release my site to the public, so you can see the results this approach in action:

> **[Spatial Awareness](https://spatialawareness.io/)**
>
> Maps and spatial goodies curated by Robin Hawkes

---

<div class="post-metadata">

**Author:** ![moeen](https://avatars.discourse-cdn.com/v4/letter/m/c89c15/32.png) [@moeen](https://forum.ghost.org/u/moeen)\
**Post date:** [July 18, 2020, 3:21pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592/3 "2020-07-18T15:21:07Z")

</div>

Thanks, but as this need a core file changed, this mean that if we update, this crash, that is right?

---

<div class="post-metadata">

**Author:** ![Defite](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/defite/32/14231_2.png) [@Defite](https://forum.ghost.org/u/Defite)\
**Post date:** [July 22, 2021, 3:30pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592/4 "2021-07-22T15:30:40Z")

</div>

Sure thing. You have to track changes on that files.

---

<div class="post-metadata">

**Author:** ![Defite](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/defite/32/14231_2.png) [@Defite](https://forum.ghost.org/u/Defite)\
**Post date:** [July 22, 2021, 3:37pm UTC](https://forum.ghost.org/t/guide-how-to-use-responsive-images-with-cloudinary-and-casper-3/15592/5 "2021-07-22T15:37:28Z")

</div>

I’m working (just finished) on another problem with responsive images via Cloudinary - images in post. I have to change some core files, because I use Ghost as headless cms and don’t want to parse and replace urls in my frontend.

First of all, I added few lines to my config.development.json

```auto
"imageOptimization": {
    "cloudinary": {
      "baseUrl": "https://res.cloudinary.com/my_name/image/fetch/f_auto,q_auto"
    }
  }

```

Then I changed `versions/4.9.4/node_modules/@tryghost/kg-default-cards/lib/utils/set-srcset-attribute.js`:

```auto
if (isLocalContentImage(image.src, options.siteUrl)) {
        const [, imagesPath, filename] = image.src.match(/(.*\/content\/images)\/(.*)/);
        const srcs = [];

        srcsetWidths.forEach((width) => {
            if (width === image.width) {
                // use original image path if width matches exactly (avoids 302s from size->original)
                srcs.push(`${image.src} ${width}w`);
            } else if (width <= image.width) {
                // avoid creating srcset sizes larger than intrinsic image width
                if (options.imageOptimization.cloudinary) {
                    srcs.push(`${options.imageOptimization.cloudinary.baseUrl},w_${width}/${imagesPath}/${filename} ${width}w`);
                } else {
                    srcs.push(`${imagesPath}/size/w${width}/${filename} ${width}w`);
                }
            }
        });

        if (srcs.length) {
            elem.setAttribute('srcset', srcs.join(', '));
        }
    }

```

The main part here is where I check for cloudinary object and prepend Ghost image url with it. This will output proper srcset attribute with cloudinary images.

Last step I did, I changed `versions/4.9.4/node_modules/@tryghost/kg-default-cards/lib/cards/image.js`:

```auto
const { cloudinary } = options.imageOptimization;
const payloadSrc = cloudinary ? `${cloudinary.baseUrl}/${payload.src}` : payload.src;
img.setAttribute('src', payloadSrc);

```

This will output src for all images inside `content` helper.

This works fine for me (until I hit `ghost update` obviously). I wonder, if there is better way to achieve what I’m doing?
