DEV Community

Cover image for Working with images in Next.js
Omotayo David
Omotayo David

Posted on • Edited on

Working with images in Next.js

Rendering images in Next.js for the first time can be frustrating, especially for beginners who are not fond of reading documentation.

This article will help you understand how to render images in Next.js, the difference between the <img> tag and the <Image/> component, and the pitfalls to avoid when working with images in Next.js.

How to Render Images in Next.js

There are two ways you can display images in Next.js, you either use the conventional <img> tag or a specialized <Image/> component that is unique to Next. The differences between these tags are quite much, but they pretty much do the same thing; which is to display images to the browser.

If you’re rendering images or any media files relatively ( locally ) in your project, you must store them in the /public folder directory. Otherwise, you’ll get an error.

The <img> tag

This is the same HTML <img> tag you’re used to, and perhaps the tag you used the first time you rendered an image to the browser.
The tag has no special optimization feature, all it does is link images to a webpage by creating a holding space for the referenced image.

How to use the <img> tag

The <img> tag has two important attributes;

  • src
  • alt

The src attribute takes in a relative or absolute URL path of where the referenced image is located, while the alt attribute takes in an alternate text that displays when the image isn’t rendering properly in the browser for some reason.

Below is a typical example of a <img> tag markup:



<img src=/public/dev_logo.png alt=dev logo>


Enter fullscreen mode Exit fullscreen mode

You can render images on your Next.js webpage without much hassle with the <img> tag, provided the image, if stored locally, is moved to the /public folder directory. But the issue of optimization remains.

Caveats of the <img> tag

The <img> tag renders images to the webpage with their original size and resolution regardless of the viewport the image is being rendered on.

A quick play around with CSS can fix this, but even with the image displaying precisely to each viewport, the intrinsic size, which is the original size and aspect ratio of the image sent by the server will remain the same.

This is not very efficient, as it will drastically reduce the web page's performance and increase the load time.

To optimize images rendered with the <img> tag in your project, you need to implement some extra set of functionalities. some of which are:

-Lazy loading: Images will only be fetched and rendered when they are scrolled into the viewport. libraries like “react-lazyload” can easily add this functionality to your project.

-Resizing: Detecting and fetching the appropriate image size for every viewport, provided there are images of different sizes stored on the server.

-Modern image formats: Conditionally serving stored images with WebP formats when it's supported by the browser.

As you might have noticed, this is a lot of work. If only there was a better way to do this without losing a limb. Oh wait, there is.

The <Image/> component

The <Image/> component was contrived by the creators of Next.js to solve the optimization issues the former lacks. It is a better and enhanced version of the <img> tag, but unlike the <img> tag, the <Image/> component is not a native HTML element – but a built-in API in Next.js.

The component essentially wraps the <img> element with other div elements to prevent cumulative layout shift.

Benefits of using the component

The <Image/> component’s API doesn’t just render images to the browser like the <img> tag, it also optimizes the image for every viewport by implementing each of the functionalities below out of the box:

-Lazy loading: Every image linked to a webpage with the <Image/> component is fetched and rendered on-demand as soon as its holding space is scrolled into view by default. So you never have to worry about slow load time on your web pages and writing extra scripts for such functionality.

-Responsiveness: Images are automatically responsive when rendered with the <image/> component, saving you the stress of CSS media queries.

-Resizing: The appropriate size and aspect ratio of an image for a viewport is fetched and rendered on-demand, instead of fetching the intrinsic size and aspect ratio before reducing it for the target viewport.

-Optimized file size: The <Image/> component fetches the appropriate file size for each viewport, taking away the need to store images with different file sizes for every viewport on the server, and fetching them one after the other when needed.

-Optimized image format: when an image is fetched from the server with the <Image/> component, the file format is changed to a more optimized and SEO-friendly one like WebP, if the browser supports it.

How to use the <Image/> component

To use the <Image/> component in your Next.js project, the first thing you need to do is import it into your desired component from Next’s library:



Import Image from next/image


Enter fullscreen mode Exit fullscreen mode

The next step is to add the JSX syntax to your code with at least an src, widthand height property:



<Image src={dev_logo.png} width={150} height={150} />


Enter fullscreen mode Exit fullscreen mode

N.B If your image is stored locally, it must be located in the /public folder, otherwise, you’ll get an error.

The width and height properties are mandatory, without these properties, Next.js will throw an error.

People, mostly beginners, often mistake these properties for inline styles. Well, that’s exactly what they are, but the image component needs them to perform its magic. They only accept pixels as values, which must also be an integer without a unit.

