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”>
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’
The next step is to add the JSX syntax to your code with at least an src
, width
and height
property:
<Image src={“dev_logo.png”} width={150} height={150} />
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
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’
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
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
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.
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’],
},
}
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.
Top comments (3)
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
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.
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 ?