Vue 3 렌더링 성능을 개선하는 실무 점검법

Vue 3 렌더링 성능을 개선하는 실무 점검법 관련 이미지
Photo by MianShahzadRaza via Pixabay

Vue 애플리케이션이 느려졌을 때 반응형 프레임워크 자체를 원인으로 지목하기 쉽습니다. 실제로는 너무 많은 컴포넌트 생성, 불안정한 목록 키, 큰 객체에 대한 깊은 감시, 중복 API 호출과 거대한 번들이 함께 문제를 만드는 경우가 많습니다. 성능 최적화의 출발점은 감으로 코드를 바꾸는 것이 아니라 사용자가 느끼는 지연을 측정하고 렌더링·네트워크·자바스크립트 비용을 분리하는 것입니다.

먼저 측정할 사용자 경험

초기 화면이 보이는 시간, 클릭 뒤 반응 시간, 스크롤 중 끊김, 라우트 이동 시간을 실제 기기에서 측정합니다. 개발용 빌드는 디버깅 기능 때문에 운영 빌드보다 느릴 수 있으므로 프로덕션 빌드도 확인해야 합니다. 브라우저 Performance 패널과 Vue Devtools를 사용하면 오래 걸린 작업, 반복 렌더링, 컴포넌트 업데이트 원인을 찾을 수 있습니다.

반응형 상태를 필요한 범위로 줄이기

Vue는 상태의 의존성을 추적해 변경된 화면을 갱신합니다. 거대한 API 응답 전체를 깊은 reactive 객체로 만들면 추적 비용과 의도치 않은 업데이트가 늘어날 수 있습니다. 화면에서 변경할 필요가 없는 큰 데이터는 shallowRefmarkRaw를 검토하고, 필요한 부분만 반응형으로 유지합니다. 다만 무작정 반응성을 제거하면 화면이 갱신되지 않으므로 데이터가 교체되는지 내부 속성이 바뀌는지 먼저 확인합니다.

computed에서 새 객체를 반복 생성하지 않기

computed는 의존성이 바뀔 때 캐시되지만 결과에서 매번 큰 배열을 복사하거나 모든 항목을 정렬하면 변경 시 비용이 큽니다. 필터와 정렬을 서버로 옮길 수 있는지, 계산 대상을 미리 줄일 수 있는지 검토합니다. 템플릿에서 무거운 함수를 직접 호출하면 렌더링마다 실행될 수 있으므로 파생 값은 computed로 옮깁니다.

반응성 점검 항목

  • deep watch가 정말 필요한지 확인합니다.
  • watch 대상은 큰 객체보다 필요한 속성으로 좁힙니다.
  • 파생 상태를 별도 ref에 복사해 동기화하지 않습니다.
  • 변하지 않는 설정 객체를 매 렌더링마다 만들지 않습니다.
  • 불필요한 전역 상태가 여러 화면을 갱신하지 않게 합니다.

목록 렌더링과 key 안정화

수백 개 이상의 행을 한꺼번에 렌더링하면 DOM 생성만으로도 느려집니다. 화면에 보이는 행만 그리는 가상 스크롤이나 페이지 처리를 사용합니다. v-for의 key에는 배열 인덱스나 무작위 값을 쓰지 말고 변하지 않는 고유 ID를 사용합니다. key가 불안정하면 Vue가 기존 DOM을 재사용하지 못해 컴포넌트를 다시 만들고 입력 상태도 꼬일 수 있습니다.

실무 원칙: 목록 최적화는 반복문 문법을 바꾸는 일이 아니라 동시에 생성하는 DOM 수를 줄이고 항목의 정체성을 안정적으로 유지하는 일입니다.

v-if, v-show, v-memo 활용하기

