최근 피드백 서비스 '피드줍줍'의 프론트엔드는 성능 최적화 작업을 진행했습니다. 목표는 Lighthouse의 점수를 더 좋게(ㅎㅎ)만드는것이었는데, 목표를 이루진 못했지만 충분히 의미있는 개선들이 있었어서 공유해보고자합니다! 이번에는 Lighthouse에 반영되지 않았던 개선들과 왜 반영되지 않았는지에 대해서 설명해보려고 합니다.
시작하게 된 계기
피드줍줍을 사용해주시는 분들께서 가끔 버벅거린다는 피드백을 해주셨습니다. 실제로 확인해보니 main.js 파일이 무려 11.5MB나 되더라구요! 이건 분명히 문제가 있다고 생각해서 본격적인 최적화 작업에 들어갔습니다. 문제가 여기에만 있지 않을테니 할수있는한 문제를 찾아보고 최적화 작업들을 순서대로 진행해보았습니다.
시도한 성능 최적화들
1. 요청크기줄이기
소스코드 줄이기: CSS/JS minify, uglify적용하기

이미지 크기 줄이기:
- 이 이미지가 실제로는 30x30으로 쓰이는데 실제 크기는 214x214의 png였다.
- 그래서 이미지를 64x64로 리사이즈 하고, webp으로 수정하고 webp와 호환되지 않는 곳을 위해서 jpg를 만들어서 롤백 로직을 만들었다.
- 아래와 같이 이미지 크기가 무척 줄어든걸 확인할 수 있었다.



폰트크기 줄이기:
- self-host 웹폰트로 세팅을 바꾸었고
- woff2 포맷을 사용하도록 수정했고
- 웹폰트의 서브셋을 만들어서 세팅하였더니





다음과 같이 개선되었다.
소스코드 줄이기: 이전 main.js : 11.5MB / 이후 main.js : 2533KB
→ 약 78.5% 감소
이미지 줄이기 : 이전 프로필 사진: 62KB / 이후 프로필 사진: 1.4KB
→ 약 97.7% 감소
폰트크기 줄이기 : 이전 폰트: 4.769MB / 이후 폰트: 2.21MB
→ 약 54% 감소
2. 필요한 것만 요청하기
- lazy loading + Suspense를 사용해서 (router.tsx파일의 모든 페이지에 lazy loaing을 적용했다) main.js의 스크립트를 3.41 MB → 2.41MB로 감소시켰다.




3. 같은건 매번 새로 요청하지 않기
S3 업로드 시 cache-control 헤더 추가해서 캐싱 전략 사용하기
- 저희는 코드를 빌드하고 배포할때 aws의 codepipeline을 사용했는데 그때 buildspec 에서 명령어를 수정하는 방식으로 이부분을 개선했습니다.


react query의 캐싱 기능을 사용하여 최적화
이전: reactQuery를 사용하면서 staleTime과, cacheTime을 설정해주지 않았기 때문에 기본 값을 따라 staleTime으로 0을, cacheTime으로는 5분을 가졌었음.
이후: 각 페이지마다 다음과 같은 세팅을 만들어주었음.
| 페이지 | staleTime | cacheTime(gcTime) | 이유 |
| 피드백 대시보드 | 0초 | 5분 | 관리자 방 대시보드와 훅을 공유하고 있기 때문에 실시간성이 중요한 관리자 방 대시보드 캐시를 따름 |
| 관리자 방 목록 | 5분 | 15분 | 변동 빈도 낮음, 캐시 유지 유리 |
| 관리자 방 대시보드 | 0초 | 5분 | 실시간성이 중요, 캐시 fallback 유용 |
| 관리자 설정 | 1시간 | 24시간 | 정적 데이터, 캐시 재활용 적합 |
| QRcode | 24시간 | 24시간 | 정적 데이터, 캐시 재활용 적합 |
| 관리자 정보 가져오기 | 1분 | 5분 | 여러 관리자 계정으로 로그인 가능 |
4. 최소한의 변경만 일으키기
onboading 화면의 layoutShift를 없앰.
- 버튼이 들어가는 컨테이너에 최소 높이를 주어서 레이아웃 쉬프트를 없앰.



관리자 알림설정 토글 누를시 FrameDrop을 없앰.
- API 호출을 할때 토글 애니메이션이 동작해서 생긴 문제.
- API호출 뒤에 잠깐의 시간을 준뒤 애니메이션이 진행되도록 하여 프레임 드랍 해결



