cyrilwanner / next-optimized-images

🌅 next-optimized-images automatically optimizes images used in next.js projects (jpeg, png, svg, webp and gif).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Version 3

cyrilwanner opened this issue · comments

Background

This project started out as a small package. Over time, more and more features were added and it now provides many different things and is used by many developers. But I'm not 100% happy with the current version for the following reasons:

  • it depends on many different webpack loaders which has led to some workarounds and many features cannot be combined because the loaders don't work well with each other. See #84, #91 for examples.
  • those loaders also depend on other packages which have also introduced errors/bugs where we don't have the possibility to fix them directly (as in #75).
  • while this package grew, the installation and configuration became more and more complex and I think there is some space for improvement here.

Idea

To make this package robust for the future and easy to maintain, I think the only way is to fix those issues. And this probably requires a rewrite of the whole package.

It can be broken down into multiple parts:

Webpack loaders

This is the most important part of this package and is currently out of our control. To support use-cases such as mentioned in #84 or #91 and so we are able to provide a stable plugin, I think the only way is to write an own webpack loader which does the image optimizations. That would have the following advantages:

  • all features work together (e.g. you can resize a jpg, convert it to webp and generate a lqip all at the same time, which is not possible right now)
  • we have more control over the libraries used for image optimization (that part would of course still be a 3rd party library)
  • it would be published as a separate loader and could also be used outside of a next.js project

Next.js plugin

This plugin would configure the independent webpack loader for easy use with next.js. It should also support the upcoming next.js plugin system for an easy installation.

Support run-time optimization

One of the most requested features was to be able to optimize images during run-time (e.g. from an API) and not only local images during build-time (see #104, #77, #74). With the API routes introduced in next.js 9, I think it should be possible to provide an easy-to-use service for that. It obviously can't relay on webpack, but the code used in the webpack loader should be reusable.
That would have the following advantages:

  • it is possible to optimize images returned from an API/3rd party service
  • they would have exactly the same optimization options and query strings so you can use them exactly the same as a local image

But this feature would need to be defined more detailed (e.g. it would need a store, like S3, so images are not optimized on every request, and this API would be best located behind a CDN, etc.).

Provide image components

Creating an image component is not easy, especially if you want to support all features of this plugin (webp fallback, lqip, srcset, ...). And you still have to think about what happens when JS is disabled (while lazy loading), how it has to be optimized for screen readers and search engines and so on. So if you want your images to be working well for everyone, it is not just <img src={require('./image.jpg')} />, there is a lot more work to do and you don't want to do this again in every project.

So I'm thinking about providing an image component that will work well with this plugin and already does most of the things for you (non-JS fallback, a11y, lazy loading with fallbacks, ...).

It should support the following things:

Additionally, it would be nice to provide a babel plugin which makes it even easier to use this component.
So instead of this:

<Img
  src={require('./image.jpg')}
  sizes={{
    100: require('./image.jpg?resize&size=100'),
    300: require('./image.jpg?resize&size=300'),
    600: require('./image.jpg?resize&size=600')
  }}
  lqip={require('./image.jpg?lqip')}
/>

You would only use it like this and the babel plugin will transform it to the way more complex way above:

<Img src={require('./image.jpg')} sizes={[100, 300, 600]} lqip />

Feedback wanted

I am of course open to any feedback on any part of this idea. Also, if you have ideas of new features, now would be the time as they could be included from the beginning of the new version.

I am not quite sure on which image optimization library this should be based on. Right now, it is mainly imagemin but I am not really happy with it since it is not easy to install, especially in some CI/CD environments since it requires specific binaries to be installed on the system.
So I was thinking about using sharp as the main optimization library as it is a lot easier to install, has a great node API and is still quite fast compared to other node libraries.

Feedback on this would be very appreciated, especially:

  • has someone made experiences with both libraries? (regarding speed, simplicity, ...)
  • would you switch to another library?
  • if the decision falls for sharp, should imagemin (or others) also be additionally supported?

Is there an ETA?

No, development has started but is in the early stages. The features mentioned above are quite complex and require a lot of work, so it will probably take some weeks. But I will update this issue when progress is made and when a canary version is available.

Could i suggest to use wasm for the optimization part. Squoosh uses wasm to compress images, for example this lib

That's a good idea @AleVul, thanks! That would solve the problem with native binaries needed. I'll definitely check this option out 👍

Sounds awesome @cyrilwanner, are you still pursuing this?

I think this project is important and would love to see it flourish. I have no experience with Webpack stuff, but let me know if you need help with any laborious tasks or more general JS/TS parts of the library.

commented

Eagerly awaiting this! 😄

Remote image optimization would be amazing. 🎉

I'm sorry for the long silence on this issue. I just didn't have much time to work on this over the last few months, unfortunately.
But yes, all this is still planned and will come.
Last week, I started to work on this issue again so I'll hopefully be able to share some progress soon.

Currently, I'm working on compiling the different image optimization tools into WebAssembly so we are able to make the whole installation (especially in a CI/CD environment) a lot easier and more consistent. Squoosh, unfortunately, doesn't provide their compiled WASM binaries as packages, but I'm slowly getting there.
That's probably the biggest and most time-consuming part. So once that is done, the other features are way easier to implement.

Super cool Cyril, best of luck! 💪

I saw your message on the Next.js discussions board and posted a message in response to another user's questions with how I did something like this. I'd be happy to help out or give a few pointers with how I implemented something like this!

Hi @jasonsilberman and thank you very much!
I'm currently still on the foundation for all those new features, but I'll come back to you when it is ready and the remote image optimization can get implemented as I saw in your comment that you already did some work in that area.

But I already have a few initial questions, if you would like to give some insights.
It looks like the images get resized during upload and not on-the-fly when they get requested, is that correct? Also, did you deploy this into a cloud/serverless environment? If so, did you encounter any issues with sharp? Or any other things we should already consider or think about when planning/implementing this feature?
Thanks for your help!

Hey, really looking forward to the new Version! In the meantime I had the need for an Image Component by myself so I created one and shared it here:
https://github.com/tilman/next-optimized-image-component

It's still WIP but supports:

  • lqip image placeholder, blurred with SVG filter for best compatibility
  • blur radius adapts automatically based on the image size. Because we can't determine displayed image size in SSR, it is animated to this radius from a default radius of 40px
  • If the IntersectionObserver API is available, it only starts loading the image if it is near the viewport
  • use all height/width combinations for the Img component and the component will take care about placing the lqip exactly over the image
  • use webp as picture source by default with fallback for older browsers
  • also supports a no javascript fallback

Since you wrote, you want to provide an Image Components as well, I thought it's maybe interesting for you.

Feedback on provided component:

Instead of using babel transform, we can use gatsby-image strategy. Something like this:

class Lquip {
  imgSrc: string;
  blur: number;
}
class LquipColor {
  backgroundColor: string;
}
class Trace {
  imgSrc: string;
}
interface ResponsivePictureElement extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
  resources: {
    sources: DetailedHTMLProps<SourceHTMLAttributes<HTMLSourceElement>, HTMLSourceElement>[]
    fallbackSrc: string;
    placeholder?: Lquip | LquipColor | Trace;
    intrinsicWidth: number;
    intrinsicHeight: number
  }
}

