egeldenhuys / ghost-cloudflare-r2

Cloudflare R2 storage adapter for Ghost
MIT License
25 stars 3 forks source link

Issue with Image Sizing on Cloudflare R2 Adapter #2

Open minovc3 opened 8 months ago

minovc3 commented 8 months ago

Hello,

Great adapter! I'm really impressed of the features it has, but at the same time I've got stuck on one of them.

Here's one aspect that I was unable to get functioning or comprehend.

When employing local storage, images set to lazy load typically come from this path example.com/content/images/size/.

However, if you switch to using the cloudflare-r2 storage provider, and enable the RESPONSIVE_IMAGES feature, the images are not delivered from cdn.example.com/content/images/size/ as expected.

Despite this, the URLs for these images still point to cdn.example.com/content/images, which serves up the full-sized images, instead of directing to the specific size path. Without a specific URL prefix for resized images in the configuration, it's not clear how the system differentiates URLs for resized images from the original ones.

On the other hand, at least the images get sized in the R2 bucket

CleanShot 2023-11-07 at 21 24 00@2x

This is the configuration I'm using

"storage": {
        "active": "ghost-cloudflare-r2",
        "ghost-cloudflare-r2": {
          "GHOST_STORAGE_ADAPTER_R2_ENDPOINT":"https://r2.cloudflarestorage.com",
          "GHOST_STORAGE_ADAPTER_R2_ACCESS_KEY_ID": "secret",
          "GHOST_STORAGE_ADAPTER_R2_SECRET_ACCESS_KEY":"secret",
          "GHOST_STORAGE_ADAPTER_R2_BUCKET": "my-ghost-assets",
          "GHOST_STORAGE_ADAPTER_R2_DOMAIN": "https://cdn.example.com",
          "GHOST_STORAGE_ADAPTER_R2_IMAGES_URL_PREFIX": "/content/images/",
          "GHOST_STORAGE_ADAPTER_R2_RESIZE_WIDTHS": "300,600,1000,1600,2000",
          "GHOST_STORAGE_ADAPTER_R2_RESPONSIVE_IMAGES": "true"
        },
        "media": {
            "adapter": "ghost-cloudflare-r2",
            "storage_type_media": true
          },
        "files": {
            "adapter": "ghost-cloudflare-r2",
            "storage_type_files": true
        }

Here are two instances showcasing the same image: one using the adapter and the other using local storage.

###Images Loaded Using Cloudflare R2 Adapter 

<img class="post-hero__img lazyautosizes lazyloaded" data-srcset="https://cdn.example.com/content/images/2023/11/bla.jpg 300w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 600w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 1000w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 2000w" srcset="https://cdn.example.com/content/images/2023/11/bla.jpg 300w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 600w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 1000w,
                    https://cdn.example.com/content/images/2023/11/bla.jpg 2000w" data-sizes="auto" data-src="https://cdn.example.com/content/images/2023/11/bla-1.jpg" src="https://cdn.example.com/content/images/2023/11/bla-1.jpg" alt="testing cdn2" sizes="908px">

###Images Loaded Using Ghost local Storage 
<img class="post-hero__img lazyautosizes lazyloaded" data-srcset="/content/images/size/w300/2023/11/bla.jpg 300w,
                    /content/images/size/w600/2023/11/bla.jpg 600w,
                    /content/images/size/w1000/2023/11/bla.jpg 1000w,
                    /content/images/size/w2000/2023/11/bla.jpg 2000w" srcset="/content/images/size/w300/2023/11/bla.jpg 300w,
                    /content/images/size/w600/2023/11/bla.jpg 600w,
                    /content/images/size/w1000/2023/11/bla.jpg 1000w,
                    /content/images/size/w2000/2023/11/bla.jpg 2000w" data-sizes="auto" data-src="/content/images/size/w300/2023/11/bla.jpg" src="/content/images/size/w300/2023/11/bla.jpg" alt="local storage" sizes="908px">

Perhaps there's something I'm overlooking! I'd appreciate your feedback! Thank you. 🙏

egeldenhuys commented 8 months ago

Hi @minovc3, thank you for the detailed feedback on the adapter, much appreciated!

What version of Ghost are you using? Also, which theme are you using?

minovc3 commented 8 months ago

Thank you for your patience and quick response 🙏🏻 Alright. Currently, I'm using Ghost version 5.71.2 on Docker. My setup includes the Tuuli theme, here is the documentation.

Below is the snippet of code from this theme that manages the images.

 {{#if feature_image}}
          <figure class="post-hero__figure m-b-lg">
            <img
              class="lazyload post-hero__img"
              data-srcset="{{img_url feature_image size="s"}} 300w,
                      {{img_url feature_image size="m"}} 600w,
                      {{img_url feature_image size="l"}} 1000w,
                      {{img_url feature_image size="xl"}} 1500w"
              srcset="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
              data-sizes="auto"
              data-src="{{img_url feature_image size="s"}}"
              src="{{img_url feature_image size="xs"}}"
              alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"/>
            {{#if feature_image_caption}}
              <figcaption>{{feature_image_caption}}</figcaption>
            {{/if}}
          </figure>
        {{/if}}

Now, I believe I've made some progress. Look, I've noticed that If the URL that comes back is an r2 URL instead of a local {SITE_URL}/content/images URL, then unfortunately, responsive images won’t function.

So Here’s my approach:

I modified the GHOST_STORAGE_ADAPTER_R2_DOMAIN from https://cdn.example.com/ to the local {SITE_URL} blog.example.com.

Following that, I set up a redirect rule in Ghost as follows:

302:
  ^\/content\/images\/(.*)$: https://cdn.example.com/content/images/$1

I'm halfway there now.

All the featured images have been successfully redirected and are functioning, but I've hit a snag with the gallery images – none of them are redirecting.

So, as it stands, the featured images are optimized while using the redirect workaround, but the galleries and images within posts are not being redirected.

Here is an example:

This is a post card image with the R2 adapter:

<figure class="kg-card kg-image-card"><a data-no-swup="" data-fslightbox="" href="https://blog.example.com/content/images/2023/11/test.jpg" aria-label="Click for Lightbox"><img src="https://blog.example.com/content/images/2023/11/test.jpg" class="kg-image" alt="" loading="lazy" width="1500" height="1106"></a></figure>

This is a post card image with the local adapter:

<figure class="kg-card kg-image-card"><a data-no-swup="" data-fslightbox="" href="https://blog.example.com/content/images/2023/11/test.jpg" aria-label="Click for Lightbox"><img src="https://blog.example.com/content/images/2023/11/test.jpg" class="kg-image" alt="" loading="lazy" width="2000" height="1475" srcset="https://blog.example.com/content/images/size/w600/2023/11/test.jpg 600w, https://blog.example.com/content/images/size/w1000/2023/11/test.jpg 1000w, https://blog.example.com/content/images/size/w1600/2023/11/test.jpg 1600w, https://blog.example.com/content/images/size/w2400/2023/11/test.jpg 2400w" sizes="(min-width: 720px) 720px"></a></figure>

The redirect doesn't help on the image-card

Don't really know why!

fritz-fritz commented 1 month ago

Done a little digging into this. It seems as @minovc3 pointed out, Ghost will not correctly generate the srcset unless ghost detects the image source as being local.

See this thread on the forums https://forum.ghost.org/t/responsive-images-and-custom-storage-adapters/13183 the current codebase has changed since the thread but the logic still exists.

So in my case, I have changed my R2_DOMAIN from cdn.example.com to www.example.com to match the blog and in my cloudflare worker created a trigger route for www.example.com/content/images/ /content/media/ and /content/files. This effectively accomplishes the same as your redirect but at the edge.

What interests me is that he seems to have used Ghost's transform code to generate the resizes. THAT is much wanted and I am looking to see if I can implement as it seems Ghost is now writing the resized files to a different path:

<img srcset="/content/images/size/w160/format/webp/2024/06/default-feature-image-1.jpg 160w,
             /content/images/size/w320/format/webp/2024/06/default-feature-image-1.jpg 320w,
             /content/images/size/w600/format/webp/2024/06/default-feature-image-1.jpg 600w,
             /content/images/size/w960/format/webp/2024/06/default-feature-image-1.jpg 960w,
             /content/images/size/w1200/format/webp/2024/06/default-feature-image-1.jpg 1200w,
             /content/images/size/w2000/format/webp/2024/06/default-feature-image-1.jpg 2000w" sizes="320px"
        src="/content/images/size/w600/2024/06/default-feature-image-1.jpg" alt="Coming soon" loading="lazy">

Relevant Line Here: https://github.com/TryGhost/Ghost/blob/81b46c53ccc40a3805334fdb4850dc6bff507a33/ghost/core/core/frontend/utils/images.js#L99

So it seems Ghost now is converting the images to optimize for the web! Awesome! Let's see if we can import/integrate their functions and make the backend be really invisible!

fritz-fritz commented 1 month ago

Seems the image-transform code has moved but I found it in the TryGhost/SDK repo.

Useful function here unsafeResizeFromBuffer might be an efficient way to generate the resized images that Ghost expects and then send to the s3 client. It can be called with resizeFromBuffer. It can also transform the file type. So I know we can generate the right images... but I fear the difficult part is going to be getting them generated to the formats Ghost is specifying in the srcset. Somehow, Ghost is able to generate these on the fly so figuring out where/how that happens is my next step. But if that fails, we could just write some logic to convert to all supported formats... but that could make storage costs skyrocket potentially so I'd like to avoid

fritz-fritz commented 1 month ago

Just an update, I have a local branch I'm working on that successfully pulls the theme sizes and dynamically resizes and pushes via S3 to the correct paths for all formats (which is also dynamically grabbed from ghost's code). But unfortunately, it seems the image and gallery cards don't respect the theme sizes so I am looking now for where that is defined so that I can incorporate it. Once I have it all working right I'll send a pull request for review.