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.

1 Like

Can you please explain that second update in more detail?

STATIC_IMAGE_URL_PREFIX here: https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js
Comes from here: https://github.com/TryGhost/Ghost/blob/master/core/server/lib/url-utils/index.js
And is set here: https://github.com/TryGhost/Ghost-SDK/blob/master/packages/url-utils/lib/index.js#L38

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: https://ghost.org/docs/api/v3/handlebars-themes/responsive-images/

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: https://github.com/TryGhost/Ghost-Utils/tree/master/packages/image-transform

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: https://github.com/TryGhost/Ghost/blob/658a6dd284420d630903f4a96bce726a8de5bf26/core/frontend/helpers/img_url.js#L37

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 adapter.save() on its own? I’m not sure what would be best.

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

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: