점수 말고 행동: 사용자가 느끼는 UI 반응성 설계법

점수 말고 행동: 사용자가 느끼는 UI 반응성 설계법

Lighthouse 100점짜리 앱에서 유저가 버튼을 다섯 번 클릭하는 이유—INP와 폼 상태 지속성이 가리키는 진짜 UX의 기준.

INP Optimistic UI Core Web Vitals 폼 상태 지속성 localStorage UI 반응성 scheduler.yield UX 완료율
광고

Lighthouse는 거짓말을 하지 않는다. 다만, 전부를 말하지도 않는다

백엔드 응답 시간 80ms, Lighthouse 성능 점수 만점. 그런데 세션 녹화를 열어보면 유저가 같은 버튼을 네다섯 번 연속으로 누르고 있다. 에러 로그는 없다. 요청도 정상적으로 처리됐다. 그렇다면 문제는 어디에 있을까?

dev.to에 공유된 실제 Next.js 대시보드 구축 사례는 이 질문에 명확한 답을 제시한다. 문제는 성능이 아니었다. 침묵이었다. 버튼을 눌렀을 때 화면이 아무것도 바뀌지 않았다. 로딩 스피너도, 비활성화 상태도, 어떤 시각적 피드백도 없었다. 유저 입장에서는 클릭이 '먹히지 않은' 것이다. 그래서 다시 눌렀다. 그리고 또 눌렀다. 조급함이 아니라 합리적인 판단의 결과였다.

INP가 측정하는 것: '빠름'이 아니라 '응답'

우리는 오랫동안 FCP, LCP, 번들 사이즈 같은 로딩 성능 지표에 집중해왔다. 하지만 유저가 실제로 앱과 보내는 시간의 대부분은 페이지가 로드된 이후다. 신뢰는 로딩 화면이 아니라 인터랙션 순간에 결정된다.

Core Web Vitals의 INP(Interaction to Next Paint)는 바로 이 지점을 측정한다. API가 응답한 시점도, 함수 실행이 끝난 시점도 아니다. 유저가 행동한 후 화면에 무언가 변화가 시각적으로 나타나기까지의 시간이다. 100~200ms 이내면 즉각적으로 느껴지고, 500ms를 넘어서면 유저는 자신의 행동이 처리됐는지를 의심하기 시작한다. 느린 앱은 답답하지만, 무반응 앱은 이탈을 만든다.

수정은 '더 빠르게'가 아니라 '먼저 말을 걸어라'

해결책은 처리 속도를 높이는 것이 아니었다. 순서를 바꾸는 것이었다. 기존 코드는 모든 작업(데이터 처리 → API 호출)이 끝난 후에야 UI를 업데이트했다. 수정된 코드는 클릭 즉시 setLoading(true)를 호출하고, scheduler.yield()(또는 setTimeout(r, 0) 폴백)로 브라우저가 먼저 페인트할 수 있도록 양보한 뒤 나머지 작업을 이어간다. 총 처리 시간은 동일하다. 하지만 경험은 완전히 다르다.

이 패턴의 확장판이 바로 Optimistic UI다. '좋아요'를 누르면 서버 응답을 기다리지 않고 즉시 카운트가 올라간다. 실패하면 롤백한다. React와 Next.js 환경에서는 useOptimistic과 Server Actions가 이 흐름을 구조적으로 지원한다. 원칙은 하나다: 결과를 먼저 보여주고, 실제 처리는 뒤에서 조율한다.

폼도 같은 문제다: 진행한 것을 기억하지 못하는 UI

반응성의 문제는 클릭 피드백에서만 발생하지 않는다. 다단계 폼에서 탭을 닫거나 새로고침했을 때 모든 입력이 사라지는 상황은, 침묵하는 버튼과 동일한 UX 실패 패턴이다. 유저의 노력을 기억하지 못하는 UI는 신뢰를 잃는다.

Web Storage API를 다룬 또 다른 실전 가이드는 이 문제를 정량적으로 정의한다. 보험 가입, 대출 신청, B2B 온보딩처럼 여러 단계에 걸친 폼에서 중간에 이탈한 유저는 빈 폼으로 돌아왔을 때 다시 시작하지 않는 경우가 많다. 이탈로 이어지고, 때로는 영구적으로 돌아오지 않는다. localStoragesessionStorage를 활용한 상태 지속성은 낮은 구현 비용으로 완료율에 직접적인 영향을 미치는 몇 안 되는 UX 개선 중 하나다.

구현 패턴은 명확하다. React에서는 useState 초기화 함수에서 저장된 값을 동기적으로 읽어 빈 필드 플래시 없이 복원하고, React Hook Form이라면 watch()로 변경을 감지해 localStorage에 직렬화하고 reset()으로 복원한다. 여기서 중요한 세 가지 정리 시점이 있다: 만료 시간 초과 시 자동 삭제, 제출 성공 후 클리어, 그리고 유저가 직접 초기화할 수 있는 '다시 시작' 버튼. 그리고 절대로 저장하지 말아야 할 것들—카드 번호, 비밀번호, 인증 토큰—을 직렬화 대상에서 명시적으로 제외해야 한다.

시사점: '빠른 UI'가 아니라 '반응하는 UI'를 설계하라

두 사례가 함께 가리키는 결론은 하나다. 성능 점수는 통제된 환경의 스냅샷이다. 유저는 느린 기기, 불안정한 네트워크, 멀티태스킹 중에 우리 앱을 사용한다. Lighthouse 100점이 보장하지 못하는 것은 바로 이 현실 속 상호작용의 품질이다.

진짜 반응성은 숫자가 아니라 행동에서 드러난다. 클릭 직후 무언가 바뀌는가. 뒤로 돌아왔을 때 내 작업이 남아있는가. 실패했을 때 UI가 정직하게 롤백하는가. 이 질문들에 '예스'라고 답할 수 있을 때, 비로소 유저는 앱을 신뢰한다.

전망: 반응성 설계는 컴포넌트 단위로 내재화된다

React의 useOptimistic, Next.js의 Server Actions와 Streaming, 그리고 스케줄러 API의 점진적 확산은 이 패턴들을 프레임워크 수준에서 기본값으로 만들어가고 있다. 개발자가 매번 수동으로 순서를 고민하지 않아도 되는 방향으로 생태계가 움직이고 있다는 뜻이다. 하지만 도구가 패턴을 지원하더라도, '클릭 직후 무엇을 보여줄 것인가'를 먼저 설계하는 사고방식은 여전히 개발자 몫이다. 측정할 수 있는 것을 최적화하기 전에, 유저가 느끼는 것을 먼저 상상하는 습관—그것이 점수 너머의 UI 품질을 결정한다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요