Skip to content

refactor: 이미지 최적화#210

Merged
leemanjae02 merged 11 commits into
kusitms-com:mainfrom
leemanjae02:refactor/manjae/image-optimization
Apr 28, 2026
Merged

refactor: 이미지 최적화#210
leemanjae02 merged 11 commits into
kusitms-com:mainfrom
leemanjae02:refactor/manjae/image-optimization

Conversation

@leemanjae02
Copy link
Copy Markdown
Collaborator

@leemanjae02 leemanjae02 commented Apr 14, 2026

#️⃣연관된 이슈

#이슈번호

📝작업 내용

Next.js <Image> 기반 이미지 파이프라인 전반 최적화로 초기 로딩 대역폭과 체감 성능 개선.

적용 범위

  • 전역 설정: next.config.ts (4K 고해상도 지원 및 최적화 설정)
  • 공통 컴포넌트: ProjectCard — 프로젝트 리스트 페이지 전반의 S3 이미지 최적화
  • 핵심 페이지 컴포넌트: 메인(IntroSection, StatsSection), 아카이브(Banner), 프로젝트(Banner, ProjectTypeBanner)
  • 정적 리소스: public/ 하위 67개 래스터 이미지 (WebP 전환 완료)

무엇을 / 왜 했는지

작업 상세 내용 및 이유
기기별 JS 조건부 렌더링 도입 적용: IntroSection, Banner(Archive, Projects), StatsSection
이유: CSS display: none은 태그가 DOM에 존재하여 브라우저 프리로드 스캐너가 이미지를 미리 다운로드하는 문제를 야기함. 자바스크립트로 기기별 태그를 물리적으로 분리하여 모바일에서 데스크탑용 거대 이미지(w=3840) 로드를 원천 차단함.
4K 해상도 지원 및 sizes 세분화 적용: next.config.ts 및 전역 <Image> 컴포넌트
이유: 데스크탑 고해상도(2048, 3840) 설정을 복구하여 고화질 디스플레이에 대응하되, sizes 속성을 미디어 쿼리 단위로 쪼개어 브라우저가 현재 뷰포트에 가장 적합한 픽셀 데이터만 요청하도록 최적화함.
LCP 후보 blur placeholder 적용 적용: IntroSection(메인 그래픽), StatsSection(배경), ProjectTypeBanner(목록 배너)
이유: 페이지 진입 시 가장 먼저 보이는 핵심 이미지들에 초소형 Base64 데이터를 인라인으로 주입. 실제 이미지 다운로드 전 "흐릿한 이미지"를 즉시 페인트하여 사용자 체감 속도를 높이고 LCP 지표를 50% 이상 개선함.
PNG/JPG → WebP 무손실 변환 적용: public/ 하위 정적 리소스 67개 파일 전체
이유: 동일 화질 대비 용량이 30~40% 작은 최신 포맷으로 전환하여 대역폭 소모를 줄이고 로딩 속도를 가속화함.
ProjectCard S3 이미지 최적화 적용: ProjectCard(포스터, 로고)
이유: unoptimized 속성을 제거하여 S3 원본 이미지가 Vercel 이미지 파이프라인을 거치게 함. 이를 통해 자동 WebP 변환, 적정 사이즈 리사이징(w=640), CDN 캐싱을 적용하여 실전송량을 약 95% 절감함.

📊 최적화 수치 (Lighthouse v13.1 실측 평균)

메인 페이지 (/) 개선 지표

※ 측정 환경: Lighthouse CLI (No Throttling, 캐시 없음). 원본 사이트의 경우 모바일 내 데스크탑 이미지(w=3840) 로드 병목으로 인해 엄격한 검사 기준에서 LCP가 높게 측정됨.

지표 www.kusitms.com (Original) Vercel Preview (Optimized) 변화
LCP (최대 콘텐츠 페인트) 2,195ms 983ms -55.2%
Performance Score 94 99 +5점
TTFB (첫 응답 시간) 10ms 9ms 동일 수준
CLS (레이아웃 시프트) 0.0005 0.0000 매우 안정적

🖼️ 이미지 용량/전송량 개선

  • 정적 이미지 총 용량: 15.00MB → 9.59MB (-36.1%)
  • S3 이미지 실전송량: 약 12MB → 480KB (-95.7%) (WebP 변환 및 리사이즈 적용)
  • 모바일 네트워크 검증: 모바일 시뮬레이션 환경에서 데스크탑용 거대 이미지(w=3840) 호출 0건 확인 완료.

💬리뷰 요구사항

  • 고해상도(Retina 이상) 모니터에서 배너 이미지가 깨지지 않고 선명하게 노출되는지 확인 부탁드립니다.
  • IntroSection 등에서 기기 크기를 변경(Resize)할 때 레이아웃과 이미지가 정상적으로 전환되는지 확인 부탁드립니다.

- next.config에 deviceSizes/imageSizes 세분화
- Banner, IntroSection 등 fill 이미지에 sizes 지정
- 뷰포트별 적정 해상도 이미지 선택으로 네트워크 비용 절감
- sharp로 Main_Graphic/Background/Project 이미지를 12px base64 WebP로 인코딩
- src/constants/blurDataURL.ts에 정적 상수로 저장 (총 549B)
- IntroSection, StatsSection, ProjectTypeBanner에 placeholder="blur" 추가
- 로컬 LCP 평균 2,390ms → 803ms (-66.4%), Render Delay -83.6%
- ProjectCard Poster(300x190), Logo(95x95)에서 unoptimized 제거
- sizes 속성 추가하여 디바이스별 적정 해상도 요청
- S3 이미지가 _next/image 파이프라인 진입, WebP 자동 변환 + w=640 확인
- 상세/스토리 페이지는 Vercel 크레딧 소모 고려해 유지
- /projects/meetup Vercel Preview에서 S3 이미지 바이트 절감 실측
- 샘플 4개 평균 -95.7% (원본 PNG 267~851KB → WebP 16~34KB)
- Step 2/Step 3 LCP 기준값 표기 일관성 정리 (2,378ms로 통일)
- Step 4는 SVGO 효과 미미로 보류 결정 기록
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 14, 2026

