작년 8월부터 합류하여 개발하고 있던 모아동 프로젝트에서 이번에 이미지 최적화 작업을 시도하게 되었습니다
이 프로젝트는 현재 아래의 링크를 통해 확인해볼 수 있습니다!
모아동
모아 동아리! 부경대학교 모든 동아리를 한눈에!
www.moadong.com
부경대학교 학생들을 위해 에브리타임에서 일일히 찾아봐야하는 번거로움을 줄이고자 동아리를 한눈에 볼 수 있는 웹 사이트를 만들게 되었습니다! 현재 Android 및 Ios에서 앱도 출시되어 있어 확인해볼 수 있습니다!
이번 2026년 1학기 신입생들이 많이 사용해주길 바라며 방학인 지금,
사용자를 고려하여 각종 버그를 고치고 리펙토링하고 있습니다!
오늘의 개발 내용
모아동 프로젝트의 동아리 상세페이지에는 아래의 이미지와 같이 소개 내용 / 활동 사진으로 총 두 개의 탭이 있습니다

여기서 문제는 소개 내용 탭에서 활동 사진 탭으로 전환할 때 발생했습니다
활동 사진 탭에 진입했을때 피드의 형태로 이미지가 보이게 되는데, 이미지가 바로 보이지 않고, 빈 영역이 잠시 보였다가 이미지가 하나씩 뜨거나 로딩이 오래 걸리는 현상이 반복되었습니다
이 부분이 사용성 측면에서 거슬리는 부분이었네요... 최적화는 처음이라 걱정이 많았습니다만... 원인이 무엇인지 분석하는 것에 가장 시간을 많이 사용했던 것 같네요!
이미지 로딩 속도가 느린 원인이 무엇일까
이미지가 로딩되는 것에 어떤 원인이 있을까 고민해보았습니다
가장 처음에는 이미지가 현재 어떻게 로딩되고 있는지 몰라서 이미지 크기 문제라고 생각했습니다
일부 동아리에서 크게 지연이 발생했는데, 사용되는 이미지의 용량이 피드에 사용되는 이미지치고 2MB 이상으로 꽤 컸어서 그쪽으로 생각이 들었습니다
이미지 용량 문제라면 당장 서버쪽에서 해결할 문제이기 때문에,
여러 방법을 찾아보며 프론트 측에서 해볼 수 있는 방식을 찾아보게 되었습니다
개발을 위해 현재의 코드와 상태를 확인하며 알아본 결과,
상세페이지 내에서 소개 내용과 활동 사진 탭에 대해 라우트를 분리한 것 때문이라는 생각이 들었습니다
기존 구조에서는 활동 사진 탭이 클릭되는 순간에 ClubFeed 컴포넌트가 처음 마운트되고 있었습니다
이 시점에서 <img src="..."> 태그가 생성되고 있었기에, 이 순서가 끝난 후 이미지 네트워크 요청이 시작되는 구조였던 것입니다
데이터는 동아리별 상세페이지에 진입 시, 소개 내용, 활동 사진 등의 데이터를 한 번에 불러오지만, 이미지 요청은 탭 진입 시 요청이 시작되는 구조로 인해 탭 전환 시 체감 지연이 발생하고 있던 것이었습니다
따라서 이러한 생각을 가지고 여러 방식을 시도해보았습니다!!
최적화를 위한 여러 시도
1차 시도 : TanStack Query prefetchQuery
맨 처음에는 상세페이지에 진입했을때, 활동사진 데이터를 미리 불러오지 못 해서 지연이 발생한다고 생각했습니다...
바보같은 생각이었죠... 코드를 좀 더 확인하고 진행했다면 실수하지 않았을텐데, TanStack Query도 처음 사용해봐서 직접 적용하면서 확인해보자라는 생각이었습니다
prefetchQuery를 적용해봤지만, 아무런 변화가 없었습니다
이미지 로딩 속도, 체감 UX도 달라지는 것이 거의 없었고, 이를 통해 prefetchQuery에 대해 좀 더 알아보면서 데이터만 가져올 뿐, <img> 태그를 생성하지 않는다는 것을 알게되었습니다
이미 데이터는 상세페이지 진입 시점에 잘 가져오고 있었고, 문제는 <img> 태그 생성 시점이었는데, 이 방법으로는 이미지 로딩 시점에 변화가 없어 유의미한 결과를 얻을 수 없었습니다
2차 시도 : 이미지 preload(new Image())
1차 시도에 실패하고 정확한 원인을 알게되어 좀 더 확신을 가지고 이번에는 이미지 리소스를 직접 preload하고자 했습니다
상세페이지 진입 시점에 new Image().src = url의 형태로 활동 사진 이미지를 백그라운드에서 미리 다운로드하도록 한다면
이미지 네트워크 요청이 탭 클릭 이전에 시작될 것이고 따라서 활동 사진 탭 진입 시 빠르게 로딩될 것이라 기대했습니다
그러나, 여전히 중간중간 끊기는 느낌이 있었고 일정하게 빠르지 않았습니다
매번 진입할 때마다 속도가 달랐고, 크게 체감되는 느낌이 아니었습니다
이 역시 좀 더 생각해보니,,, 이미지 객체를 미리 만들어 다운되도록 하더라도 이전에 생각한 것처럼 ClubFeed 컴포넌트 자체는 활동 사진 탭을 클릭했을 때 처음 렌더링되기 때문에... 이미지가 있더라도 렌더링 지연이 그대로 있을 수 밖에 없겠구나라는 결론에 도달했습니다...
최종 선택 : 컴포넌트 프리마운트 (Hidden DOM)
분명 원인을 알겠고, 어떻게 하면 되는지 알겠는데, 그 방법을 어떻게 시도해야할지 잘 모르겠어서 시간이 많이 걸렸던 것 같네요..
원하는 의도와는 다른 방법을...계속 시도해보면서 의도한 방식을 시도할 수 없는건가...라는 고민이 들었던 것 같아요
그래서 시도하기 전, 좀 더 많이 알아보고 이 방식을 진행했습니다
결론은 이미지를 포함한 ClubFeed 컴포넌트 자체를 상세페이지 진입 시, 미리 렌더링하도록 하는 것입니다!
생각해보니...계속 상세페이지 내의 ClubFeed 컴포넌트를 어떻게 해야한다고 생각했는데,
상세페이지 진입 시점에 두 개의 컴포넌트 ClubIntroContent와 ClubFeed 컴포넌트를 모두 마운트하는 방식을 생각했습니다!!
처음 상세페이지에 진입할때, 기존에는 소개 내용 탭만 먼저 렌더링되기 때문에, ClubIntroContent 컴포넌트만 마운트되고 있었는데 활동 사진 탭의 ClubFeed 컴포넌트도 함께 마운트시키되 보이지만 않도록 하는 것입니다
분명, 이 방식으로 바꾼다면 초기 렌더링 시 메모리 사용이 증가할 것이고 보이지 않는 컴포넌트까지 함께 마운트되는 것이기 때문에 비용적으로나 구조적으로나 조금 고민이 되었습니다
그러나, 동아리 상세페이지에서 활동 사진 탭의 내용은 ClubFeed로 이미지 로딩이 지연되는 것이 더 큰 문제라고 생각했습니다
또한 현재 최대 이미지 수가 15장으로 제한되어 있는 것을 고려했을 때, 비용보다도 UX 개선 효과가 더 큰 이점이 될 것이라고 판단했습니다
따라서 상세페이지 진입 시점에 두 컴포넌트를 모두 마운트한 후,
탭 전환을 display: none / block 으로만 제어하도록 하여 구조를 변경했습니다
<img> 태그가 이미 DOM에 존재하게 되었고, 탭 클릭을 하더라도 이미 마운트가 되어있는 상태이므로 재마운트가 발생하지도 않고, 따라서 렌더링 지연 없이 활동사진이 즉시 표시되는 것을 확인할 수 있었습니다! 체감 UX가 확실히 개선되는 것을 볼 수 있었네요! 드디어 성공해서 너무 기쁩니다ㅏ!!!!
아래 동영상은 왼쪽이 개선 전이구... 오른쪽이 개선 후 이미지 로딩 속도 차이입니다!
확인해보시면! 정말 속도가 많이 빨라져서 너무 뿌듯하네요ㅎㅎ
생각보다 적은 코드 수정으로 많은 효과를 준 것이 좋은 것 같습니다
추가 최적화 : eager / lazy 로딩 분기
프리마운트 상태에서도 모든 이미지를 eager로 로딩할 필요가 없다고 생각했습니다
화면의 크기에 따라 활동사진이 한 번에 보이는 개수가 달라지기 때문에,
데스크탑과 노트북은 한 번에 15장이 모두 보이는 반면,

