Serverless-next.js: NextJS 10.0.0 <Image /> component causes 404

Created on 30 Oct 2020  路  14Comments  路  Source: serverless-nextjs/serverless-next.js

Describe the bug


I realise NextJS 10 is only a couple of days old, but thought I'd just mention this to make sure it's captured.
Using the new NextJS Component doesn't work.

Actual behavior


Using the Image component results in a 404 error from the server. It works in dev, but deploying to AWS doesn't.

Expected behavior


The image should return correctly.

Steps to reproduce

Screenshots/Code/Logs

Request URL: https://mywebsite.com/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2F9h...etc etc
Request Method: GET
Status Code: 404

Response Headers:
cache-control: public, max-age=0, s-maxage=2678400, must-revalidate
content-encoding: gzip
content-type: text/html
date: Fri, 30 Oct 2020 01:42:59 GMT
etag: W/"6805d78c611deceec9b5bd040ab84078"
last-modified: Fri, 30 Oct 2020 01:37:01 GMT
server: AmazonS3
status: 404
vary: Accept-Encoding
via: 1.1 2b782f5f082f9e98adf8c50f24b6bb6d.cloudfront.net (CloudFront)
x-amz-cf-id: XDhH5J8y7n052fhPQ6T9Rvh1yAptUwU9eh7sT1RQWbqBNNlyEGyuNw==
x-amz-cf-pop: HAM50-C3
x-cache: Error from cloudfront

Versions

enhancement release-1.19

Most helpful comment

I am hoping to hopefully get it out around Thanksgiving (Nov 26).

The code is there from Next.js - just a matter of porting it and then optimizing it for a serverless environment as mentioned above using Rollup.js

All 14 comments

Thanks for reporting - we'll prioritize the Next.js 10 features right after 1.18. Just want to address a couple small things before releasing 1.18.

Investigating this now. It looks like there is a bit complex logic since it's on-demand optimization: https://github.com/vercel/next.js/blob/48acc479f3befb70de800392315831ed7defa4d8/packages/next/next-server/server/image-optimizer.ts

Will work on porting & optimizing this code for this component.

It does add some additional image transformation library, so we would probably want to also use Rollup.js to optimize this out to reduce handler size, if one doesn't use image components.

Just wondering if there is a rough time span for releasing the next 10 image component support?

Bigs props to building/maintaining this package, it's awesome 馃憤

I am hoping to hopefully get it out around Thanksgiving (Nov 26).

The code is there from Next.js - just a matter of porting it and then optimizing it for a serverless environment as mentioned above using Rollup.js

@dphang what's the ETA on that one? I'd really love to use it anytime soon. Is there anything I could help with?

@confix sorry for the delay, had been caught up in some personal stuff over the weekend. I'll try to get a PR out by this week.

Have created a draft PR above - still working on updating unit tests to maintain the code coverage, and cleaning up the code a little, though e2e tests have been added.

Feel free to pull that branch and try it out in the meanwhile and let me know if you find any bugs (note, you will need Node 12.x, set input imageOptimizer=true and also forward Accept headers on cloudfront.defaults input to properly return modern image formats e.g WEBP).

@dphang does this mean that it will affect cold start for the lambdas ? Just wondering what effects about increasing the bundle size so much 馃憤

Does this affect directly the performances of the lambdas that are already in place? Or it is non dependent (e.g. in a separate lambda).

Also, for previous RFC implementations there are charts to clarify the approach & solutions but here there is less documentations. It could help to add protocol charts for this feature too. What do you think ?

Good question! So from my experience adding code to the bundle has very little effect to cold start performance, it is mostly loading that code into the Node.js container that is slow. In the PR, none of the image optimization code is loaded on the critical path, they are dynamically loaded when an image request comes in. Though I have put it in default-lambda for now, but it can be easily refactored into its own Lambda. With +7 MB (due to the underlying sharp image processing binaries) you may see a few milliseconds added to cold start time to download the code (but not loaded until actually needed).

And adding 7 MB for the libraries required for image optimization may have other issues, namely that it may not fit into the 50 MB Lambda limit for some users. So maybe a new cache behavior (_next/image) and handler (image-lambda) should be created with a new Lambda instead? Image optimization is heavy so I'm probably leaning to its own lambda to keep other code paths unimpacted by cold starts / code size issues.