export function Picture(props: ResponsivePictureElement) {
  const { resources: { sources, fallbackSrc, placeholder, intrinsicWidth, intrinsicHeight } } = props;
  let { style } = props
  if (placeholder instanceof Lquip) {
    // TODO can `filter` be applied to `backgroundImage` ?
    style = { ...style, backgroundImage: `url('${placeholder.imgSrc}')`, filter: `blur(${placeholder.blur}px)` }
  } else if (placeholder instanceof LquipColor) {
    style = { ...style, backgroundColor: placeholder.backgroundColor }
  } else if (placeholder instanceof Lquip) {
    style = { ...style, backgroundImage: `url('${placeholder.imgSrc}')` }
  }
  style = { ...style, height: intrinsicHeight, width: intrinsicWidth }
  return <picture {...props} style={style}>
    {sources.map((r) => <source
      srcSet={r.srcSet}
      media={r.media}
      sizes={r.sizes}
      type={r.type}
      {...r}
    />)}
    <img src={fallbackSrc} />
  </picture>
}

usage

<Picture resources={require('./myimage.png')} />
<Picture resources={require('./myimage.png?trace&resize&size[]=300&webp')} />

The 2 above can be equivalent with preset in the config.

WDYT?

Hi @jasonsilberman and thank you very much!
I'm currently still on the foundation for all those new features, but I'll come back to you when it is ready and the remote image optimization can get implemented as I saw in your comment that you already did some work in that area.

But I already have a few initial questions, if you would like to give some insights.
It looks like the images get resized during upload and not on-the-fly when they get requested, is that correct? Also, did you deploy this into a cloud/serverless environment? If so, did you encounter any issues with sharp? Or any other things we should already consider or think about when planning/implementing this feature?
Thanks for your help!

You are right, I am processing images on upload and not on the fly. I did this because we host the images on Google Storage (with a CDN in front) and so we could not easily do resizing on the fly. Additionally, this is for a static site and we know all of the images dimensions ahead of time, so we could pre-cut all of the exact images we needed.

We have deployed our image pipeline to a serverless environment, Vercel. I have not experienced any issues with using Sharp on Vercel's platform as of yet. The one thing to be consider is that you can only write to a /tmp folder on Vercel, and so you could not save images to disk. We use the readStream and writeStream APIs so this was not an issue for us.

Hope this helps.


