So the images are displayed,
and NextJS tries to optimize them.
But in my case here,
where I'm using Cloudinary for hosting those images,
that's not all I wanna do.
Because image hosts like Cloudinary typically give you
more ways of loading images and files in an optimized way.
Image hosts like Cloudinary allow you
to manipulate the image URL
and add some extra configuration,
some extra parameters to that
URL that will trigger Cloudinary
to generate multiple versions of an image file,
and cache those different versions
and serve those different versions to your application.
So, Cloudinary will perform the image resizing then.
But in order to use these Cloudinary capabilities,
we have to manipulate the URL that's set as an image source.
And we have to do that in our NextJS application.
Now, how can we do that though?
Well, you can add a special loader prop
on the NextJS image element.
And this loader prop wants a function as a value,
a function that will be executed by NextJS
when it determines the path of the image.
So when it determines this source here,
it will essentially funnel the source you're setting here
through that loader function so that you can manipulate it,
and overwrite the image path before the image
is actually served and rendered onto the screen.
Now, therefore, here I'll add a new function outside
of the component function, and I'll name it imageLoader.
The name is up to you though.
And this will get a configuration object
automatically passed in by NextJS.
And I will log that configuration object
so that we can see what's inside.
And then I will actually return config.src
because that's one property
we'll have on this configuration object,
which is the source we're setting here
through that source prop.
And I'm returning that unchanged for the moment
so that the image is still displayed, though of course,
at moment, we won't do anything about it,
but that will change.
But now we can use this image loader as a value
for this loader prop.
And if we now save everything and reload, the images,
of course, are displayed as they were before.
But if we open up the terminal to see the logs,
we can see that our configuration object looks like this.
It in the end has the source property,
which has the source as we set it on the image
then a width property which is very large here way too large
because NextJS doesn't know how to size the image here.
And a quality property which in my case here is undefined.
Now, you can control the value of that quality property
by adding the quality prop on the image element,
and setting this to a numeric value between zero and 100,
like for example 50.
And you can do this whenever you're using
the NextJS image component,
and behind the scenes NextJS will generate,
and serve the same image with that quality then instead
of the native quality of the image.
And that quality value you're setting here will then,
for example, when using an image loader function
as we're doing it here,
be made available on this config object
so that you can pick it up
when generating that URL you wanna actually render.
And that's what we'll work on next now.
Generate a different URL so that we take advantage
of those on-demand image optimization capabilities
offered by Cloudinary.
And therefore, this approach-approach, of course,
only makes sense if you're using a host like Cloudinary,
which does offer on-demand resizings and optimizations,
which most static asset hosts
like Cloudinary do support though.
So, how can we now change that URL?
Well, of course, if you don't know
the backend you're working with,
you might wanna check out its documentation.
So here in the Cloudinary docs, you can, for example,
learn how you can manipulate and change the URL
to which the request is sent to change the image
that's served back from Cloudinary.
And you can change a lot there.
You can, for example, crop it on demand,
but you can also change the height
and width of the served image file.
And that's the part that's interesting to us here.
For that, to apply those changes,
we need to add parameters like this to the Cloudinary URL,
and therefore we need to manipulate the URL we have
in our application.
Because at the moment it looks like this,
and we now essentially need to add parameters like this
in front of the image file name
after this upload segment here in the path.
So, we need to add those parameters
after this upload segment in our path here.
Therefore, in this image loader function,
what we should do here is split this source path
into two parts, one part,
which is the file name and one part,
which is everything up to including that upload segment.
And I'll name the first part here URL start.
And here I'll use my config source value,
and call the built-in split function,
which you can call on any string in JavaScript to split it
on that upload segment,
including that slash that comes after it.
And that will then give me an array
which includes two elements
where the first element will essentially be this part.
And the second element will be this part.
And indeed, that version number here belongs
to the file name and therefore should be included
in that second part.
So therefore, to get the first element,
I'll access the first element in the array
that's generated by split.
And then I'll get my URL end here by using the same code,
but getting the second element.
And now we can insert our transformation parameters
between these two segments, between start and end.
Therefore, I'll add a transformations constant
where I'll dynamically generate a string.
And for that, I'll use backticks
so that I can easily inject values into a string.
Where I wanna set the width,
which when using Cloudinary,
is done by adding a parameter called W_,
and then width value you want Cloudinary to generate
for this image on the server.
Here, I'll set this to, let's say,
200 because we have very small images here,
so I don't need a lot of width.
Then, separate by a comma, I'll also set the height.
And here I'll set this to 150
because it's a bit less than the width.
And rough values are good enough here
because I don't care about
getting the exact width and height
that will be displayed on the screen.
I just wanna make sure that I do get a much smaller image,
and I do load a much smaller image
than what the user uploaded, because there is no reason
to load a large file if we then display it
in a very small size.
Last but not least, I also wanna get Cloudinary to generate
an image at a different quality.
And for that, we can add the Q_parameter.
And then I actually wanna use the quality
that was set here through the quality prop.
And as you see, that is made available here.
This output was before I added the quality prop.
That's why it's undefined here,
but it will be this value here if the quality prop is set.
And therefore, I'll set this to the value I get
from config.quality.
Well, and with that, we can then return a new string,
a new source path, which is essentially the URL start.
Then we need to add upload/
because we split on that value
so that was actually excluded from the URL start.
Then I inject my transformations.
And then another/ and then URL end.
And that's now the new path that will now get Cloudinary
to resize and optimize the image
on the Cloudinary servers already.
And then the next image component
will display that pre-optimized image.
But the next component is used to make sure that this loader
is executed before the image is rendered.
So, that's what the next image component
will do for us here.
So now with that imageloader added, if you save everything,
you'll see the images are loaded up, but they are distorted.
And they are distorted because
I'm setting a width and height here
which doesn't necessary match the aspect ratio
of the original image and therefore distortion happens.
Now, one way to work around that would be to manipulate
the Cloudinary URL to automatically crop the image as part
of their resizing process so that the aspect ratio
is kept and we don't distort the image.
So, that's something you could do.
But an alternative would be to remove
this height resizing request here and just set the width.
And with that, Cloudinary will make sure to resize
the height automatically
whilst keeping the original aspect ratio.
And therefore, by changing this, if I save that and reload,
you see that the distortion now is gone,
but we still make sure that a smaller image is requested
from that backend.
And therefore with that,
we make sure that no unnecessarily large images are loaded
when serving images that have been uploaded by users.
And that's therefore another way
of using the NextJS image component.
Here, we're using it with the fill prop and the loader prop
to on-the-fly resize the image or request a resized image
from the backend that hosts the images,
so that we then serve and display the optimized image.
In our header component on the hand,
we were using a locally stored image that was part
of the project,
and we were using the image component to load
that locally stored image in an optimized way.
In that case, when using a local image,
we then don't have to specify width and height,
but we can still do it as a little workaround, so to say,
to get NextJS to load a smaller image
than the original image file,
though it would be preferred to actually resize
the original file.
But these are some of the different ways of using
this image component to optimize images
on your NextJS website.
As mentioned, you can learn more about configuring images
and using this image component
in the official documentation.
Because, of course, there are a lot of different use cases
for displaying images,
and they all have different requirements.
But I now hopefully got you started
with the image component and showed you some
of the most important features it offers.
Now, one thing you might notice now
that we basically finished our work
on those user-generated images
is that I'm getting a warning here
in my developer tools JavaScript console
that I should add the sizes prop
when using the fill prop on an image
to optimize performance.
And indeed, in general, when using the fill prop,
because you don't know the width
and height of the loaded image in advance,
it is recommended that you add the sizes prop,
which I mentioned earlier already.
And then you should use this sizes prop to set one
or more sizes depending on the available viewport width.
Now here it's special though.
Due to the way I'm loading this image,
where I'm loading this image loader function
and where I enforce a certain width for this image,
I actually don't really care about my sizes here
because I make sure that I load an image
of a certain width anyways.
Therefore, what you could do in that case
is actually get rid of fill
and instead manually re-add width here
and set that to 200, for example,
because I know that here I'll be loading an image of a width
of 200 because that's the setting I have here.
So, the width of this image is actually no longer unknown.
But of course, if I do that and I remove fill,
and set the width
and I try to load this page,
I'll get an error that the required
height property is missing.
Because that's another required property you need to set
if you're not importing a local image,
and you're not using the fill prop.
Now, the height, however, is something I don't know here
because I leave that up to Cloudinary in this case.
I'm just setting some width and I leave it up to Cloudinary
to then adjust the height of the image appropriately.
Now, what we could do here
if we still wanna set the width,
since we do know that in advance,
is we could set any height that's about right here.
Because in my use case here,
this actually won't matter too much,
because I'll be resizing the image
and how it's displayed with CSS anyways.
Now, we should just make sure that we go
to the global CSS file,
and there we should actually set the width and height
of the image as it will be displayed due to CSS
on the image element itself again, like this,
so that it is displayed correctly with CSS.
And with that,
the images are still displayed correctly here,
but I'm still performing those changes with Cloudinary,
and I'm just setting width and height here to get rid
of these extra warnings.
And that's something you might consider doing
from an optimization perspective.
It actually won't really matter here
because we were using Cloudinary
for the image transformation and optimization anyways.
Nonetheless, this is something you might
wanna consider changing.