2026년 06월 09일
29

프론트엔드 시스템 디자인이 왜 중요한가?

프론트엔드
KKingmo

Changmo Oh

@KKingmo

전체 카테고리 보기

프론트엔드를 처음 배울 때는 보통 이렇게 생각한다.

“API에서 데이터를 받아오고, React 컴포넌트로 화면에 보여주면 되는 것 아닌가?”

상품 페이지라면 상품 정보를 가져와서 이미지, 가격, 재고, 버튼을 보여주면 된다.
뉴스피드라면 게시글 목록 API를 호출해서 리스트를 렌더링하면 된다.
장바구니라면 cart 배열을 만들고, 그 배열을 화면에 뿌리면 된다.
동영상 서비스라면 <video> 태그를 넣으면 된다.
처음에는 이 접근이 틀리지 않다.

하지만 실제 서비스의 프론트엔드는 여기서 끝나지 않는다.

사용자는 각기 다른 기기, 브라우저, 네트워크 환경에서 서비스를 사용한다.
서버 데이터는 계속 바뀌고, UI 상태는 사용자의 행동에 따라 실시간으로 변한다.


프론트엔드 시스템 디자인이란 무엇인가

프론트엔드 시스템 디자인은 사용자가 보는 화면을 기준으로 다음 요소들을 함께 설계하는 일이다.

  • 상태 관리
  • 서버 데이터 캐싱
  • 데이터 흐름
  • 렌더링 전략
  • 라우팅
  • 인증과 권한
  • 이미지와 미디어 최적화
  • 성능
  • 장애 복구
  • 모니터링
  • 조직 확장성

단순히 “이 컴포넌트를 어디에 둘까?” 수준의 문제가 아니다.

물론 컴포넌트 분리도 중요하다.

하지만 실제 사용자 경험을 안정적으로 보장하려면 화면 상태, 서버 상태, 데이터 갱신 방식, 렌더링 타이밍, 성능 저하 상황, 장애 감지 방식까지 함께 고민해야 한다.


예시: 쇼핑몰 상품 상세 페이지

쇼핑몰의 상품 상세 페이지를 생각해보자.

겉으로 보면 어렵지 않아 보인다.

  • 상품 상세 API를 호출한다.
  • 상품 이미지를 보여준다.
  • 가격을 보여준다.
  • 재고 상태를 보여준다.
  • 장바구니 담기 버튼을 만든다.

이 정도면 상품 상세 페이지가 완성된 것처럼 보인다.

하지만 실제 서비스에서는 여기서부터 질문이 시작된다.

  • 상품 가격이 바뀌면 화면은 언제 업데이트되어야 할까?
  • 장바구니 담기 버튼을 눌렀는데 서버가 재고 부족을 반환하면 어떻게 처리해야 할까?
  • 사용자가 수량 증가 버튼을 빠르게 여러 번 누르면 서버 요청도 그만큼 보내야 할까?
  • 상품 이미지가 늦게 로드되어 화면 레이아웃이 밀리면 어떻게 해야 할까?
  • 저사양 모바일 기기에서 JavaScript 실행이 오래 걸려 버튼 반응이 늦어지면 어떻게 해야 할까?
  • 새 릴리즈 이후 특정 브라우저에서만 결제 버튼이 동작하지 않으면 어떻게 원인을 찾을 수 있을까?

이 질문들이 등장하는 순간, 프론트엔드는 단순히 화면을 그리는 일이 아니게 된다.


질문 1. 상품 가격이 바뀌면 화면은 언제 업데이트되어야 하는가?

상품 가격은 서버에서 바뀔 수 있다.
예를 들어 사용자가 상품 페이지를 열어둔 상태에서 가격이 변경되었다고 해보자.
그럼 화면은 언제 바뀌어야 할까?
페이지를 새로고침할 때만 바뀌면 될까?
일정 시간마다 다시 가져와야 할까?
사용자가 장바구니에 담으려는 순간 최신 가격을 다시 확인해야 할까?
결제 직전에는 반드시 최신 가격을 검증해야 할까?

이 문제는 단순 렌더링 문제가 아니다.

서버 상태를 클라이언트가 얼마나 오래 신뢰할 것인지에 대한 문제다.
여기서 서버 상태 캐싱 전략이 필요해진다.

예를 들어 다음과 같은 기준을 설계해야 한다.

상품 정보:
- 일정 시간 동안 캐시 가능
- 사용자가 페이지에 다시 진입하면 재검증
- 장바구니 담기 시점에 재고와 가격 재확인
- 결제 직전에는 반드시 서버 기준으로 최종 검증

즉, 프론트엔드는 “API를 한 번 호출해서 보여주는 곳”이 아니라 서버 데이터의 신선도와 사용자 경험 사이에서 균형을 잡는 실행 환경이다.


질문 2. Add to Cart를 눌렀는데 서버가 재고 부족을 반환하면?

사용자가 Add to Cart 버튼을 눌렀다.
프론트엔드 입장에서는 버튼 클릭 이벤트가 발생했고, 장바구니 추가 API를 호출하면 된다.
그런데 서버가 이렇게 응답한다. “재고 부족”

그럼 화면은 어떻게 되어야 할까?

버튼을 다시 활성화해야 할까?
사용자에게 토스트 메시지를 보여줘야 할까?
재고 정보를 즉시 다시 가져와야 할까?
이미 화면에 표시된 재고 숫자도 바꿔야 할까?
장바구니에 임시로 추가해둔 상태가 있다면 롤백해야 할까?

이 문제는 서버 상태와 클라이언트 상태가 충돌하는 상황이다.

사용자는 “장바구니에 담았다”고 생각하지만, 서버는 “담을 수 없다”고 말한다.
이때 프론트엔드는 사용자의 행동 결과를 명확하게 정리해줘야 한다.

좋지 않은 처리는 이런 것이다.

사용자가 버튼 클릭
→ 아무 반응 없음
→ 콘솔에는 에러 발생
→ 화면은 그대로
→ 사용자는 버튼을 여러 번 누름

좋은 처리는 이런 방식에 가깝다.

사용자가 버튼 클릭
→ 버튼 로딩 상태 표시
→ 서버 응답 확인
→ 재고 부족이면 명확한 메시지 표시
→ 최신 재고 정보 재조회
→ 버튼 상태 갱신

프론트엔드 시스템 디자인에서는 이런 실패 시나리오를 기본 흐름만큼 중요하게 다룬다.


질문 3. 사용자가 수량 + 버튼을 빠르게 5번 누르면 서버 요청도 5번 보내야 하는가?

장바구니에서 사용자가 수량 증가 버튼을 빠르게 5번 눌렀다고 해보자.

그럼 서버 요청도 5번 보내야 할까?
단순하게 구현하면 클릭할 때마다 API 요청이 나간다.

PATCH /cart/item/1 quantity=2
PATCH /cart/item/1 quantity=3
PATCH /cart/item/1 quantity=4
PATCH /cart/item/1 quantity=5
PATCH /cart/item/1 quantity=6

이 방식은 문제가 많다.
요청 순서가 보장되지 않을 수 있다.
느린 네트워크에서는 마지막 요청보다 이전 요청이 나중에 도착할 수 있다.
서버 부하가 커진다.
사용자 입장에서는 UI가 버벅일 수 있다.
이때 필요한 개념이 디바운싱, 스로틀링, 요청 병합, 낙관적 업데이트 같은 것들이다.

예를 들어 사용자가 빠르게 5번 눌렀다면 화면에서는 즉시 수량을 6으로 보여주되, 서버에는 최종 값만 보내는 방식이 가능하다.

사용자 클릭 5회
→ UI는 즉시 quantity 6으로 변경
→ 짧은 대기 시간 후 서버에 최종 quantity 6만 전송

이렇게 하면 사용자는 빠르게 반응하는 UI를 경험하고, 서버 요청 수는 줄일 수 있다.

여기서 중요한 점은 “버튼 클릭 = API 호출”로 바로 연결하지 않는 것이다.

사용자 입력과 서버 반영 사이에 어떤 버퍼와 동기화 전략을 둘지 설계해야 한다.


질문 4. 상품 이미지가 늦게 로드되어 레이아웃이 밀리면?

상품 이미지는 텍스트보다 늦게 로드될 수 있다.
처음에는 가격과 버튼이 위에 있다가, 이미지가 뒤늦게 로드되면서 화면 높이가 바뀌고 버튼 위치가 아래로 밀릴 수 있다.
사용자는 버튼을 누르려고 했는데, 갑자기 레이아웃이 밀리면서 엉뚱한 곳을 누를 수도 있다.

이 문제는 CLS, 즉 Cumulative Layout Shift와 관련이 있다.

쉽게 말하면 페이지가 로드되는 동안 화면 요소가 얼마나 흔들리는지를 의미한다.
이미지가 늦게 로드될 수 있다는 사실을 고려하지 않으면 사용자 경험이 나빠진다.

해결 방식은 다음과 같다.

- 이미지 영역의 width, height를 미리 확보한다.
- skeleton UI를 사용한다.
- placeholder를 보여준다.
- lazy loading을 적용하되 레이아웃이 밀리지 않게 한다.
- 중요 이미지는 우선 로드한다.

즉, 이미지를 보여주는 것도 단순히 <img> 태그를 넣는 문제가 아니다.
이미지가 늦게 도착해도 화면 구조가 안정적으로 유지되도록 설계해야 한다.


질문 5. 저사양 모바일 기기에서 JavaScript가 오래 실행되어 버튼이 안 눌리면?

프론트엔드 코드는 사용자의 브라우저에서 실행된다.
개발자의 최신 맥북에서는 빠르게 동작하던 코드가
저사양 안드로이드 기기에서는 느리게 동작할 수 있다.
특히 JavaScript 실행 시간이 길어지면 메인 스레드가 막힌다.
메인 스레드가 막히면 사용자가 버튼을 눌러도 반응이 늦다.

사용자 입장에서는 이렇게 느낀다.

버튼을 눌렀는데 안 눌린다.
화면이 멈춘 것 같다.
앱이 버벅인다.

이때 필요한 고민은 다음과 같다.

- 초기 JavaScript 번들 크기를 줄일 수 있는가?
- 필요 없는 코드를 나중에 로드할 수 있는가?
- 무거운 계산을 분리할 수 있는가?
- 렌더링을 너무 자주 발생시키고 있지는 않은가?
- 리스트 렌더링에 virtualization이 필요한가?
- 사용자 입력을 막는 long task가 있는가?

프론트엔드 성능은 단순히 “페이지가 몇 초 만에 뜨는가”만의 문제가 아니다.
사용자의 입력에 얼마나 빠르게 반응하는지도 중요하다.


질문 6. 새 릴리즈 이후 Safari에서만 Checkout 버튼이 먹통이면 어떻게 찾을까?

실제 서비스에서는 특정 브라우저에서만 문제가 생기는 경우가 많다.
예를 들어 새 버전을 배포했는데 Chrome에서는 정상이다.
Android에서도 정상이다.
그런데 Safari에서만 Checkout 버튼이 동작하지 않는다.

이 문제를 어떻게 찾을 수 있을까?

개발자가 직접 모든 브라우저와 모든 기기를 매번 확인할 수는 없다.
그래서 관측 가능성이 필요하다.
프론트엔드에도 모니터링이 필요하다.

- 어떤 브라우저에서 에러가 발생했는가?
- 어떤 버전에서 문제가 시작되었는가?
- 어떤 버튼 클릭 이후 문제가 생겼는가?
- 사용자의 네트워크 상태는 어땠는가?
- JS 에러 로그가 남았는가?
- 특정 릴리즈 이후 전환율이 떨어졌는가?

이런 데이터를 수집하지 않으면 문제를 감으로 찾아야 한다.
반대로 RUM, 에러 로깅, 성능 지표, 사용자 행동 로그가 있다면 문제를 좁혀갈 수 있다.

RUM은 Real User Monitoring의 약자다.
실제 사용자의 환경에서 성능과 오류 데이터를 수집하는 방식이다.
프론트엔드 시스템 디자인에서 모니터링은 부가 기능이 아니다.
대규모 사용자 경험을 안정적으로 운영하기 위한 핵심 요소다.


Threads 뉴스피드는 단순한 게시물 리스트가 아니다

Meta의 Threads 같은 서비스를 생각해보자.
겉으로 보면 뉴스피드는 단순한 게시물 리스트다.

게시글 API 호출
→ 게시글 목록 렌더링

하지만 실제 문제는 훨씬 복잡하다.

1. 서버 상태 캐시

사용자가 이미 본 게시글을 매번 새로 가져올 필요는 없다.
하지만 너무 오래된 데이터를 계속 보여줘도 안 된다.
그래서 서버 상태를 어떻게 캐시하고, 언제 다시 검증할지 정해야 한다.

- 이전에 불러온 피드를 유지할 것인가?
- 뒤로 가기 후 다시 돌아왔을 때 스크롤과 데이터를 복원할 것인가?
- 새 게시글이 생기면 기존 리스트 위에 바로 붙일 것인가?
- 사용자가 원할 때만 새로고침할 것인가?

2. Cursor Pagination

피드에서는 보통 페이지 번호 기반 페이지네이션보다 커서 기반 페이지네이션을 많이 사용한다.
페이지 번호 방식은 이런 식이다.

?page=1
?page=2
?page=3

하지만 실시간으로 게시글이 계속 추가되는 피드에서는 페이지 번호 방식이 흔들릴 수 있다.
새 게시글이 중간에 추가되면 2페이지의 내용이 바뀔 수 있기 때문이다.
커서 기반 페이지네이션은 특정 지점을 기준으로 다음 데이터를 가져온다.

?cursor=abc123

이 방식은 무한 스크롤, 실시간 피드, 게시글 목록에 더 적합하다.

3. Real-time Pending Buffer

실시간 피드에서는 새 게시글이 계속 들어온다.
그렇다고 사용자가 글을 읽고 있는 중에 갑자기 위쪽에 새 글을 끼워 넣으면 스크롤 위치가 밀린다.
사용자는 읽던 글을 놓칠 수 있다.
그래서 새 글을 바로 리스트에 넣지 않고 pending buffer에 임시로 보관할 수 있다.

새 게시글 5개 도착
→ 바로 화면에 삽입하지 않음
→ “새 게시글 5개 보기” 버튼 표시
→ 사용자가 누르면 위에 추가

이렇게 하면 실시간성과 스크롤 안정성을 동시에 챙길 수 있다.

4. Optimistic Like

사용자가 좋아요 버튼을 눌렀다.
서버 응답을 기다린 뒤에야 좋아요 숫자를 바꾸면 UI가 느리게 느껴진다.
그래서 보통은 먼저 화면을 바꾼다.

사용자 좋아요 클릭
→ UI에서 즉시 좋아요 상태로 변경
→ 서버 요청 전송
→ 성공하면 유지
→ 실패하면 롤백

이것이 optimistic update다.
사용자 경험은 빨라지지만, 실패했을 때 롤백 처리까지 설계해야 한다.

5. Scroll Stability

피드에서 스크롤 안정성은 매우 중요하다.
새 데이터가 위에 추가되거나, 이미지가 늦게 로드되거나, 광고가 삽입되면 스크롤 위치가 흔들릴 수 있다.
사용자가 읽던 위치를 잃어버리면 피드 경험은 크게 나빠진다.
따라서 다음을 고려해야 한다.

- 이미지 영역 높이 선점
- 새 게시글 삽입 타이밍 제어
- 뒤로 가기 시 스크롤 복원
- 광고나 추천 게시글 삽입 시 위치 안정성 유지

6. Virtualization

피드가 길어지면 수천 개의 게시글을 모두 DOM에 올릴 수 없다.
브라우저가 느려지고 메모리 사용량이 증가한다.
이때 virtualization을 사용한다.
virtualization은 실제 화면에 보이는 항목만 렌더링하고, 보이지 않는 항목은 DOM에서 제외하는 방식이다.

전체 데이터: 10,000개
실제 렌더링: 현재 화면 주변 20~50개

사용자는 긴 리스트를 보는 것처럼 느끼지만, 브라우저는 훨씬 적은 DOM만 관리한다.

7. RUM

Threads 같은 서비스에서는 실제 사용자가 어떤 환경에서 문제를 겪는지 알아야 한다.
개발 환경에서는 빠르게 동작하더라도 실제 사용자 환경에서는 다를 수 있다.

- 특정 국가에서 피드 로딩이 느린가?
- 특정 브라우저에서 좋아요 버튼 에러가 많은가?
- 특정 앱 버전 이후 스크롤 이탈률이 증가했는가?
- 저사양 기기에서 long task가 많이 발생하는가?

이런 데이터를 봐야 실제 문제를 찾을 수 있다.


Amazon 장바구니도 단순한 cart 배열이 아니다

장바구니를 처음 구현할 때는 보통 이렇게 생각한다.

const cart = [
  { productId: 1, quantity: 2 },
  { productId: 2, quantity: 1 },
];

그리고 이 배열을 화면에 보여주면 된다고 생각한다.
하지만 실제 장바구니는 훨씬 복잡하다.

- 재고가 부족하면 어떻게 할 것인가?
- 가격이 변경되면 어떻게 보여줄 것인가?
- 쿠폰 적용 상태는 어디에 둘 것인가?
- 비회원 장바구니와 회원 장바구니를 어떻게 합칠 것인가?
- 여러 탭에서 장바구니를 수정하면 어떻게 동기화할 것인가?
- 결제 직전 서버 상태와 어떻게 검증할 것인가?

장바구니는 단순 배열이 아니라 서버 상태, 사용자 입력, 결제 흐름, 재고 정책이 얽힌 상태 시스템이다.


Netflix도 단순한 video 태그가 아니다

Netflix 같은 서비스를 단순히 보면 이렇게 생각할 수 있다.

<video src="movie.mp4" controls />

하지만 실제 동영상 서비스는 단순 video 태그로 끝나지 않는다.

- 네트워크 상태에 따라 화질을 바꿔야 한다.
- 사용자의 시청 위치를 저장해야 한다.
- 자막과 오디오 트랙을 동기화해야 한다.
- DRM을 처리해야 한다.
- 버퍼링을 최소화해야 한다.
- 모바일, TV, 브라우저별 동작 차이를 처리해야 한다.
- 재생 실패를 감지하고 복구해야 한다.

즉, 화면에 비디오를 띄우는 것과 안정적인 스트리밍 경험을 제공하는 것은 전혀 다른 문제다.


핵심은 “보여주기”가 아니라 “운영하기”다

프론트엔드 구현을 단순하게 보면 이렇게 보인다.

Amazon 상품 페이지 = 상품 정보 보여주기
Threads 피드 = 게시글 리스트 보여주기
Amazon 장바구니 = cart 배열 보여주기
Netflix = video 태그 넣기

하지만 시스템 관점에서는 이렇게 바뀐다.

Amazon 상품 페이지
= 가격, 재고, 이미지, 사용자 입력, 서버 검증, 장애 처리를 조율하는 시스템

Threads 피드
= 캐시, 페이지네이션, 실시간 데이터, 스크롤 안정성, 낙관적 업데이트를 다루는 시스템

Amazon 장바구니
= 서버 상태, 클라이언트 상태, 재고, 가격, 결제 검증을 다루는 상태 시스템

Netflix
= 미디어 로딩, 네트워크 품질, 버퍼링, 복구, 기기 호환성을 다루는 재생 시스템

프론트엔드 시스템 디자인은 이 차이를 이해하는 데서 시작한다.


프론트엔드 시스템 디자인에서 계속 던져야 하는 질문

좋은 프론트엔드 설계는 “어떻게 보여줄까?”에서 끝나지 않는다.
계속 이런 질문을 해야 한다.

데이터는 언제 가져올 것인가?
가져온 데이터는 얼마나 신뢰할 것인가?
언제 다시 검증할 것인가?
실패하면 어떻게 복구할 것인가?
사용자 입력이 빠르게 반복되면 어떻게 처리할 것인가?
화면이 느리게 로드될 때 레이아웃은 안정적인가?
저사양 기기에서도 반응성이 유지되는가?
특정 브라우저에서만 문제가 생기면 어떻게 찾을 것인가?
실제 사용자 환경의 성능을 측정하고 있는가?

이 질문을 하지 않으면 프론트엔드는 단순한 사이드 프로젝트 수준에 머물 수 있다.
반대로 이 질문을 하기 시작하면 프론트엔드는 시스템 설계의 영역으로 들어간다.


정리

프론트엔드는 더 이상 HTML, CSS, JavaScript로 화면을 만드는 일에만 머물지 않는다.
현대 프론트엔드는 사용자와 서버 사이에서 상태를 조율하고, 성능을 관리하고, 장애를 복구하고, 브라우저와 기기 차이를 흡수하며, 실제 사용자 경험을 안정적으로 유지하는 시스템이다.
그래서 프론트엔드 시스템 디자인은 중요하다.
단순히 API 데이터를 받아 화면에 보여주는 것과, 수많은 사용자가 안정적으로 사용할 수 있는 화면을 설계하는 것은 다르다.
상품 페이지 하나를 만들더라도 가격 갱신, 재고 부족, 빠른 입력, 이미지 로딩, 모바일 성능, 브라우저별 장애까지 고려해야 한다.
뉴스피드 하나를 만들더라도 서버 상태 캐시, 커서 페이지네이션, 실시간 pending buffer, optimistic like, scroll stability, virtualization, RUM까지 고려해야 한다.
결국 프론트엔드 시스템 디자인의 핵심은 이것이다.

사용자가 보는 화면을 중심으로 상태, 데이터, 렌더링, 성능, 장애 복구, 관측 가능성을 함께 설계하는 것.

프론트엔드는 화면의 끝이 아니라, 사용자가 직접 만지는 시스템의 시작점이다.