"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을 보면 알 수 있다.
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}
/>
다음과 같은 문제가 발생할 수 있습니다:
- 반응형 문제:
width
와height
를 고정값으로 설정하면 반응형 디자인이 어려워집니다. - 비율 유지 문제: 이미지 비율이 1:1이 아닌 경우 원형으로 자르기가 어려워집니다.
- fallback 이미지 처리: 원본 이미지 위에 fallback 이미지를 중첩하기 어렵습니다.
- 스타일 충돌: 외부에서 전달된
className
이 내부 스타일과 충돌할 수 있습니다.
현재 구조의 장점
현재 구조는 다음과 같은 장점이 있습니다:
- 명확한 책임 분리: 컨테이너는 크기와 모양을 담당하고, 이미지는 콘텐츠 표시를 담당합니다.
- 유연한 크기 조정:
fill
속성과object-cover
를 사용하여 다양한 크기의 이미지를 적절하게 표시합니다. - 중첩 이미지 지원: 원본 이미지 로드 실패 시 fallback 이미지를 같은 위치에 표시할 수 있습니다.
- 스타일 확장성: 외부에서 전달된
className
을 적용하여 컴포넌트를 쉽게 확장할 수 있습니다.
이러한 이유로 현재의 CSS 구조는 아바타 컴포넌트에 적합한 접근 방식입니다. 이 구조는 Next.js의 Image
컴포넌트를 사용할 때의 모범 사례를 따르고 있으며, 다양한 사용 사례에 유연하게 대응할 수 있습니다.
'개발 > NEXTJS' 카테고리의 다른 글
nextjs 다크모드 적용시 깜빡이는거 해결 (0) | 2025.03.04 |
---|---|
사용자 이미지가 새로고침을 할 때 이전의 이미지가 보였다가 다시 되돌아 가는 현상 해결 (0) | 2025.03.02 |
nextjs jwt 토큰에서 만료 값 읽어와서 쿠키 설정하기 (0) | 2025.03.02 |
api router에서 params 가져오는 법 (0) | 2025.02.26 |
상대 경로와 절대 경로 (0) | 2025.02.26 |