[next/image] Responsive images with "art direction" #19880
-
I am using the The idea is that based on media queries (all I need is a certain width breakpoint in this case), change the src of the image. For example, on desktop have a more landscape image, and on mobile show an image that is more of a square. Here is the example from the MDN docs: <picture>
<source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg">
<source media="(min-width: 800px)" srcset="elva-800w.jpg">
<img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva">
</picture> How can I achieve this using the Image component? My only idea so far is to have multiple Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments 35 replies
-
Your suggestion should work fine. If you hide your elements by using CSS |
Beta Was this translation helpful? Give feedback.
-
The problem with this approach is when you set |
Beta Was this translation helpful? Give feedback.
-
I've had this issue before. The problem with display: none is I believe the images are still downloaded, so it's an insane cost to have like 3 downloads for every image at all times. So I ended up making up all images load lazy so that this doesn't happen. It's not a great work around at all because lazy loading every image isn't optimized either, but it wasn't a production site. I think the only way to do it correctly is with javascript and not CSS media queries |
Beta Was this translation helpful? Give feedback.
-
We ended up writing our own We'll iterate some more internally, and publish it at a later time, when we've tested it fully. |
Beta Was this translation helpful? Give feedback.
-
Update: We have extracted the core logic from This allows usage outside of
Exampleimport { unstable_getImgProps as getImgProps } from 'next/image'
export default function Page() {
const common = { alt: 'Hero', width: 800, height: 400 }
const { props: { srcSet: dark } } = getImgProps({ ...common, src: '/dark.png' })
const { props: { srcSet: light, ...rest } } = getImgProps({ ...common, src: '/light.png' })
return (<picture>
<source media="(prefers-color-scheme: dark)" srcSet={dark} />
<source media="(prefers-color-scheme: light)" srcSet={light} />
<img {...rest} />
</picture>)
} PR: #51205 |
Beta Was this translation helpful? Give feedback.
-
Art Direction hero image with preloading: import {
unstable_getImgProps as getImgProps,
ImageProps,
StaticImageData,
} from 'next/image'
import Head from 'next/head'
const AdaptiveImage = ({
className,
alt = 'Hero image',
fill = true,
priority = true,
sizes = '100vw',
breakpoint = 640,
desktopImage,
mobileImage,
style = { objectFit: 'cover' },
...props
}: {
breakpoint?: number
desktopImage: StaticImageData
mobileImage: StaticImageData
} & Partial<ImageProps>) => {
const commonPreload = {
rel: 'preload',
as: 'image',
imageSizes: sizes,
}
const common = { alt, fill, priority, sizes, style, ...props }
const { srcSet: desktop } = getImgProps({ ...common, src: desktopImage }).props
const { srcSet: mobile, ...rest } = getImgProps({ ...common, src: mobileImage }).props
const desktopMedia = `(min-width: ${breakpoint}px)`
const mobileMedia = `(max-width: ${breakpoint - 1}px)`
return (
<>
<Head>
<link
{...commonPreload}
media={desktopMedia}
href={desktopImage.src}
imageSrcSet={desktop}
/>
<link
{...commonPreload}
media={mobileMedia}
href={mobileImage.src}
imageSrcSet={mobile}
/>
</Head>
<picture className={className}>
<source media={desktopMedia} srcSet={desktop} />
<source media={mobileMedia} srcSet={mobile} />
<img alt={alt} {...rest} />
</picture>
</>
)
}
export default AdaptiveImage Usage: import HeroDesktop from './hero-desktop.png'
import HeroMobile from './hero-mobile.png'
<AdaptiveImage
alt='My Hero'
desktopImage={HeroDesktop}
mobileImage={HeroMobile}
/> |
Beta Was this translation helpful? Give feedback.
-
It's a great solution, unfortunately it won't work with the App Router. Any idea on how to make it working? |
Beta Was this translation helpful? Give feedback.
-
to anyone looking for solution to this problem in 2025, the getImageProps api is stable now, |
Beta Was this translation helpful? Give feedback.
-
Another option I found useful and simpler is creating a component using three Image blocks (from Nextjs). It still leverages all the performance optimizations from Nextjs component but conditionally loads one image source or another. This could be an starting point as more configs can be added import Image from "next/image";
type PictureProps = {
mobile: string;
tablet: string;
desktop: string;
alt: string;
};
export default function PictureComponent({
mobile,
tablet,
desktop,
alt,
}: PictureProps) {
return (
<>
<Image
alt={alt}
src={mobile}
fill
sizes="100vw"
style={{ objectFit: "cover" }}
className="block md:hidden"
/>
<Image
alt={alt}
src={tablet}
fill
sizes="100vw"
style={{ objectFit: "cover" }}
className="hidden md:block lg:hidden"
/>
<Image
alt={alt}
src={desktop}
fill
sizes="100vw"
style={{ objectFit: "cover" }}
className="hidden lg:block"
/>
</>
);
} |
Beta Was this translation helpful? Give feedback.
Update: We have extracted the core logic from
next/image
into a newunstable_getImgProps()
function.This allows usage outside of
<Image>
, such as:background-image
orimage-set
context.drawImage()
or simplynew Image()
<picture>
media queries to implement Art Direction or Light/Dark Mode imagesExample