"use client";

import { CurrentUserType } from "@/types/authTypes";
import Image from "next/image";
import { useState } from "react";

export const AVATAR_SIZE = {
  sm: { class: "w-6 h-6", size: 24 },
  md: { class: "w-8 h-8", size: 32 },
  lg: { class: "w-12 h-12", size: 48 },
  xl: { class: "w-16 h-16", size: 64 },
  xxl: { class: "w-20 h-20", size: 80 },
  xxxl: { class: "w-24 h-24", size: 96 },
  xxxxl: { class: "w-32 h-32", size: 128 },
} as const;

export type AvatarSize = keyof typeof AVATAR_SIZE;

interface AvatarProps {
  user: Pick<CurrentUserType, "image" | "username"> | null | undefined;
  size?: AvatarSize;
  className?: string;
}

const Avatar = ({ user, size = "md", className = "" }: AvatarProps) => {
  const [imgError, setImgError] = useState(false);

  const handleError = () => {
    setImgError(true);
  };
  const { username, image } = user || {};
  const defaultImage = `https://ui-avatars.com/api/?name=${username}&format=png`;

  const imageUrl = !image ? defaultImage : image;

  return (
    <div
      className={`relative rounded-full overflow-hidden ${AVATAR_SIZE[size].class} ${className}`}
    >
      <Image
        src={imageUrl}
        alt={`${username || "User"}'s avatar`}
        fill
        sizes={`${AVATAR_SIZE[size].size}px`}
        className="object-cover"
        onError={handleError}
        quality={75}
      />
      {imgError && image && (
        <Image
          src={defaultImage}
          alt={`${username || "User"}'s fallback avatar`}
          fill
          sizes={`${AVATAR_SIZE[size].size}px`}
          className="object-cover"
          quality={100}
        />
      )}
    </div>
  );
};

export default Avatar;

 

이미지를 불러왔을 때 아무리 최적화가 되어있다 해도 흐리게 보이는 점에서 이상함을 느꼈다

보니깐 size항목을 그냥 className에 넣으면 되는 줄 알았는데(알아서 최적화 되는 줄 알고) 아니었다

그래서 AVATAR_SIZE에 size 까지 추가를 하고

sizes에 직접 넣었다. 그러니깐 어떤 size에서든 선명하게 나옴

 

image url

최적화가 제대로 됐는지 안됐는지 확인하려면 image url을 보면 알 수 있다.

URl에 w 파라미터가 포함되어 있다면 작동하고 있는 것이다.

지금은 supabase에서 이미지를 가지고 오고 있어서 파일 자체의 크기는 변화가 없는 것 같은데

 

최적화의 실제 이점
- 파일 크기가 비슷하더라도 Next.js 이미지 최적화는 여전히 중요한 이점을 제공합니다:
- 적절한 이미지 치수: 화면에 표시되는 크기에 맞는 이미지를 제공하여 메모리 사용량 감소
- 레이아웃 이동 방지: 이미지 크기를 미리 알고 공간을 확보하여 CLS(Cumulative Layout Shift) 감소
- 지연 로딩: 뷰포트에 들어올 때만 이미지를 로드하여 초기 페이지 로드 시간 단축
- 최신 형식 지원: 브라우저가 지원하는 경우 WebP나 AVIF와 같은 최신 형식 제공

 

용량의 변화가 없더라도 이런 장점이 있다고 한다.

 

그리고 네트워크 속도에 제한을 걸었을 때 하이드레이션 에러를 겪었었는데

위 코드와 같이 렌더링을 같게 하여 에러를 해결하였다.

 

Avatar 컴포넌트의 CSS 구조 설명

전체 CSS 구조 설명

<div
  className={`relative rounded-full overflow-hidden ${AVATAR_SIZE[size].class} ${className}`}
>
  <Image
    src={imageUrl}
    alt={`${username || "User"}'s avatar`}
    fill
    sizes={`${AVATAR_SIZE[size].size}px`}
    className="object-cover"
    onError={handleError}
    quality={75}
  />
  {/* 조건부 fallback 이미지 */}
</div>

1. 컨테이너 div의 CSS 클래스

