Responsive images and custom storage adapters

With an external storage adapters Ghost no longer creates resized images for use with srcsets. Is there a way to implement this with the storage adapters?

I poked around in the source and it looked like the lack of a .saveRaw() method caused it to skip resizing so I’ve added an appropriate .saveRaw() but didn’t have any luck.

Any other tips?

Update: Does it only generate the images when the Ghost engine itself receives an image request with a size? Seems that way.

Update2: This is tough. It also seems like the generated path has a hardcoded ‘/content/images’ from url tools.


Can you please explain that second update in more detail?

Comes from here:
And is set here:

As staticImageUrlPrefix: 'content/images'
It took me a long time to find that.

Now I have a Google Load Balancer with a content/images/* prefix directed to the bucket from the domain to force Ghost to prove srcsets correctly (it is specifically coded to only provide them for “internal” images).

I’m not sure I follow. Are you saying you’ve fixed it or still got a problem?

What exactly are you trying to achieve and with what adapter?

The goal is to be able to use the google storage adapter with srcsets.

Step 1 was to get Ghost to generate the srcsets at all (it is coded not to if the adapter is external, but the only way that it checks is by host and path). It generates the srcsets now.

Step 2 is to patch up the adapter to generate images for every size in the theme as seen here: How to use responsive images in Ghost themes

I’m on step 2. 50% fixed?

So you’re trying to get your adapter to generate image sizes, rather than proxying back through Ghost?

I guess that makes sense :thinking:

Might be helpful to know Ghost’s image transform code is in a shared package here:

I’m wondering what we could do to make this easier for adapters though…

That’s the goal. When using the Google Storage Adapter to take advantage of a GCP Bucket Ghost will never even see the image requests (as intended) - but it looks like it generates the resized images only on request. The adapter has to generate them. Then you have to trick Ghost into producing srcsets for the image tags because it’s logic locked to not produce them for external hosts.

It’s kind of painful.

You can see the logic here for internal vs external images: Ghost/img_url.js at 658a6dd284420d630903f4a96bce726a8de5bf26 · TryGhost/Ghost · GitHub

It seems like this should just be a toggle somewhere.

The image resizing stuff was layered on after the adapter stuff - and we didn’t want to break existing adapters, hence the weird saveRaw function.

There’s def still issues and room for improvement so I’m all ears. If you can share the workarounds you’re having to use that would be cool.

The first work-around is the load balancer work-around mentioned. A google load balancer remaps content/images/* → bucket. This tricks Ghost into providing the srcsets correctly in the frontend helpers because it thinks https://MYSITE/content/images/* is being served by Ghost (but it isn’t).

An assetPath config item was added to the ghost-google-cloud-storage-new adapter to make it easier to tweak the path.

This is the fork of the ghost-google-cloud-storage-new adapter I’m working in

Maybe a toggle for the srcset helpers in the templates to force them on? Once forced on, maybe the Ghost admin upload could get the sizes from the theme and use the on its own? I’m not sure what would be best.

Small side note: I am not the greatest node developer… ;)

I got it working! :partying_face:

Uploaded images go through the Ghost image processor/Sharp and are sent to the bucket with all of the sizes specified in the package.json of the current theme. All images are served directly from the bucket. Images appear in the theme with the appropriate srcsets.

I have this really nasty chunk of synchronous code to sort out later:

if(!targetFilename.includes('_o.')) {
    var data = fs.readFileSync(image.path);
    Object.keys(imageDimensions).map(imageDimension => {
        imageTransform.resizeFromBuffer(data, imageDimensions[imageDimension]).then((transformed) => {
            this.saveRaw(transformed, assetPath + 'size/' + imageDimension + '/' + targetFilenameOut);

But for now it does exactly what I want. I can use the Ghost API freely, the Ghost UI with responsive images, and all the responsive images (with the API) in a separate front end. :slight_smile:

1 Like

hey @elijahsgh and @Hannah how you got this custom storage adapter to set srcset for all different sizes ?
I followed your code and become able to generate images of all different sizes
but can not make any use of it since srcset has not changes yet

Please help

Hello Nitish.
Unfortunately I haven’t tried this custom storage adapter with the newer versions of ghost. It may have changed.

In previous versions there is specifically a chunk of code that is “hardcoded” to guess if media is coming from ghost. If ghost believes the media is external it will not provide the srcsets. If it believes it is serving the media then it will.

@elijahsgh were you ever able to alter srcset ?

For newly created/updated posts you should see it using srcset if the storage adapter presents images as being local (url matches SITE_URL/content/images) and has the saveRaw method.

If you’re referring to old/existing posts then nothing will change automatically, the html for a post is generated on save and is static. To update older posts to have srcsets you’d need to make a change to the post’s content and save, or force a re-render through the API (there’s an example API script to force-render all posts here).

@Kevin I just want to know is it possible to save images (on external store like S3 ) and be able to use responsive images (fetch from s3) as well

1 Like
here in this code I have saveRaw() function which saves responsive images to s3 and returns the url,
Still I see only identical image_urls in srcset

If the returned URL is an S3 url rather than a local {SITE_URL}/content/images URL then no, responsive images won’t work.


then what is the solution?
how I can achieve my goal (using s3 as storage and also getting responsive images )
like if can work with any other external store ,using any plugins like cloudinary.

goal ==> we want to store our image externally

1 Like

You’re on the right track. See Kevin’s post for a better explanation for what I meant by “hardcoded chunk of code” where “ghost thinks it is serving the images”.

Kevin specifically mentioned {SITE_URL}/content/images which is what I did with GCP. Basically you need a url matcher that matches that path and serves from the bucket. When Ghost generates a template it will think that it is serving the images. If your external storage does not match the {SITE_URL}/content/images pattern ghost will never generate srcsets.

I haven’t tried any of this with version 4 but if Kevin says that’s how it works it probably still works that way. :+1:

This is what it looks like in GCP.
Screenshot from 2021-05-22 00-24-43