And using a separate Lambda adds a few other advantages:

  • Currently I copy a set of node_modules for sharp that's pre-built for Lambda, but it may interfere with the serverless-trace target if there are the same modules, since it also uses a node_modules directory, as opposed to bundling the requirements into each page. By using a separate Lambda, we can avoid this.
  • You can optionally increase the memory to the highest for images to also improve the CPU performance, since it is computation heavy (as recommended by the Sharp team: https://sharp.pixelplumbing.com/install#aws-lambda) while using a lower memory size for the API and default lambda). These images will still be cached, so you only pay for the initial request to a CloudFront edge node.
  • Easier to maintain and unit test.

@danielcondemarin let me know your thoughts.

Good question! So from my experience adding code to the bundle has very little effect to cold start performance, it is mostly loading that code into the Node.js container that is slow. In the PR, none of the image optimization code is loaded on the critical path, they are dynamically loaded when an image request comes in. Though I have put it in default-lambda for now, but it can be easily refactored into its own Lambda. With +7 MB (due to the underlying sharp image processing binaries) you may see a few milliseconds added to cold start time to download the code (but not loaded until actually needed).

And adding 7 MB for the libraries required for image optimization may have other issues, namely that it may not fit into the 50 MB Lambda limit for some users. So maybe a new cache behavior (_next/image) and handler (image-lambda) should be created with a new Lambda instead? Image optimization is heavy so I'm probably leaning to its own lambda to keep other code paths unimpacted by cold starts / code size issues.

And using a separate Lambda adds a few other advantages:

  • Currently I copy a set of node_modules for sharp that's pre-built for Lambda, but it may interfere with the serverless-trace target if there are the same modules, since it also uses a node_modules directory, as opposed to bundling the requirements into each page. By using a separate Lambda, we can avoid this.
  • You can optionally increase the memory to the highest for images to also improve the CPU performance, since it is computation heavy (as recommended by the Sharp team: https://sharp.pixelplumbing.com/install#aws-lambda) while using a lower memory size for the API and default lambda). These images will still be cached, so you only pay for the initial request to a CloudFront edge node.
  • Easier to maintain and unit test.

@danielcondemarin let me know your thoughts.

I'd also be inclined to having image optimisation in a separate Lambda. Whenever possible I think we should aim to keep the default handler lightweight as that's where most traffic is routed through.
The only question I have is can we guarantee that Next will request the image from a URL that starts with _next/image/* ? I'm not super familiar with the new feature but seems like it doesn't support custom loaders - https://nextjs.org/docs/basic-features/image-optimization#loader.

The default Image component would use the loader on the same domain (the /_next/image paths) which is working in my draft PR.

It also supports a few other ones (Akamai etc) but not a custom loader i.e one of your own.

It also seems you can also use one of the built-in ones and just mimick its API as well as a way to support a custom loader. Proper custom loader support may also be coming soon. See: https://github.com/vercel/next.js/discussions/18450

The default Image component would use the loader on the same domain (the /_next/image paths) which is working in my draft PR.

It also supports a few other ones (Akamai etc) but not a custom loader i.e one of your own.

It also seems you can also use one of the built-in ones and just mimick its API as well as a way to support a custom loader. Proper custom loader support may also be coming soon. See: vercel/next.js#18450

Ah thanks for clarifying. In that case a custom cache behaviour for _next/image/* and its own Lambda sound like a nice approach! In future if custom loaders are available we could potentially add one for us but at this point I don't see much benefit in that if Next Server is going to keep _next/image in the URL.

@danielcondemarin thanks for confirming, I will refactor and I think this new image Lambda can be enabled by default as long as user is on Next.js 10 (which we can detect by the presence of image-manifest.json) since it's a separate path.

As for the custom loader, once supported I think we don't have to do anything, it would be the client-side component pointing to a different URL (Akamai, custom loader, etc.)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dhmacs picture dhmacs  路  31Comments

tyler-ground picture tyler-ground  路  15Comments

Meemaw picture Meemaw  路  23Comments

anialamo picture anialamo  路  14Comments

asterikx picture asterikx  路  42Comments