The component can be served in a project like so:



Import Image from next/image
const Home = () => {
  return (
    <>
      <h1>Homepage</h1>
      <Image src={hero_image.png} alt=Hero image width={150} height={150}/>
      <p>This is the homepage!</p>
    </>
  )
}
Export default Home


Enter fullscreen mode Exit fullscreen mode

Passing the image’s file name into the src property is enough for Next to detect and serve the image, as long as the image is in the public folder. You could also import the image statically just as you did the component itself:



Import hero from ../public/hero_image.png


Enter fullscreen mode Exit fullscreen mode

With the above code added, the new block of code will look like this:



Import Image from next/image
Import hero from ../public/hero_image.png

Const Home = () => {
  Return (
    <>
      <h1>Homepage</h1>
      <Image src={hero} alt=Hero image width={150} height={150}/>
      <p>This is the homepage!</p>
    </>
  )
}
Export default Home


Enter fullscreen mode Exit fullscreen mode

The only difference here is that the former is being passed dynamically as a path string while the latter, is as a variable.

The <Image/> component has other optional properties, some of which are:

object-fit : Specifies how an image should be resized to fit its container.

layout : The layout behavior of the image as the viewport changes size.

sizes : A string mapping media queries to device screen sizes.

placeholder : A placeholder to use while the image is loading.

Visit the docs for more info.

Caveats of the <Image/> component

The only major drawback of the <Image/> component is its limitations and insusceptibility to CSS styling. The component isn’t immune to styling per se, and since it’s wrapped around an <img> element, the possibility of styling isn’t ruled out.

Styling the component isn’t as easy as slamming it a className and having a field day in your CSS stylesheet. There are procedures to follow.

The <image/> component cannot be styled with a component-scoped stylesheet that doesn’t use Next.js’ built-in CSS support.

So if we have a project that has a Homepage component and an embedded <image/> component with a className of “img”, a scoped CSS stylesheet for our Homepage component will be named like this:



Homepage.module.css


Enter fullscreen mode Exit fullscreen mode

Adding rules to the “img” class in the Homepage stylesheet will be futile. Now imagine the inevitable frustration awaiting anyone using Next.js for the first time.

The only way you can style <image/> components in your project is to;
Create a global stylesheet, add rules to the images’ classes in the global stylesheet, and then importing the global stylesheet within page/_app.js as seen in the screenshot below.

next global stylesheet code

Every page and component in your project will have access to the global stylesheet after the importation.

How to serve images from external sources in your Next.js project

Fetching and rendering images from a server, CMS or any external sources in Next.js isn’t as straightforward as passing the URL of the referenced image into the src property.

Since the <image/> component optimizes images automatically, either locally or externally, to prevent abuse on external URLs; you must specify which domains are allowed to be optimized in your project.

This is done by adding an image object with a domain to the next.config.js module in your project:



module.exports = {
Images: {
domains: [example.com],
},
}

Enter fullscreen mode Exit fullscreen mode




Conclusion

We've seen the differences between the <img> tag and the <Image/> component, as well as their advantages and disadvantages. Now it's up to you to decide on what to use in your project. And how you could save yourself a lot of time and frustration by avoiding pitfalls like;

-Trying to style the <Image/> component from a non-global stylesheet.
-Not storing local images in the /public folder.
-Not specifying the domains of externally linked images.

Complicated as it may look, we've barely grazed the surface of the <Image/> component, so for more advanced stuff, please go to the official documentation.

Follow me on twitter and linkedin

Top comments (3)

Collapse
 
ravenoss profile image
David Richards

HI,
Thanks for the writeup.
Just a friendly note: after stating 'The width and height properties are mandatory', you've actually left them out of your code examples.
Separately, have you had any success serving static images from the /public directory while at the same time getting 'dynamic' images from externally linked service such as Cloudinary?
I can get one or the other to work but not both. The local image gets prepended with the Cloudinary URL. Using img for local static files works but with warnings to use Image
David

Collapse
 
david4473 profile image
Omotayo David

Hi, I'm sorry for replying so late, hope you were able to solve the problem. If you don't mind me asking, I'd love to know how you solved it. And thanks for pointing out my mistake, I'll rewrite the article when I have the chance.

Collapse
 
erinkahn profile image
Erin Kahn

What does your hero image file look like? I don't know how to export an image file as a component to import it as a component

Import hero from ‘../public/hero_image.png’

image file export ?