RicoCheesethe studio log · v2.0
Live · KRRead posts
목록으로
뉴스PUBLISHED · 2026년 5월 19일·8 MIN READ

앱을 몰래 망가뜨리는 4가지 작은 실수 (4 Tiny Mistakes That Secretly Destroy App Performance)

커스텀 헤더, 코드 스플리팅, 의존성 패키지, 대형 배경 이미지 — 각각 별 것 아닌 것처럼 보이지만 쌓이면 앱을 느리게 만드는 실수들을 정리한다.

#javascript#angular#react#frontend
4 Tiny Mistakes That Secretly Destroy App Performance

개발자들은 보통 고성능 머신에서 빠른 CPU, 넉넉한 RAM, 빠른 인터넷을 쓰며 개발한다. 하지만 실제 사용자는 전혀 다른 환경에 놓여 있는 경우가 많다. 오래된 노트북, 저가형 Android 폰, 약한 Wi-Fi, 최악의 모바일 인터넷. 결과적으로 앱이 사용자의 10~30%에게 고통스러울 만큼 느린 상황이 벌어진다.

아래 예시들은 실제 프로젝트에서 직접 마주쳤거나 다른 개발자에게서 전해들은 사례다. 가상의 시나리오가 아니다.

1. 커스텀 헤더 → Preflight 요청 #

브라우저는 "단순하지 않은(non-simple)" 요청 — 예컨대 PUT·DELETE 메서드를 쓰거나 커스텀 헤더가 붙어 있을 때 — 에 앞서 추가적인 OPTIONS 요청을 먼저 보낸다. 이것이 preflight 요청이다.

누군가 3년 전에 X-Feature-Whatever 같은 커스텀 헤더를 추가했다고 가정하자. 그 헤더가 이미 쓸모없어졌더라도 코드에 남아 있으면, 무해해 보이는 GET 요청이 네트워크 호출 두 번으로 둔갑한다.

발표에서 공유된 실제 사례를 보면, 해당 커스텀 헤더가 수년 전 레거시였음에도 아무도 의문을 품지 않은 채 모든 리팩터를 살아남았다.

커스텀 헤더 없는 GET 요청:

GET request without custom header

커스텀 헤더가 있는 GET 요청 (preflight 포함):

GET request with custom header containing preflight request

요청 하나만 놓고 보면 큰 문제처럼 보이지 않는다. 그러나 앱 시작 시 수십 개의 호출이 발생하는 상황에서, 특히 느린 모바일 연결에서는 체감이 달라진다.

프론트엔드 전용 로직에만 커스텀 헤더를 쓰고 있다면, query param·cookie·로컬 상태·스타트업 시 한 번 fetch하는 설정 엔드포인트 같은 대안을 검토할 수 있다.

2. 아무 효과도 없는 코드 스플리팅 #

"우리는 이미 코드 스플리팅을 하고 있어요. lazy loading도 쓰고 있고요."

직접 감사(audit)한 Angular 애플리케이션은 외형상 잘 구조화되어 있었다. 모듈 분리, lazy loading, 제대로 된 아키텍처, 모범 사례(best practices)가 모두 갖춰져 있었다. 그럼에도 앱은 느렸다.

webpack-bundle-analyzer, source-map-explorer, rollup-plugin-visualizer, @next/bundle-analyzer 같은 도구로 번들 내부를 들여다본 결과를 보면, 모듈 분리는 이루어졌지만 각 모듈의 크기가 2 KB 수준이었다. 실제로 중요한 코드는 거의 모두 어디서든 import하는 하나의 거대한 shared 모듈 안에 들어 있었고, 결국 대부분이 main 번들에 포함된 것이다.

아래 코드는 겉으로는 문제없어 보인다.

untitled
ts
{
  path: 'admin',
  loadChildren: () =>
    import('./admin/admin.module').then(m => m.AdminModule)
}

하지만 AdminModule이 애플리케이션의 절반을 담은 거대한 shared 모듈을 import하고 있다면, lazy loading은 사실상 의미가 없다.

import()나 lazy 모듈을 사용한다고 해서 번들이 건강하다는 보장은 없다. 브라우저가 실제로 무엇을 내려받는지 항상 확인해야 한다.

3. 불필요한 런타임 의존성 #

npm 패키지 설치를 아무도 관리하지 않는 프로젝트에서 흔히 벌어지는 일이다.

  • 분석(analytics) SDK 세 개
  • 날짜 라이브러리 두 개
  • Moment.js 로케일 전체
  • 유틸 함수 하나 쓰려고 Lodash 전체 import
  • 전역으로 import된 Firebase
  • 아이콘 팩 세 개
  • 인터넷 절반을 조용히 끌어오는 "작고 가벼운 헬퍼 패키지"

실제로 본 어떤 앱은 세 가지 날짜 라이브러리를 동시에 로딩하고 있었다. 아이러니하게도 그 앱은 날짜를 거의 다루지 않았다. 개발자마다 선호하는 라이브러리가 달랐을 뿐이었다.

Lodash를 예로 들면, 아래 두 import는 번들 크기에서 차이가 난다.

untitled
ts
// 전체 패키지 import
import _ from 'lodash';

// 필요한 함수만 import
import debounce from 'lodash/debounce';

차이가 사소해 보여도, 여러 해에 걸쳐 성장한 엔터프라이즈 애플리케이션에서는 이 누적이 크다. tree shaking은 마법이 아니다(tree shaking is not magic).

노트에 줄이 그어진 텍스트 — 실수를 표시하는 메모 Photo by KATRIN BOLOVTSOVA on Pexels

4. 거대한 배경 이미지 #

대형 이미지가 나쁘다는 것은 모두가 안다. 그런데도 여전히 거대한 이미지가 프로덕션에 배포된다.

개발자 머신과 빠른 인터넷에서는 문제없어 보이는 배경 이미지가, 느린 기기에서는 체감 성능(perceived performance)을 크게 떨어뜨린다.

개선 방법으로는 AVIF 또는 WebP 포맷 사용, 적극적인 압축, 폴드 위(above the fold) 대형 히어로 이미지 지양, 중요하지 않은 시각 요소 lazy load, 진정으로 중요한 에셋만 preload 등이 있다.

마무리 #

앱 최적화는 끝이 없는 주제다. 이 글이 다루는 것은 표면의 일부에 불과하다. 하지만 핵심은 하나다 — 성능 문제는 종종 **천 번의 작은 베임(death by a thousand cuts)**이다.

불필요한 헤더 하나, 과도한 의존성 하나, "임시" shared 모듈 하나, 아무도 의심하지 않은 배경 이미지 하나.

개별적으로는 어느 것도 치명적이어 보이지 않는다. 함께 쌓이면, 오래된 기기나 느린 모바일 네트워크에서 앱이 둔하게 느껴지는 결과로 이어진다.

그리고 가장 무서운 부분은, 이 결정들이 처음 도입되었을 때는 모두 합리적으로 보였다는 점이다.


이 글은 위 출처의 내용을 바탕으로 작성된 초안입니다. 원문의 의견과 정보를 그대로 전달하며, 별도의 견해를 포함하지 않습니다.