lukeshay / astro-aws

An Astro SSR adapter for AWS Lambda
https://www.astro-aws.org
MIT License
33 stars 9 forks source link

[Bug]: Using `Image` component and image server returns error #128

Open bnjns opened 7 months ago

bnjns commented 7 months ago

💻

Input code

Something like this:

---
import { Image } from 'astro:assets'
import exampleImg from '../assets/image.png'
---

<Image src={exampleImg} />

Current and expected behavior

During build the src attribute is correctly set to use the image server (eg, /_image?href=%2F_astro%2Fimage.Y0Z6qWya.png&f=webp), but when rendering the lambda returns a 500 error, rather than the image itself.

In the server logs, we see this error:

[ERROR] TypeError: Component.render is not a function
    at Object.renderToStaticMarkup (file:///var/task/entry.mjs:55:30)
    at renderFrameworkComponent (file:///var/task/entry.mjs:1554:68)
    at async renderComponent (file:///var/task/entry.mjs:1744:10)
    at async renderComponentToString (file:///var/task/entry.mjs:1787:28)
    at async renderPage (file:///var/task/entry.mjs:1950:17)
    at async lastNext (file:///var/task/entry.mjs:23261:25)
    at async callMiddleware (file:///var/task/entry.mjs:22622:10)
    at async _RenderContext.render (file:///var/task/entry.mjs:23284:22)
    at async _NodeApp.render (file:///var/task/entry.mjs:23777:18)
    at async handler2 (file:///var/task/entry.mjs:27026:22)

Environment

Possible solution

No response

Additional context

I originally wondered if this was an issue with using sharp or swoosh, so followed the docs to just use a passthrough service but that didn't seem to help.

For now, we're able to bypass this by using an img tag and referencing the src attribute:

---
import exampleImg from '../assets/image.png'
---

<img src={exampleImg.src} />
lukeshay commented 7 months ago

We do not support Sharp or Squoosh yet. Because of that, image optimization does not work. I will add this to the backlog.

Related #93 #94

bnjns commented 7 months ago

I was aware of those 2 issues, so I didn't expect it to work with sharp or swoosh, however I'm surprised it doesn't work with the passthrough image service as that doesn't/shouldn't do any image optimisation?

Admittedly, I know nothing about the internals of astro though!

lukeshay commented 7 months ago

It looks like we will have to implement ExternalImageService. I will have to do some research on how we should approach this.

bnjns commented 7 months ago

I've just realised that of course this doesn't work - the default image service sets the url to /_image?href=%2F_astro... which routes to the SSR server, but that doesn't have access to the underlying images as those are exported in the client directory during build and so uploaded to S3.

This probably just needs a custom image service which replaces the url with just what's used for the href query parameter - this will just match the CloudFront routing rule for the static objects in S3. This obviously wouldn't allow any optimisation because it's bypassing the SSR lambda, but that might be okay until this adapter supports sharp/swoosh?

I'll try to throw a PoC together to give this a try.

bnjns commented 7 months ago

I've managed to throw together a very barebones POC that works for us (stick this file in your astro root alongside your astro config file):

// image_service.ts
import type { ExternalImageService } from "astro";

const imageService: ExternalImageService<{}> = {
  getURL(options, imageConfig) {
    // Because this doesn't support transforming, we can discard anything that isn't the src attribute
    return typeof options.src === "object" ? options.src.src : options.src;
  },
  getHTMLAttributes(options, imageConfig) {
    const { src, format, quality, ...attributes } = options;
    return {
      ...attributes,
      loading: options.loading ?? "lazy",
      decoding: options.decoding ?? "async",
    };
  },
};

export default imageService;

Then updated our astro.config.mjs to use:

export default defineConfig({
  ...
  image: {
    service: {
      entrypoint: "./image_service",
    },
  },
})

Hopefully this helps for building an actual solution!