ui icon indicating copy to clipboard operation
ui copied to clipboard

How should I use next/image?

Open angelhodar opened this issue 1 year ago • 1 comments

Hey! First of all thanks for this amazing library. I was looking for them to use in my project and I am just wondering how the AvatarImage component integrates with the next/image component. I am using it in my project so I am worried about poor performance when using this component on anyone that accepts an image src as prop.

Thanks in advance!

angelhodar avatar May 17 '23 18:05 angelhodar

I think next/image can be used as src prp, for the AvatarImage component. And this will help us utilize some next/image perks like lazy loading, resizing of image

Mmm i dont understand what do you mean by using as src prop. Src prop has to be a string i think, not a component

angelhodar avatar May 19 '23 16:05 angelhodar

You could provide an asChild prop to the AvatarImage component. Since shadcn-ui is built on top of Radix UI, all valid Radix UI props are also valid shadcn-ui props.

Here's how you can use it:

<Avatar>
    <AvatarImage asChild src='/next.svg'>
        <Image src='/next.svg' alt='logo' width={40} height={40} />
    </AvatarImage>
    <AvatarFallback>N</AvatarFallback>
</Avatar>

Notice that I provide src prop twice in both AvatarImage and Image components. If AvatarImage does not receive a src prop, it will automatically use AvatarFallback. Unfortunately, it seems the only way to make the intended results.

After I checked the rendered HTML, the result is the same as the Image component. For more information check Radix UI Avatar API Reference and documentation on how to use asChild prop.

Comparison

Here are comparisons between different configurations to help see the difference: visual comparison

The code:

<div className='border rounded w-56 border-stone-800 p-4'>
    <p>Next Image</p>
    <Image src='/next.svg' alt='logo' width={40} height={40} className='my-4'><Image />
</div>

<div className='border rounded w-56 border-stone-800 p-4'>
    <p>shadcn-ui Avatar</p>
    <Avatar>
        <AvatarImage src='/next.svg' alt='logo' />
        <AvatarFallback>N</AvatarFallback>
    </Avatar>
</div>

<div className='border rounded w-56 border-stone-800 p-4'>
    <p>shadcn-ui Avatar + img tag</p>
    <Avatar>
        <AvatarImage asChild src='/next.svg'>
            <img src='/next.svg' alt='logo' />
        </AvatarImage>
        <AvatarFallback>N</AvatarFallback>
    </Avatar>
</div>

<div className='border rounded w-56 border-stone-800 p-4'>
    <p>shadcn-ui Avatar + Next Image (asChild, without src)</p>
    <Avatar>
        <AvatarImage asChild>
            <Image src='/next.svg' alt='logo' width={40} height={40} />
        </AvatarImage>
        <AvatarFallback>N</AvatarFallback>
    </Avatar>
</div>

<div className='border rounded w-56 border-stone-800 p-4'>
    <p>shadcn-ui Avatar + Next Image (asChild, with src)</p>
    <Avatar>
        <AvatarImage asChild src='/next.svg'>
            <Image src='/next.svg' alt='logo' width={40} height={40} />
        </AvatarImage>
        <AvatarFallback>N</AvatarFallback>
    </Avatar>
</div>

Rendered HTML:

<div class="border rounded w-56 border-stone-800 p-4">
  <p>Next Image</p>
  <img
    alt="logo"
    loading="lazy"
    width="40"
    height="40"
    decoding="async"
    data-nimg="1"
    class="my-4"
    style="color: transparent"
    src="/next.svg" />
</div>

<div class="border rounded w-56 border-stone-800 p-4">
  <p>shadcn-ui Avatar</p>
  <span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
    <img class="aspect-square h-full w-full" alt="logo" src="/next.svg"/>
  </span>
</div>

<div class="border rounded w-56 border-stone-800 p-4">
  <p>shadcn-ui Avatar + img tag</p>
  <span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
    <img src="/next.svg" alt="logo" class="aspect-square h-full w-full"/>
  </span>
</div>

<div class="border rounded w-56 border-stone-800 p-4">
  <p>shadcn-ui Avatar + Next Image (asChild, without src)</p>
  <span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
    <span class="flex h-full w-full items-center justify-center rounded-full bg-muted">
      N
    </span>
  </span>
</div>

<div class="border rounded w-56 border-stone-800 p-4">
  <p>shadcn-ui Avatar + Next Image (asChild, with src)</p>
  <span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
    <img
      alt="logo"
      loading="lazy"
      width="40"
      height="40"
      decoding="async"
      data-nimg="1"
      class="aspect-square h-full w-full"
      src="/next.svg"
      style="color: transparent"/>
  </span>
</div>

Hope this helps @angelhodar

raffizulvian avatar Aug 12 '23 03:08 raffizulvian

@raffizulvian Thank you so much for the detailed explanation!!! I think I just have to try to understand the asChild prop more in depth. Maybe @shadcn wants to add this to the Avatar documentation?

angelhodar avatar Aug 12 '23 06:08 angelhodar

You're welcome! To add a little bit, apparently, the reason why we need to provide src twice is the default behavior of Radix UI's Avatar. There is already someone who opens a discussion here.

raffizulvian avatar Aug 12 '23 10:08 raffizulvian

You could use Suspense for this

Code:

<Avatar>
    <Suspense fallback={<AvatarFallback>LG</AvatarFallback>}>
        <Image src="/next.svg" alt="logo" width={50} height={50} />
    </Suspense>
</Avatar>

wellsm avatar Apr 20 '24 14:04 wellsm

What folder should the image be placed? Inside public?

avi-mukesh avatar Apr 22 '24 14:04 avi-mukesh

What folder should the image be placed? Inside public?

Best to import it statically and let next sort it

import image from "./image.jpeg";
....
  <Image src={image} />

I wrote a little component that lets you use Avatar like a next Image:

// components/ui/nextAvatar.tsx
import { ImageProps, getImageProps } from "next/image";
import { omit, pick } from "lodash/fp";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar";

type Props = Omit<ImageProps, "fill">;

export default function NextAvatar(props: Props) {
  const imageProps = getImageProps({ width: 40, height: 40, ...props }).props;

  return (
    <Avatar
      className={props.className}
      style={pick(["width", "height"], imageProps)}
    >
      <AvatarImage
        {...omit(["blurWidth", "blurHeight", "style"], imageProps)}
        style={pick(["objectFit", "objectPosition"], imageProps.style)}
      />
      {imageProps.placeholder === "blur" && (
        <AvatarFallback style={imageProps.style} />
      )}
    </Avatar>
  );
}

totalolage avatar May 07 '24 11:05 totalolage