대시보드 페이지 불필요한 렌더링 개선
- 무한스크롤 로직에서 새롭게 데이터를 가져올때마다 맨 위에있는 피드백도 재렌더링 하는 문제 발생
- React.memo와 memo를 사용해서 해결



성능 최적화 결과
| 메인 번들 | 11.5MB | 2.3MB | 80% ↓ |
| 아바타 이미지 | 62KB | 1.7KB | 97% ↓ |
| 폰트 파일 | 4.8MB | 2.2MB | 54% ↓ |
| 초기 로딩 시간 | ~3초 | ~1초 | 67% ↓ |
Lighthouse가 그대로였던 이유
1. Lighthouse가 측정하는 건 특정 지표뿐이다
Lighthouse는 점수를 매길 때 Core Web Vitals와 연관된 소수의 지표를 중심으로 계산한다고 합니다.
- FCP (First Contentful Paint)
- LCP (Largest Contentful Paint)
- CLS (Cumulative Layout Shift)
- TBT (Total Blocking Time)
- SI (Speed Index)
즉, 네트워크 요청 크기나 번들 크기 자체는 점수에 직접 반영되지 않습니다. 파일을 80% 줄여도, 그게 곧바로 LCP나 TBT 개선으로 연결되지 않으면 점수 변화가 없을수밖에 없었던 것이라고 합니다.
2. 개선이 '사용자 체감' 중심이지, '지표 산출 구간' 중심은 아니었음
번들 크기 감소 (11.5MB → 2.3MB)
→ 실제 사용자 네트워크 환경에서는 빠르게 느껴지지만, Lighthouse는 시뮬레이션된 네트워크(Throttle)로 돌리기 때문에 절대적인 파일 크기 차이가 크게 반영되지 않을 수 있음.
이미지 최적화 (97% 감소)
→ 하지만 이 이미지가 LCP 요소(화면에서 제일 큰 이미지)가 아니라면, 점수에 영향 없음.
폰트 최적화 (54% 감소, woff2 적용)
→ 폰트는 보통 지연 로딩(font-display: swap)이 걸려 있으면 점수에 거의 영향이 없음.
React Query 캐싱 / S3 캐시 전략따라서 캐시를 통한 2회차 방문 최적화는 점수에 전혀 반영되지 않음.
→ Lighthouse는 첫 방문 시나리오만 측정함
LayoutShift 제거, FrameDrop 제거
→ 이건 분명 UX 개선이지만, CLS 지표에 잡힐 정도로 큰 쉬프트가 없었다면 점수 변화 없음.
따라서 저희가 이번에 진행했던 성능최적화는 확실히 의미있는 개선이지만, 점수화하는 지표에는 영향을 주지 않았고, 첫방문 시나리오네는 드러나지 않는 최적화였으며. 점수 자체가 시뮬레이션 기반이라 작은 개선은 오차 범위에 묻혔다는 이유로 점수가 그대로였다! 라고 결론 낼수있을것같습니다.
마무리
이번 최적화 작업을 통해 단순히 번들 사이즈를 줄이는 것뿐만 아니라, React의 렌더링 메커니즘과 웹 성능 전반에 대해 깊이 이해할 수 있었어요. 무엇보다 중요한 건 사용자 경험이라는 걸 다시 한번 느꼈습니다ㅎㅎ... 3초 기다리던 로딩이 1초로 줄어들면서 최적화의 필요성을 다시금 느끼게 된것같아요.
다음 단계에서는 실제 점수 향상에 영향을 주는 최적화, 즉 SEO와 Core Web Vitals 지표 개선에 집중해 보려고 합니다. 검색엔진과 Lighthouse가 중요하게 평가하는 LCP, CLS, 메타 태그, 구조화 데이터 같은 영역을 손봐서, 사용자 경험은 물론이고 검색 노출과 점수 개선까지 이어지는 최적화를 해볼 계획입니다!
참고 자료
'프론트엔드' 카테고리의 다른 글
| Sentry로 에러 모니터링 시스템 구축하기 in 피드줍줍 (0) | 2025.09.21 |
|---|---|
| AWS CodePipeline으로 자동 배포 시스템 구축하기 (0) | 2025.09.21 |
| React + PWA에서 FCM 알림 API 연동기 (0) | 2025.09.21 |
| GIF로 vscode 단축키 5분만에 습득하기 (mac) (0) | 2025.02.23 |
| Todo Tree로 깔끔하게 Todo, 요구사항 관리하기 (4) | 2024.11.05 |