Some other thoughts on the plugin in general:

  • I think it would be important that you can download/upload images to S3 or other cloud providers. As this would allow images to be served over CDN, or perhaps use already uploaded user-content as your image.
  • I appreciate that you are making sure to take into considerations the accessibility, performance, and SEO implications of this plugin. I think this is also important for today's websites.
  • Lastly, while Gatsby's tools only work at build-time because of the nature of Gatsby, it would be great if we could find a way to make this work on both static and dynamic sites. I agree that it seems API Routes (for serverless deployments) or some middleware that could be used with custom servers would be ideal!

Anyway, I'm excited about the development of this plugin!

I surprised Vercel hasn't built an image component yet. They have ones for head and link. 🤔

+1, a nextJS version of gatsby-image would be amazing ✨

commented

Any progress on this?

commented

@cyrilwanner next-optimized-images has been an absolute life-saver, so thanks for the project.

FWIW, I recently found next-img, a somewhat similar project by @kidkarkolis. It offers a few unique things like a build cache; you might want to check it out for some inspiration.

@paambaati glad to hear that!

I've seen that package too. And as a small update to the current state: I'm actively working on the version 3 and the first canary version is just a few days away.
(I couldn't invest too much time recently since I have university exams over the next two weeks, but I think I should still get the canary version out this weekend. And after those two weeks, I'll work full-time on this package until everything is ready and released)
And I can just say that all the features that package provides (and even more) are already done and will be available in the canary version. The build cache (and so way faster dev builds with optimized images) is just one of them.

I'll of course post here once the canary version is published to npm.

