본문으로 건너뛰기

성능

본 문서는 번들 예산의 설정과 강제, 로딩 최적화 규칙, 그리고 측정 수단을 정의합니다.

성능은 아키텍처 1절의 품질 목표 서열에 포함되지 않습니다. 프로젝트마다 요구 수준이 달라 규범집 차원의 순위를 정할 수 없기 때문입니다. 다만 자동 강제 가능한 항목은 규칙으로 등재하며, 본 문서가 그 규칙의 원본입니다.

1. 번들 예산

1.1 강제 수단

예산의 원본은 angular.jsonbudgets입니다. 프론트엔드에서 자동 강제가 가장 확실한 성능 항목입니다.

{
  "budgets": [
    { "type": "initial", "maximumWarning": "1MB", "maximumError": "2MB" },
    { "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
  ]
}

중요

maximumWarning 은 빌드를 실패시키지 않습니다

초과 시 경고만 출력하고 종료 코드는 0 입니다. 빌드를 멈추는 것은 maximumError 뿐입니다. 따라서 maximumWarning 만 설정하면 자동 강제가 아니라 눈으로 보고 넘기는 항목이 되며, CI 도 통과시킵니다.

수치는 프로젝트가 정합니다. 본 문서는 값을 규정하지 않고 예산을 반드시 설정하고 maximumError를 지정한다는 규칙만 둡니다. 경고만 설정하면 초과가 누적되어도 아무도 멈추지 않습니다.

1.2 예산 설정 절차

  1. 초기 구현이 어느 정도 완성된 시점의 실제 크기를 측정합니다.
  2. 그 값에 여유를 더해 maximumError로 설정합니다.
  3. 초과가 발생하면 예산을 올리기 전에 원인을 확인합니다.

예산을 올리는 것이 항상 잘못은 아니지만, 올린 사유를 커밋 메시지에 남깁니다. 사유 없이 반복해 올리면 예산은 의미를 잃습니다.

2. 로딩

2.1 지연 로딩

대상 수단 규칙
화면 loadComponent / loadChildren 필수. 정적 임포트를 금지합니다
무거운 컴포넌트 @defer 측정으로 확인 후 적용
큰 라이브러리 동적 import() 사용 시점에 로드

화면 지연 로딩은 예외 없이 적용합니다. 정적 생성 경로의 청크는 빌드가 modulepreload 힌트를 심어 초기 번들과 병렬로 받으므로 첫 표시가 늦어지지 않습니다. 상세는 라우팅과 네비게이션 2절에 있습니다.

@defer측정 후에만 적용합니다. 추측으로 넣으면 번들은 줄지만 사용자가 그 영역에 도달했을 때 지연이 생겨 체감이 나빠질 수 있습니다.

@defer (on viewport) {
  <app-assessment-chart [data]="data()" />
} @placeholder {
  <div class="h-64"></div>
}

@placeholder에 실제와 비슷한 크기를 지정합니다. 지정하지 않으면 로드 시점에 레이아웃이 밀립니다.

2.2 임포트

금지 대신
라이브러리 전체 임포트 후 일부만 사용 필요한 것만 명시적으로 임포트
배럴을 통한 대량 재수출 사용처가 필요한 것만 임포트
사용하지 않는 폴리필 포함 대상 브라우저 범위를 명확히 설정

shared 세그먼트의 index.ts가 지나치게 많은 것을 재수출하면 하나를 임포트해도 전부가 그래프에 들어옵니다. 공개 API를 최소로 유지하는 규칙은 캡슐화뿐 아니라 번들 크기와도 관련이 있습니다.

3. 렌더링

3.1 변경 감지

provideZonelessChangeDetection()과 시그널 기반 작성이 기본입니다. 상세는 컴포넌트 설계 5절에 있습니다.

금지 사유
템플릿에서 함수 호출 변경 감지마다 재실행됩니다
@fortrack 누락 목록 전체가 재생성됩니다
파생 값을 signal에 복사 갱신 누락 지점이 생기고 계산이 중복됩니다

@fortrack은 안정된 식별자를 지정합니다. 인덱스는 목록이 재정렬되거나 중간 삽입이 발생하지 않는 경우에만 허용합니다.

3.2 큰 목록

행이 수백 개를 넘으면 가상 스크롤(@angular/cdk/scrolling)을 검토합니다. CDK가 이미 의존성에 있으므로 추가 비용이 없습니다.

적용 전에 먼저 서버 페이지네이션을 검토합니다. 전부 받아 와서 가상 스크롤로 그리는 것보다 필요한 만큼만 받는 편이 네트워크와 메모리 모두 유리합니다.

4. 이미지와 자산

항목 규칙
이미지 컴포넌트 템플릿의 이미지는 NgOptimizedImage를 사용합니다
크기 지정 width·height 또는 fill필수로 지정합니다
첫 화면 이미지 priority를 지정합니다
포맷 WebP 또는 AVIF를 우선합니다
아이콘 SVG를 사용하며 shared/ui/icon/ 래퍼를 경유합니다

크기를 지정하지 않으면 이미지 로드 시 레이아웃이 밀립니다. NgOptimizedImage는 크기 미지정 시 경고를 냅니다.

4.1 빌드가 만든 HTML 안의 이미지

문서 본문처럼 빌드가 만든 HTML 을 [innerHTML] 로 삽입하는 경로에서는 NgOptimizedImage 를 쓸 수 없습니다. 디렉티브는 Angular 가 컴파일한 템플릿에만 붙으며, 삽입된 문자열 안의 요소는 대상이 아닙니다.

이 경로에서는 변환 단계가 같은 일을 대신합니다. 규칙은 지키되 수단이 다릅니다.

항목 템플릿 빌드가 만든 HTML
크기 지정 width·height 속성 변환 시 원본을 읽어 속성으로 박습니다
폭별 사본 ngSrcset 변환 시 srcsetsizes 를 생성합니다
지연 로딩 loading 변환 시 loading="lazy" 를 붙입니다

외부 주소의 이미지는 크기를 알 수 없어 자리를 예약하지 못합니다. 그 경우 레이아웃 이동을 감수하며, 감수할 수 없으면 자산을 저장소로 가져옵니다.

4.2 나타나는 방식

이미지는 다 받아진 뒤에 드러냅니다. 받는 도중의 빈 상자나 부분적으로 그려지는 모습을 노출하지 않습니다. 완성된 화면만 노출한다는 로딩 전략 1절의 원칙이 이미지에 적용된 형태입니다.

수단은 CSS 전환이며 시점만 스크립트가 알립니다. load 는 버블링하지 않으므로 문서에서 캡처 단계로 한 번 받습니다. 이미지마다 리스너를 다는 방식은 빌드가 만든 HTML 안의 이미지를 놓칩니다.

주의: 감추기를 기본값으로 두지 않습니다

img { opacity: 0 } 을 기본으로 두고 스크립트가 드러내는 구성은, 스크립트가 없거나 실패한 환경에서 이미지가 영영 보이지 않게 만듭니다. 정적 생성한 HTML 에는 이미지가 이미 들어 있으므로 그 경우에도 내용은 온전해야 합니다. 감추는 규칙은 스크립트가 도는 것이 확인된 뒤에만 적용합니다.

주의: 전환이 아니라 애니메이션으로 만듭니다

transition 으로 구현하면 이미지에 붙은 transition-transform 같은 유틸리티가 transition-property 를 통째로 덮어 페이드가 사라집니다. 클래스 선택자가 요소 선택자보다 우선하므로 전역 규칙이 지며, 그 이미지에서만 조용히 동작하지 않습니다. 애니메이션은 전환 속성과 겹치지 않아 호버 전환과 함께 쓸 수 있습니다.

로드 실패도 완료로 취급해 드러냅니다. 그러지 않으면 깨진 이미지가 투명한 채로 남아 대체 텍스트조차 보이지 않습니다.

움직임을 줄이도록 설정한 사용자에게는 전환 없이 곧바로 드러냅니다.

4.3 폰트

항목 규칙
호스팅 자체 호스팅합니다. npm 패키지로 설치하면 빌드가 url()을 따라 산출물에 포함시킵니다
분할 unicode-range 서브셋을 사용합니다. 한글 전량을 한 파일로 받지 않습니다
두께 가변 폰트를 사용합니다. 두께마다 파일을 두지 않습니다
표시 정책 font-display: swap을 적용합니다
preload 동적 서브셋에는 적용하지 않습니다

외부 CDN 로드는 추가 연결을 만들고 CSP 허용 목록을 넓힙니다. 브라우저의 캐시 파티셔닝으로 도메인 간 폰트 캐시 공유는 더 이상 동작하지 않으므로, CDN 을 쓰는 이유였던 "다른 사이트에서 이미 받았을 것"이라는 기대가 성립하지 않습니다.

preload 를 적용하지 않는 이유는 동적 서브셋과 맞지 않기 때문입니다. 한글이 수십 개 범위로 쪼개져 있어 어느 조각이 필요한지는 화면에 실제로 나타난 글자에 따라 달라집니다. 특정 조각을 지정하면 쓰이지 않는 파일을 받게 되고 필요한 조각은 여전히 늦게 요청됩니다. 서브셋을 쓰지 않는 폰트에는 preload 가 유효하므로 이 규칙은 분할 방식과 함께 판단합니다.

폰트 선택과 그 대가는 ADR-0014가 원본입니다.

5. 측정

지표 의미 확인 수단
LCP 주요 콘텐츠가 그려지기까지의 시간 Lighthouse, 실사용자 측정
INP 상호작용에 대한 응답 지연 Lighthouse, 실사용자 측정
CLS 레이아웃 이동량 Lighthouse
초기 번들 크기 첫 로드에 필요한 JS 양 빌드 출력, budgets

측정 없이 최적화하는 것을 금지합니다. 지연 로딩·가상 스크롤·메모이제이션은 모두 대가가 있으며, 문제가 없는 곳에 적용하면 복잡도만 늘어납니다.

측정 대상은 운영 빌드입니다. 개발 빌드는 최적화가 적용되지 않아 수치가 무의미합니다.

ng build            # 운영 빌드. 번들 크기와 예산 확인

6. 렌더링 전략과의 관계

정적 생성 경로에서는 서버가 HTML을 미리 만들어 두므로 첫 화면이 빠릅니다. 인증 화면은 클라이언트 렌더링이므로 이 이득이 없습니다.

이는 렌더링 전략에서 의도적으로 선택한 결과입니다. 업무 화면의 초기 로딩 개선은 번들 축소와 지연 로딩으로 달성하며, 서버 렌더링으로 해결하지 않습니다.

증분 하이드레이션은 Angular 22에서 기본 활성화되어 있으나 정적 생성 경로에서만 의미를 갖습니다.

7. 금지 사항

금지 사유
번들 예산 미설정 또는 경고만 설정 초과가 누적되어도 멈추지 않습니다
화면 라우트의 정적 임포트 초기 번들에 모든 화면이 포함됩니다
측정 없이 @defer·가상 스크롤 적용 대가만 치르고 이득이 없을 수 있습니다
템플릿에서 함수 호출 변경 감지마다 재실행됩니다
@fortrack 누락 목록 전체가 재생성됩니다
이미지 크기 미지정 로드 시 레이아웃이 밀립니다
이미지 감추기를 CSS 기본값으로 지정 스크립트가 실패한 환경에서 이미지가 영영 보이지 않습니다
개발 빌드로 성능 측정 수치가 무의미합니다
사유 없는 예산 상향 예산이 의미를 잃습니다
© 2026 dev.goraebap