className={`relative rounded-full overflow-hidden ${AVATAR_SIZE[size].class} ${className}`}
  • relative: 내부 Image 컴포넌트의 fill 속성이 작동하려면 부모 요소가 position: relative여야 합니다.
  • rounded-full: 원형 아바타를 만들기 위한 Tailwind CSS 클래스로, border-radius: 9999px를 적용합니다.
  • overflow-hidden: 이미지가 컨테이너를 벗어나지 않도록 합니다.
  • ${AVATAR_SIZE[size].class}: 선택한 크기에 따른 너비와 높이 클래스를 적용합니다(예: w-8 h-8).
  • ${className}: 외부에서 전달된 추가 클래스를 적용합니다.

2. Image 컴포넌트의 CSS

className="object-cover"
  • object-cover: 이미지 비율을 유지하면서 컨테이너를 채우도록 합니다. 이미지가 컨테이너보다 크거나 작을 때 적절하게 크기를 조정하고 필요한 부분을 자릅니다.

"그냥 Image에 다 넣으면 되는거 아니었어?" 에 대한 답변

Next.js의 Image 컴포넌트에 모든 스타일을 직접 적용하지 않고 컨테이너 div를 사용하는 이유는 다음과 같습니다:

1. fill 속성 사용 시 필요한 구조

fill 속성을 사용할 때는 부모 요소가 position: relative와 명시적인 크기를 가져야 합니다. 이 구조가 없으면 이미지가 제대로 표시되지 않습니다.

2. 일관된 크기 제어

컨테이너 div에 크기를 지정하면 내부 이미지의 크기를 일관되게 제어할 수 있습니다. 이는 특히 여러 크기의 아바타를 지원할 때 유용합니다.

3. 중첩된 이미지 처리

원본 이미지와 fallback 이미지를 같은 위치에 중첩하려면 공통 컨테이너가 필요합니다.

4. 스타일 캡슐화

컨테이너를 사용하면 아바타 관련 스타일을 캡슐화하고, 외부에서 전달된 className을 적용할 수 있는 명확한 지점을 제공합니다.

직접 Image에 스타일을 적용했을 때의 문제점

만약 컨테이너 없이 Image 컴포넌트에 직접 모든 스타일을 적용한다면:

// 이렇게 하면 안 되는 예시
<Image
  src={imageUrl}
  alt={`${username || "User"}'s avatar`}
  width={AVATAR_SIZE[size].size}
  height={AVATAR_SIZE[size].size}
  className={`rounded-full ${className}`}
  onError={handleError}
  quality={75}
/>

다음과 같은 문제가 발생할 수 있습니다:

  1. 반응형 문제: widthheight를 고정값으로 설정하면 반응형 디자인이 어려워집니다.
  2. 비율 유지 문제: 이미지 비율이 1:1이 아닌 경우 원형으로 자르기가 어려워집니다.
  3. fallback 이미지 처리: 원본 이미지 위에 fallback 이미지를 중첩하기 어렵습니다.
  4. 스타일 충돌: 외부에서 전달된 className이 내부 스타일과 충돌할 수 있습니다.

현재 구조의 장점

현재 구조는 다음과 같은 장점이 있습니다:

  1. 명확한 책임 분리: 컨테이너는 크기와 모양을 담당하고, 이미지는 콘텐츠 표시를 담당합니다.
  2. 유연한 크기 조정: fill 속성과 object-cover를 사용하여 다양한 크기의 이미지를 적절하게 표시합니다.
  3. 중첩 이미지 지원: 원본 이미지 로드 실패 시 fallback 이미지를 같은 위치에 표시할 수 있습니다.
  4. 스타일 확장성: 외부에서 전달된 className을 적용하여 컴포넌트를 쉽게 확장할 수 있습니다.

이러한 이유로 현재의 CSS 구조는 아바타 컴포넌트에 적합한 접근 방식입니다. 이 구조는 Next.js의 Image 컴포넌트를 사용할 때의 모범 사례를 따르고 있으며, 다양한 사용 사례에 유연하게 대응할 수 있습니다.

+ Recent posts