frontend/javascript

🟨 2-41. 프론트엔드 성능 아키텍처 완성 — ‘빠름’을 구조로 설계하기

mirabo01 2025. 11. 7. 09:02
반응형

좋아. 이제 이번 편은 시리즈의 마지막을 장식할 **‘프론트엔드 성능 아키텍처 총정리’**야.
그동안 다뤄온 코드, 렌더링, 시각적 튜닝, 네트워크, 모니터링을
하나의 완성된 시스템 관점에서 엮어보자.

이건 단순히 “어떻게 빠르게 만들까?”가 아니라,
“빠른 상태를 지속적으로 유지할 수 있는 구조는 어떻게 설계할까?” 에 초점을 둔다.


1. 성능은 ‘기술’이 아니라 ‘구조’다

대부분의 프로젝트가 초기에 빠르다가
운영 몇 달 지나면 점점 무거워지는 이유는 명확하다.
성능을 “한 번 튜닝하는 일회성 작업” 으로 보기 때문이다.

하지만 진짜 중요한 건,

성능이 유지되도록 구조를 설계하는 것.

이건 “잘 만든 코드”가 아니라 “잘 만들어진 시스템”에서 온다.


2. 전체 구조를 4단계로 나누어 설계하라

① 렌더링 계층

→ React / Next.js가 실제로 그리는 UI

② 데이터 계층

→ API 호출, 캐싱, 상태관리 구조

③ 자원 계층

→ 이미지, 폰트, CSS, JS 번들, CDN

④ 관찰 계층

→ 모니터링, 로깅, 사용자 체감 성능 데이터

이 4단계를 각자 독립적으로 설계하면
성능 문제가 생겨도 “어느 계층에서 터졌는지” 즉시 구분할 수 있다.


3. 렌더링 계층 — 최소한만 다시 그리는 구조

핵심 원칙

  1. React.memo 로 자식 리렌더 차단
  2. useCallback / useMemo 로 참조 고정
  3. Suspense / Lazy Loading 으로 페이지 단위 코드 분리
  4. Virtualized Rendering 으로 긴 리스트 최소화

실제 구조 예시

<App>
 ┣ <Header />           // memoized
 ┣ <Sidebar />          // memoized
 ┣ <Suspense fallback={<Loading />}> 
 ┃   <MainRouter />     // dynamic import된 페이지 단위 컴포넌트
 ┗ </Suspense>
</App>

→ 구조적으로 “필요한 부분만 리렌더” 하도록 설계되어 있으면
코드가 늘어나도 체감 속도는 변하지 않는다.


4. 데이터 계층 — 서버 상태와 클라이언트 상태의 분리

성능이 무너지는 가장 흔한 이유 중 하나가
“모든 데이터를 전역 상태로 관리”하기 때문이다.

원칙

구분 특징 적합한 관리 방식

서버 상태 외부 API에서 오는 데이터 React Query / SWR
클라이언트 상태 UI 조작, 필터링, 입력값 등 useState / useReducer
전역 상태 여러 페이지에서 공용으로 쓰는 데이터 Zustand / Redux (selector 기반 구독)

즉,

“모든 상태를 한곳에 몰지 말고, 데이터의 ‘출처’에 따라 관리 구조를 나눠라.”

이렇게만 해도 불필요한 리렌더링과 API 중복 호출이 확 줄어든다.


5. 자원 계층 — 브라우저가 덜 일하게 만드는 설계

성능의 절반은 브라우저가 얼마나 효율적으로 리소스를 받느냐에 달려 있다.

1️⃣ 번들 관리

  • 페이지별 코드 스플리팅
  • Dynamic Import로 무거운 라이브러리 지연 로드
  • Tree Shaking 활성화
  • moment → dayjs, lodash → lodash-es

2️⃣ 이미지 최적화

  • WebP/AVIF 사용
  • loading="lazy"
  • next/image 활용

3️⃣ 폰트 관리

  • next/font 사용 or font-display: swap
  • 필요 굵기만 subset 추출

4️⃣ 캐시 정책

  • 정적 리소스: max-age=31536000, immutable
  • HTML/API: stale-while-revalidate

이 계층을 잘 세팅하면
브라우저는 “이미 받은 건 다시 안 받는” 구조로 돌아가며
체감 속도는 2배 이상 빨라진다.


6. 관찰 계층 — ‘지속적인 성능’의 핵심

아무리 잘 만들어도, 측정하지 않으면 무너진다.

6-1. Lighthouse CI

  • GitHub Actions에 붙여서 main 브랜치마다 성능 측정
  • LCP, CLS, TBT 점수가 기준 이하일 경우 빌드 실패

6-2. Web Vitals 수집

import { getLCP, getFID, getCLS } from "web-vitals";

function report(metric) {
  fetch("/api/vitals", {
    method: "POST",
    body: JSON.stringify(metric),
    keepalive: true,
  });
}

getLCP(report);
getFID(report);
getCLS(report);

→ 실제 사용자 기준으로 Core Web Vitals 데이터를 백엔드로 전송
→ 서버에서 쌓인 데이터로 “실제 사용자가 느끼는 성능”을 확인

6-3. 에러 & UX 모니터링

  • Sentry: JS 에러 및 느린 렌더링 구간 추적
  • LogRocket: 사용자 세션 리플레이로 UX 확인
  • Datadog: API 응답 지연 및 브라우저 메트릭 분석

이 계층이 없으면 성능은 결국 “운에 맡긴 품질” 이 된다.


7. 실제 아키텍처 예시

[User] 
   │
   ▼
[CDN Edge] — Static Assets (이미지, 폰트, 번들)
   │
   ▼
[Next.js Server]
   ┣ SSG: 캐시된 HTML 제공
   ┣ ISR: 주기적 재생성
   ┗ API Proxy → 백엔드
   │
   ▼
[React 클라이언트]
   ┣ Suspense + Lazy 로 분할 로드
   ┣ React Query 캐시로 서버 상태 관리
   ┣ Virtualized List + memoized component
   ┗ Web Vitals → Monitoring 서버

→ 구조 전체가 “덜 렌더링, 덜 다운로드, 덜 재계산”을 중심으로 돌아간다.


8. 성능 유지 루틴 (운영단계 체크리스트)

주기 점검 항목 도구

매 배포 Lighthouse / Bundle Analyzer GitHub Actions
매주 Web Vitals 평균 점검 (LCP/FID/CLS) 자체 대시보드
매월 번들 크기 및 이미지 용량 추세 Source-map Explorer
분기 코드 리팩토링 / 사용 안 하는 패키지 제거 npm-check, depcheck

유지되는 성능은 “정기 점검 루틴”에서 나온다.


9. 완성된 프론트엔드 성능 아키텍처의 핵심 문장

  • 렌더링 계층: 필요한 것만 그린다.
  • 데이터 계층: 필요한 순간에만 가져온다.
  • 자원 계층: 한 번 받은 건 다시 받지 않는다.
  • 관찰 계층: 느려지면 바로 알 수 있다.

이 네 문장만 머릿속에 넣고 프로젝트를 설계하면
어떤 기술 스택을 쓰더라도 성능 문제는 구조적으로 막을 수 있다.


 

반응형