로드 시간, JS 크기, Core Web Vitals에 대한 명확한 한계를 정하고 빠른 감사와 우선 규칙으로 웹 앱 성능을 유지하는 방법.

성능 예산은 빌드 전에 합의하는 한계 집합입니다. 시간 한계(페이지가 얼마나 빠르게 느껴지는지), 크기 한계(얼마나 많은 코드를 배포하는지), 또는 요청 수·이미지·서드파티 스크립트 같은 단순한 캡일 수 있습니다. 한계를 넘으면 “나중에 고치면 되지”가 아니라 깨진 요구사항으로 다룹니다.
속도가 보통 나빠지는 이유는 배포할수록 추가되기 때문입니다. 새 위젯 하나가 JavaScript, CSS, 폰트, 이미지, API 호출을 더하고 브라우저의 작업량을 늘립니다. 작은 변경도 쌓이면 앱이 무거워졌다고 느껴지고, 특히 중급형 폰과 느린 네트워크에서는 체감이 큽니다.
의견은 보호해주지 않습니다. 한 사람은 "내 랩톱에서는 괜찮아 보인다"고 하고 다른 사람은 "느려"라고 하면 팀은 논쟁합니다. 예산은 논쟁을 끝내고 성능을 측정하고 강제할 수 있는 제품 제약으로 바꿉니다.
이 점에서 Addy Osmani의 사고 방식과 맞닿습니다: 성능을 디자인 제약이나 보안 규칙처럼 다루세요. 보안을 ‘하려고’ 하거나 레이아웃이 ‘보이길 바라지’ 않습니다. 기준을 정하고 지속적으로 검사하며 그것을 깨는 변경을 차단합니다.
예산은 여러 실무적 문제를 한 번에 해결합니다. 기능 추가의 비용을 명확히 하고(기능을 추가하면 다른 곳에서 비용을 지불해야 함), 회귀를 초기에 잡고(수정이 저렴할 때), 모두에게 같은 "충분히 빠른" 정의를 제공합니다. 또한 출시 직전의 대책없이 발생하는 패닉을 줄여줍니다.
예산이 필요한 시나리오는 이렇습니다: 대시보드 보기를 위해 풍부한 차팅 라이브러리를 추가합니다. 모든 사용자에게 배포되어 메인 번들이 커지고 첫 번째 의미 있는 화면이 늦어집니다. 예산이 없으면 기능이 "작동한다"는 이유로 넘어갑니다. 예산이 있으면 팀은 선택해야 합니다: 차트를 지연 로드하든지, 라이브러리를 대체하든지, 보기를 단순화하든지.
특히 Koder.ai와 같은 채팅 기반 빌드 워크플로로 앱을 빠르게 생성·반복할 수 있을 때 이 문제는 더 중요해집니다. 속도는 훌륭하지만, 추가 의존성이나 UI 장식을 눈치채지 못하고 배포하기도 쉽습니다. 예산은 빠른 반복이 느린 제품으로 변하는 것을 막아줍니다.
모든 것을 측정하고 아무도 책임지지 않으면 성과가 실패합니다. 실제 사용자에게 중요한 한 페이지 흐름을 골라 예산의 기준점으로 삼으세요.
좋은 시작점은 전환이나 일상 업무에 영향을 주는 주요 여정입니다. 예: "홈에서 회원가입", "로그인 후 대시보드 첫 로드", "결제 및 확인" 같은 것들입니다. 대표적이고 빈번한 흐름을 고르세요. 극단 케이스는 피하세요.
앱은 당신의 랩톱에서만 돌지 않습니다. 빠른 기기에서 괜찮아 보이는 예산이 중급형 폰에서는 느리게 느껴질 수 있습니다.
먼저 하나의 기기 클래스와 하나의 네트워크 프로파일을 정하세요. 단순하게 유지하고 모두가 외울 수 있는 문장으로 적어두세요.
예시: 지난 2~3년 내 출시된 중급형 Android 폰, 이동 중인 4G, 콜드 로드를 측정하고 이후 주요 내비게이션 하나를 테스트, 사용자 대부분이 있는 같은 지역에서 측정.
최악의 경우를 고르는 게 아니라 실제 최빈 케이스를 골라 최적화할 수 있도록 하는 것입니다.
숫자는 비교 가능할 때만 의미가 있습니다. 한 번은 "확장 프로그램이 켜진 MacBook의 Chrome"이고 다음은 "스로틀된 모바일"이면 추세는 노이즈입니다.
예산 검사용으로 하나의 기준 환경을 정하고 지키세요: 동일한 브라우저 버전, 동일한 스로틀 설정, 동일한 테스트 경로, 동일한 캐시 상태(콜드 또는 웜). 실제 기기를 쓰면 기기 모델도 동일하게 사용하세요.
이제 "충분히 빠르다"의 정의를 완벽한 데모가 아니라 행동으로 적으세요. 예: "사용자가 빠르게 콘텐츠를 읽기 시작할 수 있다" 또는 "로그인 후 대시보드가 반응적으로 느껴진다" 같은 문장을 하나나 두 개의 지표로 번역하고 예산을 설정하세요.
예산은 사용자가 느끼는 부분과 팀이 제어할 수 있는 부분을 모두 포함할 때 가장 효과적입니다. 경험 지표(사용자가 느끼는 속도)와 리소스·CPU 한계(속도가 느려진 이유)를 섞어야 합니다.
이들은 실제 사용자에게 페이지가 어떻게 보이는지를 추적합니다. 가장 유용한 것들은 Core Web Vitals와 직접 연결됩니다:
타이밍 예산은 사용자의 불만과 직접 연결되므로 내비게이션의 북극성이 됩니다. 하지만 무엇을 고쳐야 할지는 항상 알려주지 않기 때문에 아래 유형들도 필요합니다.
이들은 빌드와 리뷰에서 강제하기 쉽습니다. 구체적이기 때문입니다.
무게 예산은 전체 JavaScript, 전체 CSS, 이미지 무게, 폰트 무게 같은 것을 제한합니다. 요청 예산은 총 요청 수와 서드파티 스크립트를 제한해 네트워크 오버헤드와 태그·위젯·트래커로 인한 ‘깜짝’ 작업을 줄입니다. 런타임 예산은 롱 태스크, 메인 스레드 시간, 하이드레이션 시간(특히 React)을 제한해 중급형 폰에서 페이지가 ‘무겁게’ 느껴지는 이유를 설명합니다.
실용적 예: 번들 크기는 괜찮아 보이는데 새 캐러셀이 무거운 클라이언트 렌더링을 추가해 페이지가 로드되더라도 필터 탭을 누르면 끈적거릴 수 있습니다. 이런 경우 "시작 시 X ms 초과하는 롱 태스크 없음" 또는 "하이드레이션이 중급 기기에서 Y초 내 완료" 같은 런타임 예산이 도움이 됩니다.
가장 강한 접근법은 이들을 하나의 시스템으로 보는 것입니다: 경험 예산이 성공을 정의하고, 크기·요청·런타임 예산이 릴리스를 정직하게 유지하며 "무엇이 바뀌었나?"를 쉽게 답하게 합니다.
너무 많은 한계를 걸면 사람들이 신경을 끕니다. 사용자 체감과 맞는 3~5개의 예산을 골라 PR이나 릴리스마다 측정할 수 있게 하세요.
실무적 시작 세트(숫자는 나중에 조정):
두 단계의 임계값은 합리성을 유지합니다. "경고"는 흐름이 흐려지고 있음을 알리고, "실패"는 릴리스를 차단하거나 명시적 승인을 요구합니다. 이렇게 한계는 실제가 되면서도 매번 큰일이 되지는 않습니다.
예산을 한 곳에 적어두세요: 커버되는 페이지/흐름, 측정 위치(로컬 감사, CI, 스테이징 빌드), 사용 기기 및 네트워크 프로파일, 그리고 지표 정의(field vs lab, gzip vs raw, 라우트 수준 vs 앱 전체)를 정확히 적어두세요.
반복 가능한 베이스라인부터 시작하세요. 한두 개 핵심 페이지를 골라 같은 기기와 네트워크로 여러 번 테스트하세요. 테스트는 최소 세 번 실행해 중앙값을 기록하면 이상치가 방향을 잘못 잡지 못합니다.
간단한 베이스라인 시트에 사용자 지표와 빌드 지표를 모두 포함하세요. 예: 페이지의 LCP와 INP, 빌드의 전체 JavaScript 크기와 이미지 총 바이트. 이렇게 하면 예산이 실체 있게 느껴집니다 — 연구실 추정치가 아니라 실제로 무엇을 배포했는지 볼 수 있습니다.
오늘보다 약간 더 나은 수준으로 예산을 설정하세요. 판타지 수치를 피하고 현재 중앙값에서 5~10% 개선을 목표로 하는 것이 현실적입니다. 예: 현재 베이스라인에서 LCP가 3.2s라면 곧바로 2.0s로 내리려 하지 말고 3.0s로 시작해 그것을 유지한 뒤 점차 강화하세요.
각 릴리스 전에 빠른 검사를 추가하세요. 사람들이 건너뛰지 않을 정도로 빠르게 유지하세요. 간단한 버전은: 합의한 페이지에서 단일 페이지 감사를 실행하고 JS 또는 이미지가 예산 초과면 빌드 실패, 커밋별 결과를 저장해 언제 바뀌었는지 확인, 항상 같은 URL 패턴으로 테스트(무작위 데이터 없음).
침해는 주간으로 검토하세요. 불만이 생길 때만 보는 것이 아니라 침해를 버그처럼 다루세요: 원인이 된 변경을 식별하고 지금 무엇을 고칠지, 무엇을 스케줄할지 결정하세요. 한계를 점진적으로 강화하되 몇 번의 릴리스에서 유지한 뒤에만 더 타이트하게 만드세요.
제품 범위가 바뀌면 예산도 의도적으로 업데이트하세요. 새 분석 도구나 무거운 기능을 추가했다면 무엇이 늘었는지(크기, 요청, 런타임), 나중에 어떻게 갚을지, 언제 예산이 복구될지를 적으세요.
예산은 빠르게 검사할 수 있어야만 도움이 됩니다. 10분 감사의 목적은 완벽한 수치를 증명하는 것이 아니라 마지막 좋은 빌드와 비교해 무엇이 바뀌었는지 파악하고 무엇을 먼저 고칠지 결정하는 것입니다.
대표 페이지 하나로 시작한 다음 매번 같은 빠른 검사를 실행하세요:
보통 네트워크 워터폴과 메인 스레드 타임라인 두 가지가 답을 줍니다.
워터폴에서 핵심 경로를 지배하는 요청을 찾으세요: 거대한 스크립트, 차단하는 폰트, 늦게 시작되는 이미지 등. LCP 리소스가 일찍 요청되지 않으면 서버가 아무리 빠르더라도 LCP 예산을 맞출 수 없습니다.
타임라인에서 롱 태스크(50ms 이상)를 찾아보세요. 시작 시점에 롱 태스크가 몰려 있으면 초기 로드에서 JavaScript가 과도하다는 신호입니다. 한 덩어리가 크면 라우팅 문제거나 공유 번들이 시간이 지나면서 커진 경우입니다.
빠른 감사는 매번 실행 환경이 달라 실패합니다. 몇 가지 기본 정보를 캡처하세요: 페이지 URL과 빌드/버전, 테스트 기기와 네트워크 프로파일, LCP 요소 설명, 추적하는 주요 숫자(예: LCP, 전체 JS 바이트, 요청 수), 그리고 가장 큰 원인에 대한 짧은 메모.
데스크톱 테스트는 빠른 피드백과 PR 체크에 적합합니다. 예산에 근접했거나 페이지가 끈적거리거나 사용자가 모바일에 치우쳐 있으면 실제 기기를 사용하세요. 모바일 CPU는 롱 태스크를 명확히 드러내며, 많은 "내 랩톱에서는 괜찮음" 릴리스가 모바일에서 실패합니다.
예산이 실패했을 때 최악의 선택은 "모두 최적화"입니다. 반복 가능한 삼자(우선순위) 규칙을 사용해 각 수정이 명확한 효과를 내도록 하세요.
사용자가 가장 먼저 느끼는 것부터 시작해 세밀한 튜닝으로 내려가세요:
팀이 새 대시보드를 배포했고 갑자기 LCP 예산을 놓쳤습니다. 캐시 헤더를 먼저 조정하는 대신 LCP 요소가 전체 폭 차트 이미지라는 것을 발견했습니다. 이미지를 리사이즈하고 가벼운 포맷으로 제공하며 초기에는 필요한 것만 로드했습니다. 다음으로 모든 라우트에서 큰 차팅 라이브러리가 로드되는 것을 발견하고 분석 페이지에서만 로드하고 서드파티 지원 위젯은 첫 상호작용 이후로 지연했습니다. 하루 만에 대시보드는 예산 안으로 돌아왔고 다음 릴리스에는 "무엇이 바뀌었나"에 대한 명확한 답이 있었습니다.
가장 큰 실패 원인은 예산을 일회성 문서로 취급하는 것입니다. 예산은 쉽게 검사할 수 있어야 하고 무시하기 어렵고 배포 방식과 연동되어야만 작동합니다.
대부분 팀은 몇 가지 함정에 빠집니다:
자주 있는 패턴은 "작은" 기능이 새 라이브러리를 불러오는 경우입니다. 번들이 커지고 느린 네트워크에서 LCP가 1초 느려지지만 지원 티켓이 오기 전까지 아무도 모릅니다. 예산은 코드 리뷰 단계에서 그 변화를 눈에 보이게 만듭니다.
단순하게 시작하고 검사를 일관되게 유지하세요. 사용자 경험에 연결되는 2~4개의 예산을 선택하고 점차 강화하세요. 테스트 설정을 고정하고 적어두세요. 가능하면 최소 하나의 실제 사용자 신호를 추적하고 연구실 테스트는 원인을 설명하는 데 사용하세요. 의존성이 의미 있는 무게를 더하면 짧은 메모를 요구하세요: 비용이 무엇이고 무엇을 대체하며 왜 가치가 있는지. 가장 중요하게는 예산 검사를 일반 릴리스 경로에 넣으세요.
예산이 지속적 마찰처럼 느껴지면 보통 두 가지 중 하나입니다: 현재에 비해 비현실적이거나 실제 결정과 연결되어 있지 않습니다. 이 두 가지를 먼저 고치세요.
작은 팀이 일주일 만에 React 분석 대시보드를 배포했습니다. 처음에는 빠르게 느껴졌지만 매주 금요일 릴리스마다 조금씩 더 무거워졌습니다. 한 달 뒤 사용자는 첫 화면이 "멈춘다"고 말하기 시작했고 필터가 느리다고 보고했습니다.
그들은 "충분히 빠른지" 논쟁을 그만두고 사용자에게 보이는 것에 묶인 예산을 적어두었습니다:
첫 실패는 두 군데에서 드러났습니다. 초기 JS 번들이 차트, 날짜 라이브러리, UI 키트가 추가되며 커졌고, 대시보드 헤더 이미지는 "임시로" 더 큰 파일로 바뀌어 LCP가 한계를 넘었습니다. INP는 필터 변경마다 무거운 리렌더와 계산이 메인 스레드를 차지해 악화되었습니다.
그들은 빠른 승리를 주는 순서로 고쳤습니다:
이미지 리사이즈·압축, 명시적 이미지 치수 설정, 차단 폰트 회피로 LCP를 회복.
사용하지 않는 라이브러리 제거, 비핵심 라우트 분리, 차트 지연 로드로 초기 JS 축소.
비싼 컴포넌트 메모이제이션, 타이핑 필터 디바운스, 무거운 작업을 핵심 경로 밖으로 이동해 INP 개선.
모든 릴리스에 예산 검사를 추가해 지표가 깨지면 릴리스가 대기하도록 함.
두 번의 릴리스 후 LCP는 같은 테스트 기기에서 3.4s에서 2.3s로 떨어졌고, INP는 약 350ms에서 180ms 이하로 개선되었습니다.
예산은 사람들이 매번 같은 방식으로 따를 수 있을 때만 도움이 됩니다. 작게 유지하고, 적어두고, 배포 과정의 일부로 만드세요.
몇 가지 지표를 골라 "경고 vs 실패" 임계값을 설정하고 테스트 방법(기기, 브라우저, 네트워크, 페이지/흐름)을 정확히 문서화하세요. 현재 최상의 릴리스에서 베이스라인 보고서를 저장하고 분명히 라벨링하세요. 어떤 예외가 유효한지와 그렇지 않은지를 결정하세요.
각 릴리스 전에 같은 감사를 실행해 베이스라인과 비교하세요. 무언가 회귀하면 버그 추적 시스템에 기록하고 결제 과정이 깨진 것처럼 처리하세요. 예외와 함께 배포하면 담당자와 만료일(보통 1~2 스프린트)을 기록하세요. 예외가 계속 갱신된다면 예산 자체를 진지하게 논의해야 합니다.
기획과 견적 단계에서 예산을 앞당기세요: "이 화면은 차트 라이브러리를 추가하므로 다른 것을 제거하거나 지연 로드해야 한다." Koder.ai로 빌드하는 경우( Koder.ai, koder.ai ) 계획 모드에 이러한 제약을 미리 작성하고 작은 단위로 반복하며 스냅샷과 롤백을 사용해 변경이 한계를 넘을 때 되돌릴 수 있습니다. 핵심은 도구가 아니라 습관입니다: 모든 새로운 기능은 그 무게를 지불하거나 배포되지 않습니다.
성능 예산은 팀이 빌드하기 전에 합의하는 명확한 한계(시간, 크기, 요청, CPU 작업)입니다.
변경이 이 한계를 넘으면 고장난 요구사항처럼 처리하세요: 수정하거나 범위를 줄이거나, 예외를 명시적으로 승인하고 담당자와 만료일을 정합니다.
성능은 점진적으로 나빠지기 때문에 단순히 ‘성능을 신경 쓴다’는 말로는 부족합니다. 각 기능은 JavaScript, CSS, 이미지, 폰트, API 호출, 서드파티 태그를 추가합니다.
예산은 무게나 작업을 추가하면 반드시 대가를 치러야 한다는 규칙을 만들어 느린 침식 현상을 멈춥니다(지연 로드, 라우트 분리, UI 단순화, 의존성 제거 등).
하나의 실제 사용자 여정과 일관된 테스트 설정을 선택하세요.
시작하기 좋은 흐름은 빈번하고 비즈니스에 중요한 것, 예를 들어:
처음에는 극단적 케이스가 아닌 반복 측정 가능한 흐름을 고르세요.
일반 사용자를 반영하는 한 가지 타겟을 정하세요. 예시:
문장으로 적어 두고 테스트 설정을 고정하세요. 기기, 네트워크, 캐시 상태, 경로가 바뀌면 추세가 노이즈가 됩니다.
사용자가 느끼는 부분과 팀이 제어할 수 있는 부분을 모두 포함하는 소수의 지표를 사용하세요:
타이밍은 고통을 보여주고, 크기/런타임 한계는 원인을 빠르게 찾게 해줍니다.
실무적인 시작값 예시는 다음과 같습니다:
처음에는 3–5개 예산만 정하고, 이후 베이스라인에 맞춰 조정하세요.
두 단계의 임계값을 사용하세요:
이렇게 하면 한계를 현실화하면서도 매번 긴급 상황이 되는 것을 피할 수 있습니다.
다음 순서로 진행하세요:
침해를 버그처럼 처리하고 해당 커밋을 찾아 수정하거나 범위를 줄이고 재발을 막으세요.
번들 크기가 통과해도 느리게 느껴질 수 있습니다. 이유:
이 문제를 잡으려면 런타임 예산(예: 시작 시 롱 태스크 제한, 하이드레이션 완료 시간 제한)을 추가하세요.
빠른 생성과 반복은 의존성, UI 장식, 서드파티 스크립트를 조용히 추가하게 만들 수 있습니다.
해결책은 워크플로에 예산을 포함하는 것입니다:
도구가 목적이 아니라 습관이 핵심입니다: 새로운 기능은 그 무게를 지불하거나 배포되지 않습니다.