(And I've split this project into multiple modular packages, like one for the WebAssembly codecs, one for the webpack loader, one for the components and this which binds all together, so it could basically also be used outside of a next.js project. That's the reason why you don't see much going in in this repository. The other repositories will of course also made public when they are ready, but they are currently lacking a bit of documentation)

It was a lot of work and there is still a lot to do, but the first canary version is now available 🎉

Not all planned features are already available. The remote image optimization & lazy loading components (like blur-up) are missing. But they are still planned and will get added soon to the canary version.
Other than that, I think most of the planned features are now done and stable.

Some of the new features are:

  • Image optimization is performed either in node or WebAssembly - there is no need for imagemin plugins anymore and so no native binaries are required (= works in every environment out of the box)
  • Build cache for images - results in way faster builds and images are also optimized in the dev environment by default
  • Image components are provided for even easier use - there shouldn't be a need to use query params anymore normally (a babel plugin does that now for you)
  • images.config.js for global configuration and definition and re-use of image types and their options
  • Full typescript support thanks to the provided image components
  • Query params can be chained now - for example ?webp&width=400
  • ...and more. Read the readme file for an overview of all features.

I suggest you check out the readme on the canary branch for more details.


Any feedback, further ideas, or bug reports are of course very much appreciated and will help to bring the canary version to the @latest tag faster. The babel plugin for the image components probably doesn't recognize 100% of all use-cases, so if you find any, please share them if possible.

I would be very happy if some of you could test the canary version and report back if something is not working. Or if some of you have open-source projects using this package, it would already help if you can share them with me so I can see if there are some use-cases I did not think about and try it myself to update next-optimized-images to the canary version.

Also, if you find anything unclear in the readme or upgrade guide, don't hesitate to ask. It was already late when I wrote there so it is possible that not everything is 100% is explained well enough.


If you want to update this plugin in an existing project, please read the upgrading guide guide as some breaking changes were introduced.

Also, not yet all features previously available are already in the canary version. If your project depends on one of the following features, you may have to wait a few more days until they were ported into the new version:

  • Gif optimization (gif images can be used normally, they just don't get optimized)
  • ?trace query param
  • ?sprite query param

And thank you all for your continued support and feedback. I understand that it took a long time to get to this point, but I'm very happy with the result and think that it was worth it. And the remaining todos won't take that long anymore.

Great work @cyrilwanner, I've given v3 a test and will look out for issues and reference them here. Might be a better way to track the v3 issues though.

#164 (having trouble building).

The babel plugin does not currently recognize styled components like the one below:

import styled from '@emotion/styled';

S.Image = styled(Img)({
  width: '100%'
});

Thank you for already testing it!

I've given v3 a test and will look out for issues and reference them here. Might be a better way to track the v3 issues though.

Yes I agree, that may be the better way to track issues 👍

The babel plugin does not currently recognize styled components like the one below:

import styled from '@emotion/styled';

S.Image = styled(Img)({
  width: '100%'
});

That is something I completely forgot about as I'm not using that in my projects. Perfect that you could test that, thank you. I'll of course add support for styled components.

Hey, really looking forward to the new Version! In the meantime I had the need for an Image Component by myself so I created one and shared it here:
https://github.com/tilman/next-optimized-image-component

It's still WIP but supports:

  • lqip image placeholder, blurred with SVG filter for best compatibility
  • blur radius adapts automatically based on the image size. Because we can't determine displayed image size in SSR, it is animated to this radius from a default radius of 40px
  • If the IntersectionObserver API is available, it only starts loading the image if it is near the viewport
  • use all height/width combinations for the Img component and the component will take care about placing the lqip exactly over the image
  • use webp as picture source by default with fallback for older browsers
  • also supports a no javascript fallback

Since you wrote, you want to provide an Image Components as well, I thought it's maybe interesting for you.

I have a bug in component =( if I use Img component in div with onClick={...}

commented

@cyrilwanner 👋 Just checking in, and I wanted to bring to your notice the impending release of Webpack 5. As next-optimized-images relies heavily on Webpack, I wanted to understand how much effort it would take for v3 to support Webpack 5. Next.js's Webpack 5 compatibility is also close to complete.

The babel plugin does not currently recognize styled components like the one below:

v3.0.0-canary.1 should now support styled-components. It was a bit trickier to implement than expected, so if I didn't catch all use-cases, please just share the code which is not working and I can quickly add support for it.

@cyrilwanner 👋 Just checking in, and I wanted to bring to your notice the impending release of Webpack 5. As next-optimized-images relies heavily on Webpack, I wanted to understand how much effort it would take for v3 to support Webpack 5. Next.js's Webpack 5 compatibility is also close to complete.

Thank you for the hint! Only very few lines of code actually depend on webpack in version 3. I just took a look at the changelog and I think that the changes do not affect this plugin. But I will, of course, verify it and make changes if necessary.

commented

v3.0.0-canary.1 should now support styled-components.

@cyrilwanner Does it work with other CSS-in-JS solutions like emotion and treat?

@cyrilwanner Does it work with other CSS-in-JS solutions like emotion and treat?

Currently, extending the provided components only works with styled-components and @emotion/styled. Unfortunately, it does not work out of the box for all CSS-in-JS solutions since they all have a different syntax. But when someone requests support for another CSS-in-JS library, I will of course do that. It shouldn't be much work to extend support for other solutions. I'll also update the error message returned in that case mentioning this.

And please note that this only applies to specifically extending the Img/Svg component (e.g. with const MyImg = styled(Img)(...);). All components of course also support the className property, so all libraries returning a class name are also supported (according to the treat example, you would use <Img className={styles.button} />). And if you just want to set a background-image property in JS, no special supports is needed as well.

In addition to setting the MozjpegOptions/WebpOptions/etc. in next.config.js, could it be made possible to overwrite them for individual images in the query params and Img component props?

For example, I may want to set palette to true for a specific PNG image, or I may want my hero images to be a higher JPEG/WebP quality than the default setting, or have different quality settings for images with text, etc.

This could be pretty powerful when combined with the types in images.config.js, eg. creating different reusable types for photo, hero, thumbnail, icon, screenshot, etc. with appropriate compression settings.

In addition to setting the MozjpegOptions/WebpOptions/etc. in next.config.js, could it be made possible to overwrite them for individual images in the query params and Img component props?

For example, I may want to set palette to true for a specific PNG image, or I may want my hero images to be a higher JPEG/WebP quality than the default setting, or have different quality settings for images with text, etc.

This could be pretty powerful when combined with the types in images.config.js, eg. creating different reusable types for photo, hero, thumbnail, icon, screenshot, etc. with appropriate compression settings.

It is currently not supported, but your use-case makes perfect sense and I'll add this in one of the next canary versions. Thank you for the feedback 👍

@cyrilwanner I had recently got up and running on the stable version, but just switched to try canary and it is a lot easier to get going without dealing with the various plugins and their libraries—great job!

I have some JPEGs I’m displaying with Img set to various sizes and densities. With WebP enabled this is producing a <picture> with several <source> elements for the JPEG versions and same again for the WebP versions.

For my use case I would be fine without the various JPEG sizes, and if your browser is in the shrinking group that supports <picture> but not WebP I would be happy to just have the single JPEG fallback in the <img src="…">.

Do you think this is an option that could be supported, especially now Safari is going to support WebP. Perhaps opt-in with webp="only" or webponly or a similar setting?

@davecardwell thank you for the feedback!

Yes, I was also thinking about that. The main reason I didn't do it yet was Safari not supporting WebP. And especially on mobile, Safari has a large market share and mobile devices are probably the main reason why you would want multiple sizes.

Since it is going to support it in the next version, I'll add such a setting (probably with webp="only", I like that suggestion) which could of course also be set globally in the images.config.js file.

Just be aware that if you are building a public website and your target audience are mobile users, you'll probably still want the jpeg sizes for now as WebP support in Safari is currently only in the Technology Preview version and until the majority of iOS users have updated their OS, it can still take 1-2 years.. But I hope that I'll be able to make this setting as default at one day :)

@cyrilwanner giving Canary a run through and it's working great for me right now. Thank you for doing this!

Are there any plans for forcing gray scale?

commented

@cyrilwanner This is a long shot, but it would be amazing if next-optimized-images is the first library to support .avif images.

From https://reachlightspeed.com/blog/using-the-new-high-performance-avif-image-format-on-the-web-today/

It's basically a super-compressed image type. Netflix has already considered .avif superior to the JPEG, PNG, and even the newer WebP image formats for its image quality to compressed file size ratio.

AVIF is supposed to land in Chrome 85 and Firefox 80 on August 25, 2020

I did some digging and I found a few library that have AVIF-related functionality —

  1. https://www.npmjs.com/package/@saschazar/wasm-avif - A WASM-based AVIF encoder/decoder.
  2. https://github.com/Kagami/avif.js - A browser polyfill for AVIF images.

@cyrilwanner giving Canary a run through and it's working great for me right now. Thank you for doing this!

Are there any plans for forcing gray scale?

Currently, grayscale can be forced only globally for all your images.
When the change proposed by @davecardwell lands in canary, this setting can be changed on a per-image basis, so you don't have to globally force all images into grayscale, you can just do it for a single image.

But if you want to force grayscale for all your images, this worked for me:

// next.config.js
const images = require('next-optimized-images');
const { ColorSpace } = require('@wasm-codecs/mozjpeg/lib/colorspace');

module.exports = images({
  images: {
    mozjpeg: {
      colorSpace: ColorSpace.GRAYSCALE,
    },
  },
});

Please be aware that caches currently don't get invalidated automatically, so you have to manually remove the folders .next and node_modules/optimized-images-loader/.cache after changing this setting.

Also, I just noticed when trying it out, that the grayscale gets lost when the image gets converted to webp. That is definitely a bug and will get fixed.

@cyrilwanner This is a long shot, but it would be amazing if next-optimized-images is the first library to support .avif images.

From reachlightspeed.com/blog/using-the-new-high-performance-avif-image-format-on-the-web-today

It's basically a super-compressed image type. Netflix has already considered .avif superior to the JPEG, PNG, and even the newer WebP image formats for its image quality to compressed file size ratio.

AVIF is supposed to land in Chrome 85 and Firefox 80 on August 25, 2020

I did some digging and I found a few library that have AVIF-related functionality —

  1. npmjs.com/package/@saschazar/wasm-avif - A WASM-based AVIF encoder/decoder.
  2. Kagami/avif.js - A browser polyfill for AVIF images.

I will definitely add support for AVIF images. However, it does not have the highest priority right now since Firefox and Chrome will only support it in the next version. I'll finish the remaining tasks for the canary version and then dive into AVIF support. With the new package structure, adding a new image format is quite easy.

And thank you for searching existing libraries, the wasm one will definitely help!

commented

First things first, thank you for this amazing plugin, the direction it's taking is amazing <3.

Secondly i would like to propose a new feature for the image component. In my usecase sometimes i need to display an image only for a specific breakpoint, for example let's say i want to display an image only for mobile, and hide it for desktop. To prevent all browsers from downloading it pointlessly i use a blank embedded image, like this: min-width=desktop src=data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, so i was thinking this could be achieved with the current implementation by setting the size to 0 or null size=[600, null] breakpoints=[1200].
Also sometimes i would like to use a completely different image for a specific breakpoint, this could be achieved by using two or more Img components with the feature i mentioned before, or by adding support for multiple sources directly to the component, but this would increase the complexity by a bit.

What do you think?

First things first, thank you for this amazing plugin, the direction it's taking is amazing <3.

Secondly i would like to propose a new feature for the image component. In my usecase sometimes i need to display an image only for a specific breakpoint, for example let's say i want to display an image only for mobile, and hide it for desktop. To prevent all browsers from downloading it pointlessly i use a blank embedded image, like this: min-width=desktop src=data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, so i was thinking this could be achieved with the current implementation by setting the size to 0 or null size=[600, null] breakpoints=[1200].
Also sometimes i would like to use a completely different image for a specific breakpoint, this could be achieved by using two or more Img components with the feature i mentioned before, or by adding support for multiple sources directly to the component, but this would increase the complexity by a bit.

What do you think?

I believe with most newer browsers (especially the mobile ones) you won't need this functionality as images hidden by CSS rules like display: none won't be downloaded by the browser.

commented

@alexej-d I didn't test this on mobile, but on desktop the images are still loaded, at least on chrome and firefox, there is a jsfiddle in this post https://stackoverflow.com/a/16873591 where you can test it. With the blank gif embedded the image load is prevented on all browsers, except the really old ones that don't support picture. The only downside i see is that you'll increase the js size with ~80bytes, and the html will grow everytime you use it, but with gzip it doesn't really matter.

commented

@alexej-d I tested it, sadly, hiding the image and/or the parent did not work for me :( this might have worked for that specific chrome version (v68), but for the version I'm currently using (v84) it's not :/. In the same post a guy tested this with multiple engines https://stackoverflow.com/a/22970996. It would be great if it worked consistently, but it's really unreliable. At my workplace we use a lot of different images between mobile and desktop so the payload would increase a lot if images are not loaded correctly.

@andreisergiu98 yeah I get where you're coming from, this is certainly not ideal. An other way with react could be using wrapper components like here to hide the image element at certain breakpoints.

First things first, thank you for this amazing plugin, the direction it's taking is amazing <3.

Secondly i would like to propose a new feature for the image component. In my usecase sometimes i need to display an image only for a specific breakpoint, for example let's say i want to display an image only for mobile, and hide it for desktop. To prevent all browsers from downloading it pointlessly i use a blank embedded image, like this: min-width=desktop src=data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==, so i was thinking this could be achieved with the current implementation by setting the size to 0 or null size=[600, null] breakpoints=[1200].
Also sometimes i would like to use a completely different image for a specific breakpoint, this could be achieved by using two or more Img components with the feature i mentioned before, or by adding support for multiple sources directly to the component, but this would increase the complexity by a bit.

What do you think?

Thank you for that idea! I was also thinking about what's the best way to solve the problem when someone wants a different image for mobile and desktop and your solution would solve both problems. 👍

The only downside i see is that you'll increase the js size with ~80bytes, and the html will grow everytime you use it, but with gzip it doesn't really matter.

I think we can live with those 80bytes. Often, (CDN) URLs are already longer than those empty images and if we would solve the problem in an additional react component only rendering it on the given viewport, that component would be much bigger. And as you said, it shouldn't be a problem with gzip anyway.
Also, no one has to use that feature as it is optional, it can still be done with display: none if you prefer that.

@cyrilwanner The more I use the React components from the canary branch the more I love the library—thank you again for the great work!

Using the <Img> component to create a <picture> works great when the source image is the same at every size, but if the art direction calls for a different source image at different sizes I can only seem to achieve this by doing an import for every image/breakpoint/pixel-density combination and manually creating a <picture> element, which is cumbersome to say the least!

I wonder if this is something that next-optimized-images could help with? An example of a possible API would be to allow an array for the src attribute on <Img> where you can specify an array of source images to correspond to the different sizes/breakpoints, for example:

import HeroMobile from "images/hero-mobile.jpg";
import HeroDesktop from "images/hero-desktop.jpg";

<Img
  className="w-full object-cover"
  src={[HeroMobile, HeroDesktop]}
  sizes={[640,768,1024]}
  densities={1,2}
  webp
/>

Which would result in…

<picture>
  <source type="image/webp" srcSet="…/hero-mobile-….webp, …/hero-mobile-….webp 2x" media="(max-width:640px)"/>
  <source type="image/jpeg" srcSet="…/hero-mobile-….jpg, …/hero-mobile-….jpg 2x" media="(max-width:640px)"/>
  <source type="image/webp" srcSet="…/hero-desktop-….webp, …/hero-desktop-….webp 2x" media="(min-width:641px and max-width:768)"/>
  <source type="image/jpeg" srcSet="…/hero-desktop-….jpg, …/hero-desktop-….jpg 2x" media="(min-width:641px and max-width:768)"/>
  <source type="image/webp" srcSet="…/hero-desktop-….webp, …/hero-desktop-….webp 2x" media="(min-width:769)"/>
  <source type="image/jpeg" srcSet="…/hero-desktop-….jpg, …/hero-desktop-….jpg 2x" media="(min-width:769px)"/>

  <img src="…/hero-desktop-….jpeg" className="w-full object-cover"/>
</picture>

Is this too niche or do you think it’s something common enough to warrant supporting?

@cyrilwanner The more I use the React components from the canary branch the more I love the library—thank you again for the great work!

Using the <Img> component to create a <picture> works great when the source image is the same at every size, but if the art direction calls for a different source image at different sizes I can only seem to achieve this by doing an import for every image/breakpoint/pixel-density combination and manually creating a <picture> element, which is cumbersome to say the least!

I wonder if this is something that next-optimized-images could help with? An example of a possible API would be to allow an array for the src attribute on <Img> where you can specify an array of source images to correspond to the different sizes/breakpoints, for example:

import HeroMobile from "images/hero-mobile.jpg";
import HeroDesktop from "images/hero-desktop.jpg";

<Img
  className="w-full object-cover"
  src={[HeroMobile, HeroDesktop]}
  sizes={[640,768,1024]}
  densities={1,2}
  webp
/>

Which would result in…

<picture>
  <source type="image/webp" srcSet="…/hero-mobile-….webp, …/hero-mobile-….webp 2x" media="(max-width:640px)"/>
  <source type="image/jpeg" srcSet="…/hero-mobile-….jpg, …/hero-mobile-….jpg 2x" media="(max-width:640px)"/>
  <source type="image/webp" srcSet="…/hero-desktop-….webp, …/hero-desktop-….webp 2x" media="(min-width:641px and max-width:768)"/>
  <source type="image/jpeg" srcSet="…/hero-desktop-….jpg, …/hero-desktop-….jpg 2x" media="(min-width:641px and max-width:768)"/>
  <source type="image/webp" srcSet="…/hero-desktop-….webp, …/hero-desktop-….webp 2x" media="(min-width:769)"/>
  <source type="image/jpeg" srcSet="…/hero-desktop-….jpg, …/hero-desktop-….jpg 2x" media="(min-width:769px)"/>

  <img src="…/hero-desktop-….jpeg" className="w-full object-cover"/>
</picture>

Is this too niche or do you think it’s something common enough to warrant supporting?

We have this use case in mostly any project. We use different pictures ( ex: full body picture, to only face picture), not only different sizes.

Nice work :) I'm using the canary version and trying to get this to work
<div style={{backgroundImage: 'url(./images/banner.jpg)' }} />
I don't know if inline stye is supported or not ?

Have you all seen the Next.js Image Component RFC? I havn't read the full thing yet, but assume there will be overlap, and reaching out to them might be beneficial.

Pretty excited about this new version and the styled-component support! I was not able to make it work though. Here is the error and the code.

Error: Babel plugin 'react-optimized-image/plugin' not installed or this component could not be recognized by it.

.babelrc

"presets": ["next/babel"],
"plugins": ["react-optimized-image/plugin"]

next-config.js

module.exports = withPlugins(
  [[optimizedImages, {}], [withBundleAnalyzer]],
  nextConfig
);

styled component

import { Img } from "react-optimized-image";

const ApplicationView = styled(Img)`
  // styles
`;

React component

<ApplicationView
  src={imageUrl}
  webp={true}
  densities={[1, 2]}
/>

While I try next build and tranform images into multiple sizes within Docker, it crashes on warning MaxListenersExceededWarning. I can see that is caused by using as many sizes as:

      <Img
        src={BigPicture}
        sizes={[
          276,
          552,
          640,
          728,
          816,
          904,
          992,
          1080,
          1350,
          1620,
          1890,
          2160,
          2400,
        ]}
      />

Is there a way to customize emitter.setMaxListeners() and increase the limit?
I have tried invoking events.EventEmitter.defaultMaxListeners = 20 before next build but that doesn't make any difference.

Version v3.0.0-canary.10

@ivawzh

Is there a way to customize emitter.setMaxListeners() and increase the limit?

They’re uncaughtException listeners on process, so I run process.setMaxListeners(20); in the top of my next.config.js and that seemed to take care of the warning.

@davecardwell that works beautifully. Thanks!

I also have the warnings for the max listeners. Unfortunately setting maxListener to any number doesn't solve my issue. I have no problem on my local machine. The issues comes when I try to deploy on Vercel :

RangeError: WebAssembly.Memory(): could not allocate memory

15:14:26.163 RangeError: WebAssembly.Memory(): could not allocate memory
15:14:26.164 RangeError: WebAssembly.Memory(): could not allocate memory
15:14:26.167 /vercel/6d54a8a7/node_modules/@wasm-codecs/mozjpeg/lib/mozjpeg.js:9
15:14:26.167 var Module=typeof mozjpeg!=="undefined"?mozjpeg:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_HAS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_HAS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_NODE=ENVIRONMENT_HAS_NODE&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module"locateFile"}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){scriptDirectory=dirname+"/";read=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath"normalize";return nodeFS"readFileSync"};readBinary=function readBinary(filename){var ret=read(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process"on";quit_=function(status){process"exit"};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDir){scriptDirectory=scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];function dynamicAlloc(size){var ret=HEAP32[DYNAMICTOP_PTR>>2];var end=ret+size+15&-16;if(end>_emscripten_get_heap_size()){abort()}HEAP32[DYNAMICTOP_PTR>>2]=end;return ret}var asm2wasmImports={"f64-rem":function(x,y){return x%y},"de
15:14:26.167  
15:14:26.170 RuntimeError: abort(RangeError: WebAssembly.Memory(): could not allocate memory). Build with -s ASSERTIONS=1 for more info.
15:14:26.170 at process.abort (/vercel/6d54a8a7/node_modules/@wasm-codecs/mozjpeg/lib/mozjpeg.js:9:10196)
15:14:26.170 at process.emit (events.js:327:22)
15:14:26.170 at processPromiseRejections (internal/process/promises.js:209:33)
15:14:26.170 at processTicksAndRejections (internal/process/task_queues.js:98:32)
15:14:26.364 error Command failed with exit code 7.
15:14:26.364 info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
15:14:26.374 Error: Command "yarn run build" exited with 7

@cyrilwanner Just tried out canary with Next v10 and it works great for me!

Awesome work!

Same issue as @DonovanCharpin

@cyrilwanner I'm not sure if you've addressed this elsewhere, but what's the main difference between this project and the new Next.js built-in image optimization?

@nicholaschiang I am going to try to explain, hopefully @cyrilwanner can be more thorough.

Next.js built-in image optimization works only with an API ( as in serverless ) and the images are not generated in build time, this also means outside of Vercel you cant use them ( for now ). This package optimizes the images in build time so you have them ready to use post-build.

Yup, I figured as much but was wondering if there were any other differences. Right now, this package is great for build-time optimization (e.g. when you have a fixed number of static images) but can't easily be used with user-uploaded images.

I noticed in this RFC that you're thinking of adding runtime optimization features and was wondering why (because Next.js 10 already has them built-in).

I also have the warnings for the max listeners. Unfortunately setting maxListener to any number doesn't solve my issue. I have no problem on my local machine. The issues comes when I try to deploy on Vercel :

RangeError: WebAssembly.Memory(): could not allocate memory

I have the same issue with the Vercel Deployment. @meienberger Have you found any workaround/solution in the meantime?

@maxwolfs I could not find a solution and I moved to a server based solution. Maybe you can try with Next v10 if you are not already there

I ran into the same error that @meienberger had.

I was able to get past it (not solve) by:

  1. Removing the sizes and breakpoints from each <Img> tag
  2. Attempting to make all the file paths static e.g. import MyImage from './myimage.jpg as opposed to <Img src={require('./${myImageJpgVar}')} />.
  3. In cases where I needed to use dynamic files I placed them in their own directory.

Note: I only have 20 or so images

I know that this "solution" isn't great but it enabled me to build my Next.js app once again. FYI it also successfully built on Vercel.

Using:

"next": "latest",
"next-optimized-images": "^3.0.0-canary.10",

I couldn't find a way to add style props to the Picture element of the component. All styles are handed down to the img element. is this intentional?

@enpclark With a <picture> element you always put the style, alt, etc. attributes on the <img> (as in, that is the norm with the HTML, regardless of this library).

I just tried this out and it's mostly pretty good! But I'm wondering why the use of picture element instead of just using the srcset property on an img element if the generated images are all going to be the same but just different sizes/densities. I got frustrated because replacing the img element with a picture element makes it trickier to style correctly, because of the extra nesting of the HTML which makes stuff like flexbox rules from the image's parent fall on their face in certain contexts.

My understanding is that picture is more intended for art direction problems, not for what can be accomplished with just a simple srcset - would you consider making the picture semantics be a separate abstraction than just the bare Img component or opt-in somehow? It's not necessary for the vast majority of use cases I have and is making my styling a pain.

Is this V3 still happening?

I haven't seen any comments from @cyrilwanner since Aug 12th 2020.

No commit have been made since Aug 9th 2020.

I hope it's not been cancelled because NextJs launched their own image optimization solution. NextJs solution is far from perfect and has plenty of troubles:

  • Caching is forcing network request to check Etag, you can't override this behavior
  • Optimization is done on the fly, which means it's a delay you wouldn't get at build time
  • The image styling used is highly restrictive, inefficient and forces the use of !important to overwrite
  • Most uses of Next/Image component results in 3 to 4 DOM nodes per image, which is simply atrocious
  • The wrappers are DIVs, so if you use in a P tag, you're semantically incorrect
  • You can't use a decent Content-Security-Policy header

As you can see, we do need an alternative because NextJs solution isn't rosy as soon as you dig a little deeper.

Is this V3 still happening?

I second @TheThirdRace’s thoughts on the new built-in Next.js solution and would very much like to continue using next-optimized-images v3.

I appreciate @cyrilwanner may have moved onto other priorities—that’s the nature of open source some times, and I thank him for his efforts thus far. It would be good to hear whether or not this library is likely to see any further development though so we can make alternative arrangements.

Thanks for bringing this up, I was wondering the same thing. I don't currently have (and I don't want to have) node at all in production, so I can't use the Next.js solution.

Is anybody using this canary version? Is it ready for release or does it still have open issues?

commented

Mayby @cyrilwanner should update the module status...

@FreedomBen I'm using it on a small project and it seems stable enough but I too am concerned there's no development here anymore. Maybe a fork would be good if someone is interested to maintain.

Is this still happening @cyrilwanner? Your feedback here would really appreciated.

So this library is dead?

Let us know if you are looking for maintainers @cyrilwanner. Im sure there are people who are happy to help!

I'm also eagerly awaiting V3

Also I love the fact that with each deploy the image is placed in cache with a new hashed file name, so each deploy I get fresh images I don't have to wait for the cache to expire for users to see new version of the same image file.

Also I'm not using Vercel to deploy I've got my own AWS CDK configuration so I have no idea if the Next image caching strategies are even going to work with me and my AWS CDK deployment at run time.

I think I saw on twitter that he's busy with his thesis or something. Eventually priorities will shift after that and all the nice tools he made will flourish again :)

It's perfectly ok to be busy, very understandable and we can all relate to that.

It would be nice if he could take 10 minutes and put a big message at the top of the Readme file on the repo so people stop asking the same question 😅

@cyrilwanner any progress can i use in production?

Great job so far on the canary and the library is very much appreciated.

The only bug I found is that inlining svg and converting them to webp causes the base64 encoded svg to be included instead of the webp one

<source type="image/webp" srcset="BASE64_SVG">

It seems like this project is dead anyways, but I thought I'd ask ... given that Next itself has switched from Babel to Rust, is making Babel a requirement of v3 wise?