모바일에서는 위의 이미지처럼 상단의 일부만이 노출되기 때문에,
eager을 최소화하는 것이 네트워크 비용적인 면에서 좋을 것이라 판단했습니다!
이 임계값을 결정하는 것에 useMemo를 처음에는 사용했었는데, 리뷰를 통해 연산 비용이 크지 않을 때는 사용하지 않는 것이 좋다는 것도 알게되었고, 좀 더 간단하게 코드를 수정할 수 있었어요!
기기별 eager 로딩할 이미지 개수도 단순 숫자로 작성했는데,
이 역시 리뷰를 통해서 다른 분들이 봤을 때는 맥락이 없어 매직 넘버로 보일 수 있다는 것을 깨달았습니다
상수로 분리하는 것의 필요성을 느끼게 된 것 같습니다!
아직 남아 있는 문제가 있는 것 같다...
프론트엔드 단에서 할 수 있는 최적화는 거의 다 적용해본 듯 합니다...!
그럼에도 일부 이미지는 여전히 느릴 때가 있는 것 같습니다
제가 찾아본 결과, 원인은 2MB 이상 되는 일부 용량이 매우 큰 이미지들 때문인 것 같았습니다
이 부분은 서버 측에서 이미지 리사이즈 작업 또는 CDN 이미지 변환 등의 작업을 한다면 개선이 될 것 같아
PR을 통해서 백엔드에게 알려 개선할 수 있으면 좋겠다고 알리며 마무리했습니다
이번 작업을 통해 여러 방식을 시도해보았는데, 실패할 때마다 힘들기도 했고,
시간도 오래 걸렸고 해결이 안 되면 어떡하나라는 걱정도 있었습니다
그래도 결국 성공했고, 계속 고민했기 때문인 것 같습니다! 또한 늦게 성공한 것에는 원인 분석을 처음부터 확실히 못 한 것이 큰 것 같아서 앞으로는 좀 더 코드를 꼼꼼히 읽고 원인 파악을 제대로 하는 것에 큰 비중을 둬야겠다고 생각하게 된 것 같습니다
제가 모아동 웹 사이트를 사용해보면서도 활동사진 이미지가 너무 늦게 로드되고, 특히 모바일에서는 더 심한 것이 계속 마음에 걸렸는데, 제가 이 작업을 하게 되면서 이미지 로딩 지연이 사라져서 좋았던 것 같습니다!😄
많은 리뷰어 분들이 의견을 남겨주시고, 적은 수정을 통해 큰 임팩트를 냈다고 칭찬해주셔서 기분이 좋았고,
코드를 단순화하는 것에도 도움이 정말 많이 되었던 것 같아요
저한테는 코드 리뷰가 시간도 오래 걸리고 어려운 점이 많았는데, 몰랐던 것을 알게되고 개선할 수도 있고
여러 방면에서 좋은 점이 참 많은 것 같다는 생각이 들었어요
얻은게 많은 작업이었던 것 같습니다! 다음 포스팅은 리뷰를 통해 알게된 Lighthouse를 통한 성능 개선 효과를 수치로 확인해보고 관련 글을 작성해보려 합니다~
'프로젝트 > 모아동' 카테고리의 다른 글
| [모아동] 앱 버전 관리를 위해 WebView 라우트를 분리한 이유와 구현 방법 (0) | 2026.02.06 |
|---|---|
| [모아동] 웹에서는 괜찮았는데 앱에서는 깨졌다 - WebView와 앱 버전의 문제 (1) | 2026.02.01 |
| [모아동] 탭 전환 UX 개선을 Lighthouse로 측정하려다 막혔던 이유 (0) | 2026.01.31 |