거의 표시되지 않는 무거운 영역은 v-if로 생성 자체를 피할 수 있습니다. 자주 토글되는 작은 요소는 이미 생성한 DOM을 표시만 바꾸는 v-show가 적합할 수 있습니다. Vue의 v-once는 절대 바뀌지 않는 내용을 한 번만 렌더링하고, v-memo는 지정한 의존성이 같을 때 하위 트리 업데이트를 건너뜁니다. 이 지시어는 병목을 측정한 뒤 사용해야 하며 잘못된 의존성을 지정하면 최신 상태가 보이지 않을 수 있습니다.

컴포넌트 props를 안정적으로 유지하기

부모가 렌더링될 때마다 새로운 객체와 함수를 만들어 자식 props로 넘기면 자식이 실제 내용 변화 없이도 갱신될 수 있습니다. 가능한 값은 원시값과 안정된 참조로 전달하고, 자식에게 전체 사용자 객체가 아니라 필요한 상태만 전달합니다. 컴포넌트를 지나치게 잘게 나누는 것도 인스턴스와 통신 비용을 늘릴 수 있으므로 재사용성과 업데이트 경계를 기준으로 분리합니다.

라우트와 기능 단위 코드 분할

초기 번들에 사용자가 당장 보지 않는 관리자 화면과 편집기를 모두 포함하면 첫 로딩이 느려집니다. 동적 import와 라우트 지연 로딩으로 필요한 시점에 코드를 내려받습니다. 큰 라이브러리를 통째로 가져오기보다 사용하는 기능만 import하고 번들 분석기로 실제 포함 크기를 확인합니다. 지나치게 많은 작은 청크는 네트워크 요청과 실행 관리 비용을 늘릴 수 있으므로 화면 단위로 균형을 잡습니다.

API 요청과 캐시 최적화

같은 데이터를 여러 컴포넌트가 각각 요청하면 네트워크와 렌더링이 반복됩니다. 요청 키와 만료 시간을 기준으로 결과를 공유하고, 라우트 전환 시 불필요한 요청은 AbortController로 취소합니다. 검색 입력에는 디바운스를 적용하되 최신 요청 식별자를 확인해 느린 이전 응답이 화면을 덮지 않게 합니다. 데이터를 미리 가져오는 기능은 실제 이동 가능성이 높은 화면에 제한적으로 사용합니다.

이미지와 CSS도 함께 확인하기

큰 이미지를 CSS로 작게 표시한다고 다운로드 용량이 줄어들지는 않습니다. 표시 크기에 맞는 이미지와 최신 포맷, 지연 로딩을 적용하고 첫 화면의 핵심 이미지는 너무 늦게 요청되지 않게 합니다. 사용하지 않는 CSS와 아이콘 세트도 번들에 포함되지 않는지 확인합니다. 폰트 파일은 필요한 굵기만 제공하고 글자 표시가 막히지 않는 로딩 전략을 사용합니다.

개선 순서

  1. 느린 사용자 행동과 기준 시간을 정합니다.
  2. 운영 빌드에서 프로파일을 수집합니다.
  3. 긴 작업이 렌더링, 계산, 네트워크 중 어디인지 나눕니다.
  4. 큰 목록과 deep watch, 반복 계산부터 줄입니다.
  5. 라우트별 코드 분할과 번들 크기를 확인합니다.
  6. 저사양 기기와 느린 네트워크에서 다시 측정합니다.
  7. 성능 기준을 자동 테스트와 배포 모니터링에 추가합니다.

최적화 후에는 기능 정확성과 접근성도 다시 확인해야 합니다. 가상 스크롤이 키보드 탐색과 스크린 리더를 방해하거나 지연 로딩 때문에 중요한 콘텐츠가 누락될 수 있습니다. 성능은 한 번의 정리 작업이 아니라 기능 변화와 함께 지표를 관찰하는 운영 과정입니다.

한 줄 요약: Vue 성능 개선은 반응형 범위와 DOM 수, 번들 및 중복 요청을 측정해 가장 큰 병목부터 줄이는 과정입니다.