@leemanjae02 is attempting to deploy a commit to the kusitms-homepage Team on Vercel.

A member of the Team first needs to authorize it.

@leemanjae02 leemanjae02 self-assigned this Apr 14, 2026
Copy link
Copy Markdown
Collaborator

@78-artilleryman 78-artilleryman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 next/Image에 있는 prop을 사용해서 최적화를 진행해주신 것 같습니다
근데 지금 이 프로젝트가 vercel에 무료 플랜으로 배포가 되어있을텐데 지금처럼 size prop를 이용하여 자동 리사이즈 / 반응형 사이즈 최적화를 해주게 되면 기본으로 제공하는 최적화 사용량을 초과하여 이미지 전부가 안 떠버리는 문제가 생길 수 있습니다

일단 전체적으로 이미지 확장자를 webp로 바꿔주어 최적화 사용량을 전부 소진을 안 할 수도 있다는 생각이 드는데 이건 나중에 접속자가 많아지는 34기 신규 회원 모집할때 vercel에 접속하여 홈페이지 모니터링 한 번 해보시는게 좋을 것 같습니다

Comment on lines 33 to 44
className="aspect-[300/190] w-full h-full object-cover"
width={300}
height={190}
unoptimized
sizes="(max-width: 768px) 50vw, 300px"
/>
);

const Logo = ({ src }: { src: string }) => (
<div className="absolute top-[-60px] right-[16px] desktop:top-0 desktop:right-[24px] w-[78px] h-[78px] desktop:w-[95px] desktop:h-[95px] flex items-center justify-center overflow-hidden rounded-full bg-white">
<Image src={src} alt="logo" width={95} height={95} unoptimized />
<Image src={src} alt="logo" width={95} height={95} sizes="95px" />
</div>
);
Copy link
Copy Markdown
Collaborator

@78-artilleryman 78-artilleryman Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

특히 이곳에 unoptimized은 제가 걸어두어서 히스토리를 잘 알고 있는데
이곳에서 정말 많은 최적화가 일어나서 이미지가 화면에 안 나오는 문제가 발생했었습니다
그래서 이곳에서는 최적화 옵션을 꺼두었습니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProjectCard에 unoptimized를 걸어두신 이유가 병현님이 말씀해주신 것처럼 버셀 무료 최적화를 많이 소모할 것 같아 걸어두신게 아닐까 생각했었습니다!
하지만 이 생각이 들었던게 최적화를 진행해본 이후라서 전 pr 리뷰 요청에 남겨두었으나 pr을 수정하면서 빼먹은 것 같습니다

@leemanjae02
Copy link
Copy Markdown
Collaborator Author

전체적으로 next/Image에 있는 prop을 사용해서 최적화를 진행해주신 것 같습니다 근데 지금 이 프로젝트가 vercel에 무료 플랜으로 배포가 되어있을텐데 지금처럼 size prop를 이용하여 자동 리사이즈 / 반응형 사이즈 최적화를 해주게 되면 기본으로 제공하는 최적화 사용량을 초과하여 이미지 전부가 안 떠버리는 문제가 생길 수 있습니다

근데 혹시 버셀에서 무료 최적화를 다 사용하게 되면 이미지 자체를 띄우지 못하는 문제가 발생하나요?

일단 전체적으로 이미지 확장자를 webp로 바꿔주어 최적화 사용량을 전부 소진을 안 할 수도 있다는 생각이 드는데 이건 나중에 접속자가 많아지는 34기 신규 회원 모집할때 vercel에 접속하여 홈페이지 모니터링 한 번 해보시는게 좋을 것 같습니다

넵! 알겠습니다 리뷰 감사합니다☺️

@78-artilleryman
Copy link
Copy Markdown
Collaborator

근데 혹시 버셀에서 무료 최적화를 다 사용하게 되면 이미지 자체를 띄우지 못하는 문제가 발생하나요?

https://vercel.com/docs/image-optimization/limits-and-pricing
여기서 사용량 설명과 초과하면 어떤 방식으로 표시가 되는지 설명이 되어있습니다 한 번 보시면 좋을 것 같아요

Copy link
Copy Markdown
Collaborator

@choihooo choihooo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

머지하기 전에 이전 사진들 지워주세요

Comment on lines +11 to +23
const [isTablet, setIsTablet] = useState<boolean | null>(null);

useEffect(() => {
const checkTablet = () => {
setIsTablet(window.innerWidth >= 768);
};
checkTablet();
window.addEventListener("resize", checkTablet);
return () => window.removeEventListener("resize", checkTablet);
}, []);

if (isTablet === null) return <div className="w-full h-[180px] bg-sky-100" />;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ismobile isTablet 이런거 다 모아서 훅으로 만드는게 좋아보입니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!

@leemanjae02 leemanjae02 merged commit 89da2f9 into kusitms-com:main Apr 28, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants