본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

UI 엔지니어링의 열두 가지 문제

작성 2026. 10. 17.추가
목차16개 항목
출처와 참고

Dan Abramov의 「The Elements of UI Engineering」(2018.12)을 바탕으로 재구성한 노트다. 문제 정의는 원문을 따르고, 각 문제를 다루는 도구는 지금 기준으로 덧붙였다.

기술이 아니라 문제를 배운다

"올해는 어떤 라이브러리를 배워야 하나" 같은 로드맵은 끝이 없고, 쓴 사람이 틀릴 수도 있다. UI 엔지니어링에서 오래 남는 지식은 특정 기술이 아니라 반복해서 나타나는 문제와 그 문제의 트레이드오프다. 라이브러리는 이 문제들 중 일부에 대한 한 가지 답일 뿐이다.

이 문제들은 규모와 상관없이 나타난다. 자동완성 입력창이나 툴팁 하나에도, 수억 명이 쓰는 앱에도 똑같이 있다. 그래서 작은 앱에서 라이브러리 없이 직접 부딪혀 보면 깊이 배울 수 있다.

열두 가지 문제

문제핵심 질문지금 주로 쓰는 도구
일관성(Consistency)같은 데이터를 화면 여러 곳과 서버에서 어떻게 맞추나서버 상태 캐시(TanStack Query·SWR), 정규화 캐시, 낙관적 업데이트(useOptimistic)
반응성(Responsiveness)입력에 얼마나 빨리 눈에 보이는 반응을 주나INP 지표, useTransition·useDeferredValue, 긴 작업 쪼개기
지연(Latency)네트워크·코드 로딩을 기다리는 동안 무엇을 보여 주나Suspense 경계, 스켈레톤, 스트리밍 SSR, 프리페치
내비게이션(Navigation)화면을 오가도 맥락(스크롤·입력)이 남나URL 상태, 스크롤 복원, bfcache, React <Activity>
낡은 캐시(Staleness)캐시를 언제 갱신하고 버리나stale-while-revalidate, 쿼리 무효화, 태그 기반 재검증
엔트로피(Entropy)폭발하는 상태 조합을 어떻게 다루나판별 유니온, 상태 기계(XState), 리듀서
우선순위(Priority)서로 다른 부분이 화면·CPU·네트워크를 두고 다투지 않게 하나top layer(<dialog>·Popover API), 동시성 렌더링, fetchpriority
접근성(Accessibility)접근성을 나중 일이 아니라 기본값으로 만들 수 있나시맨틱 HTML을 기본으로 한 컴포넌트 라이브러리, 린터, 자동 검사
국제화(Internationalization)언어·방향이 바뀌어도 빠르고 쉽게 동작하나Intl API, 논리적 속성, 언어별 메시지 지연 로딩
전달(Delivery)코드를 언제, 얼마나 보내나코드 분할, 동적 import(), 서버 컴포넌트
복원력(Resilience)한 부분이 깨져도 나머지가 살아 있나에러 바운더리, 재시도, 부분 실패 UI
추상화(Abstraction)앱이 커져도 이미 푼 문제가 다시 생기지 않나컴포넌트·훅 경계, 디자인 시스템

1. 일관성

"좋아요"를 누르면 「나 외 3명이 좋아합니다」 문구가 바뀐다. 간단해 보이지만 같은 문구가 화면 여러 곳에 있을 수 있고, 버튼 배경색도 바뀌어야 하고, 호버하면 보이는 좋아요 목록에도 내 이름이 들어가야 한다. 다른 화면에 갔다 돌아와도 좋아요 상태를 잊으면 안 된다. 여기에 다른 사용자도 같은 데이터를 바꾼다.

  • 화면 안의 일관성: 같은 데이터를 여러 컴포넌트가 따로 들고 있지 않게 한다. 서버 데이터는 서버 상태 캐시 한 곳에 두고 구독한다(TanStack Query, SWR).
  • 서버와의 일관성: 요청 전에 화면을 먼저 바꾸는 낙관적 업데이트(optimistic update)와, 실패했을 때의 롤백을 함께 설계한다.
  • 다른 사용자의 변경: 폴링, 포커스 시 재검증, WebSocket·SSE 같은 푸시 중 무엇을 쓸지 고른다.

2. 반응성

사람은 반응 없는 화면을 오래 참지 못한다. 그 한계는 동작 종류마다 다르다.

동작허용 한계
스크롤·제스처 같은 연속 동작프레임 하나(60Hz에서 약 16ms)만 빠져도 버벅인다
클릭 같은 단발 동작100ms 미만의 지연은 모두 똑같이 빠르게 느낀다는 연구가 있다. 넘으면 진행 표시가 필요하다

뇌는 벤치마크가 아니다. 레이아웃을 들썩이게 하거나 로딩 단계를 여러 번 거치는 표시는 실제보다 오래 걸린 것처럼 느끼게 한다. 애니메이션 프레임을 떨어뜨리며 20ms에 처리하는 것보다, 프레임을 지키며 30ms에 처리하는 편이 더 빠르게 느껴질 수 있다.

지금은 이 문제를 INP(Interaction to Next Paint) 지표로 측정한다(웹 성능 지표와 Lighthouse). React에서는 급하지 않은 갱신을 useTransition으로 미뤄 입력 반응을 지킨다.

3. 지연

계산 비용은 저사양 기기에서 테스트해 보고 괜찮으면 무시할 수도 있다. 하지만 네트워크 지연은 피할 수 없다. 새 데이터·코드·애셋이 필요한 동작은 모두 비동기이고 「로딩 중」 상태를 가진다. 거의 모든 화면이 그렇다.

  • 스피너가 층층이 나타나는 「스피너 폭포」와 빈 구멍을 피한다. 로딩 경계를 의미 있는 단위로 묶는다(React Suspense).
  • 데이터가 도착하며 레이아웃이 튀지 않게 자리를 미리 잡는다(스켈레톤, 고정 크기). 이것이 CLS다.
  • 비동기 의존성을 바꿀 때마다 코드를 다시 배선하지 않아도 되는 구조를 만든다. 서버가 준비된 부분부터 보내는 스트리밍 SSR도 같은 문제에 대한 답이다.

4. 내비게이션

UI는 조작하는 동안 안정적이어야 한다. 눈앞의 것이 갑자기 사라지면 안 되고, 앱 안 링크든 브라우저 뒤로 가기든 마찬가지다.

  • 프로필의 /likes와 /follows 탭을 오갈 때, 탭 바깥의 검색어가 지워지면 안 된다.
  • 다른 화면으로 가는 것은 방에 들어가는 것과 같다. 돌아오면 두고 간 그대로(새 물건이 조금 더해진 채) 있기를 기대한다.
  • 피드 중간에서 프로필을 눌렀다 돌아왔는데 위치를 잃거나 다시 로딩을 기다리면 답답하다.

지금 쓰는 답은 이렇다. 필터·탭처럼 남겨야 할 상태는 URL에 둔다. 스크롤 위치를 복원하고 bfcache를 깨지 않게 한다. 숨겨진 화면의 상태를 언마운트하지 않고 유지하려면 React 19.2의 <Activity>를 쓴다.

5. 낡은 캐시

뒤로 가기를 즉시 보여 주려면 로컬 캐시가 필요하다. 하지만 캐시는 낡는다.

  • 아바타를 바꾸면 캐시도 바뀌어야 한다. 새 글을 쓰면 캐시에 바로 나타나거나 캐시가 무효화돼야 한다.
  • 글 등록이 실패하면 캐시는 어떻게 되나?
  • 캐시를 메모리에 얼마나 두나? 다시 가져온 피드를 기존 캐시와 이어 붙이나, 버리나?
  • 페이지네이션과 정렬은 캐시에 어떻게 표현하나?

TanStack Query의 staleTime·gcTime과 쿼리 무효화, Next.js의 태그 기반 재검증이 이 질문들에 대한 답이다. 질문 자체는 도구가 바뀌어도 남는다.

6. 엔트로피

사용자의 조작과 그 순서는 예측할 수 없다. 앱은 언제든 엄청나게 많은 상태 중 하나에 있을 수 있다. 버그 스크린샷을 보고 「이게 어떻게 가능하지」라고 묻게 되면 엔트로피에 진 것이다.

상태가 N개면 전이는 N×(N−1)개다. 버튼 상태가 보통·활성·호버·위험·비활성 다섯 가지면, 버튼을 갱신하는 코드는 5×4=20가지 전이에서 모두 맞아야 한다. 아니면 일부 전이를 금지해야 한다.

  • 불가능한 상태를 타입으로 표현할 수 없게 만든다. isLoading·isError·data를 따로 두지 말고 판별 유니온 하나로 둔다(예측 가능성을 높이는 전략).
  • 전이가 복잡하면 상태 기계로 허용된 전이만 명시한다.

7. 우선순위

어떤 것은 다른 것보다 중요하다.

  • 대화상자는 그것을 연 버튼 위에 떠야 하고, 부모 컨테이너의 overflow 잘림을 뚫고 나와야 한다. 지금은 <dialog>의 showModal()과 Popover API가 요소를 브라우저의 top layer로 올려 이 문제를 푼다.
  • 새로 들어온 작업(클릭 응답)이 이미 진행 중인 긴 작업(화면 아래 글 렌더링)보다 중요할 수 있다. React의 동시성 렌더링은 급한 갱신이 덜 급한 렌더링을 끊고 먼저 처리되게 한다.
  • 앱이 커지면 여러 팀의 코드가 CPU·네트워크·화면 공간·번들 크기를 두고 다툰다.

CSS z-index처럼 하나의 「중요도」 축에 줄 세우는 방식은 대개 끝이 좋지 않다. 모두가 자기 코드가 중요하다고 생각하고, 모든 게 중요하면 아무것도 중요하지 않다. 서로 다투는 대신 협력하게 하는 구조(레이어, 스케줄러, 성능 예산)가 필요하다.

8. 접근성

접근성이 떨어지는 사이트는 소수의 문제가 아니다. 세계 인구의 16%, 6명 중 1명꼴인 약 13억 명이 의미 있는 장애를 겪고 있고(WHO, 2023), 얇은 글꼴과 낮은 대비는 나이가 들면 누구에게나 장벽이 된다. 다행히 쉽게 딸 수 있는 열매가 많다. 교육과 도구에서 시작하되, 결국 제품 개발자가 옳은 일을 하기 쉽게 만들어야 한다. 기본 컴포넌트가 처음부터 접근 가능하면 접근성은 기본값이 된다. 자세한 내용은 Accessibility 주제에 있다.

9. 국제화

앱은 전 세계에서 동작해야 한다. 언어가 다를 뿐 아니라 오른쪽에서 왼쪽으로 쓰는(RTL) 레이아웃도 제품 개발자의 수고를 최소로 하며 지원해야 한다. 그러면서 지연과 반응성을 희생하면 안 된다.

  • 날짜·숫자·복수형은 Intl API에 맡긴다.
  • margin-left 대신 margin-inline-start 같은 논리적 속성을 쓰면 RTL이 거의 공짜로 따라온다.
  • 모든 언어의 메시지를 한 번에 싣지 말고 현재 언어만 불러온다. lang·hreflang은 다국어 페이지에 있다.

10. 전달

코드를 사용자의 기기로 보내야 한다. 어떤 전송 방식과 형식을 쓸까? 단순해 보이지만 트레이드오프가 크다.

방식장점비용
네이티브 앱처럼 미리 다 받기쓰는 동안 지연이 없다앱 크기가 크다
웹처럼 필요할 때 받기첫 로딩이 작다쓰는 도중 지연이 생긴다

결국 지연을 어느 시점에 둘지 고르는 문제다. 사용 패턴(어떤 화면을 다음에 갈 확률이 높나)에 맞춰 코드 분할·프리페치를 정하고, 그러려면 실제 사용 데이터가 필요하다. 서버 컴포넌트는 렌더링 코드를 아예 클라이언트로 보내지 않는 선택지를 더했다(SPA와 CSR·SSR).

11. 복원력

버그 일부는 반드시 프로덕션에 나간다. 그때 무슨 일이 일어나나?

  • 잘못됐지만 동작은 정의된 버그: 특정 조건에서 화면이 틀리게 나온다.
  • 렌더링 코드가 터지는 버그: 화면이 앞뒤가 안 맞는 상태가 되므로 그대로 계속할 수 없다.

글 하나의 렌더링 실패가 피드 전체를 무너뜨리거나, 반쯤 깨진 상태로 연쇄 실패를 일으켜서는 안 된다. 렌더링과 데이터 요청의 실패를 격리하고 나머지가 계속 돌게 하는 것이 UI의 장애 허용(fault tolerance)이다. React에서는 에러 바운더리로 실패 범위를 정한다(프런트엔드 에러 핸들링).

12. 추상화

작은 앱에서는 위 문제들을 특수 사례로 하드코딩해도 된다. 하지만 앱은 자란다. 코드를 재사용하고, 갈라 쓰고, 합치고, 여럿이 함께 고쳐야 한다.

  • 서로 다른 사람이 아는 영역 사이에 분명한 경계를 둔다.
  • 자주 바뀌는 로직을 너무 단단하게 굳히지 않는다.
  • 특정 UI 조각의 구현 상세를 숨기면서도, 앱이 커질 때 이미 푼 문제가 다시 생기지 않게 한다.

컴포넌트 경계와 공통화의 판단 기준은 변경하기 쉬운 코드의 네 가지 기준과 컴포넌트를 분리하는 기준과 방법에 있다.

이 목록으로 공부하는 법

목록은 완전하지 않다. 디자이너와의 협업, 디버깅과 테스트도 같은 급의 문제다.

  1. 특정 뷰·데이터 라이브러리를 답으로 떠올리지 말고, 라이브러리가 없다고 가정한 채 각 문제를 다시 읽는다.
  2. 라이브러리 없이 작은 앱을 만들어 문제를 직접 재현하고 풀어 본다. 정답은 없고, 여러 트레이드오프를 시도하는 과정에서 배운다.
  3. 자주 쓰는 앱의 복잡한 UI 요소 하나를 골라 이 목록을 대 본다. 개발자가 어떤 트레이드오프를 골랐는지 설명하고, 처음부터 비슷하게 만들어 본다.

정리

  • UI 엔지니어링의 실력은 라이브러리 목록이 아니라 반복되는 문제와 그 트레이드오프를 아는 데서 나온다.
  • 열두 문제는 툴팁 하나에도, 거대한 앱에도 똑같이 나타난다.
  • 도구는 바뀌어도(서버 상태 캐시, Suspense, top layer, 에러 바운더리) 문제는 그대로 남는다.
  • 라이브러리 없이 작은 앱으로 문제를 재현해 보는 것이 가장 깊이 배우는 방법이다.

관련 노트: 리액트 상태 관리의 흐름과 고르는 기준, TanStack Query(React Query), 웹 성능 지표와 Lighthouse, 프런트엔드 에러 핸들링: 분류와 선언적 처리, 변경하기 쉬운 코드의 네 가지 기준, 프론트엔드 로드맵, UI 밀도: 시각·정보·디자인·시간·가치

원본 기록2

댓글

GitHub 계정으로 댓글을 남길 수 있어요.