허점이 없는 UI로 UX를 향상시키기

현재 제가 다니고 있는 회사에선 하나의 프로젝트를 마무리했습니다. AI 모델을 이용해 여러가지 테마에 맞게 2D 이미지와 3D 모델을 제작해주는 플랫폼입니다. 저는 프로젝트의 프론트엔드를 담당했는데요 구현하면서 '좋은 UX란 무엇인가'에 대해서 많은 고민을 했었습니다. 제가 내린 결론은 이것이었습니다.
그럼 어떤 방법을 사용해야 사용자가 실수라고 느끼지 못하고 지나갈 수 있을까요? 이번 프로젝트에서 사용했던 방법들에 대해서 말씀드리려고 합니다. :)
비동기 요청 작업 시 중복을 방지하기
사용자는 개발자가 의도한 것처럼 버튼을 한 번 누르고 반응이 나타날때까지 기다리고 있지 않습니다. 특히 설문 결과를 제출하거나 로그인을 하거나 결제를 할 때 아무런 반응이 없을 때 여러 번 눌러보게 됩니다. 저 또한 그렇구요. 이때 설문이 중복으로 처리되거나 결제가 중복으로 발생하면 사용자는 플랫폼 잘 사용을 하다가도 이탈하게 될 가능성이 높습니다. 그렇기 때문에 인터랙션이 발생하면 fetch API 요청을 보내는 동안에는 버튼을 비활성화로 변경하거나, 로딩 컴포넌트를 보여주었습니다.

로그인 버튼 클릭 시 로딩이 걸리는 구간에 isLoading 상태를 이용하여 중복 호출을 방지합니다. 그리고 UI도 disabled 처리를 하여 사용자에게 피드백을 전달합니다.
또한 try-catch 문을 이용해 에러가 발생했을 때 성공했을 때 확인팝업을 보여주면 사용자는 더 직관적으로 결과를 확인할 수 있도록 구현했습니다.
아래 코드는 실제 프로젝트의 코드를 간단한 예시로 변경했습니다.
try {
setIsLoading(true);
setMessage('성공했습니다');
} catch (error) {
setMessage(`실패했습니다. ${에러_원인}`);
} finally {
setIsLoading(false); // 성공케이스, 실패케이스 모두 해당
messageDialog.open(); // message를 보여주는 팝업
}에러 메시지를 사용자에게 친절하게 알려주기
대부분의 서버에서 전달받는 에러 메시지는 코드화되어 있거나 사용자가 이해하기 어렵습니다. 메시지를 본 사용자는 '그래서 어떻게 해야 하는데?'라는 생각을 하게 됩니다.
이번 프로젝트에서도 이 문제를 겪었습니다. 생성형 AI 모델로 이미지를 만들 때 예상치 못한 에러가 반환되는 경우가 있었고, 기획 단계에서 개발팀과 기획자 간의 논의가 필요했습니다. 모든 에러 케이스를 예측하기는 어려웠기에, 자주 발생하는 에러에 대해서는 사용자 친화적인 메시지를 미리 준비하고 대응 방법을 안내했습니다.
const IMAGE_GENERATION_ERROR_MESSAGES = {
// 입력 관련
PROMPT_EMPTY: '이미지 설명을 입력해주세요.',
PROMPT_TOO_LONG: '이미지 설명이 너무 깁니다. 500자 이내로 작성해주세요.',
INVALID_PROMPT: '이미지 설명에 허용되지 않는 내용이 포함되어 있습니다.',
// 이미지 설정 관련
INVALID_SIZE: '지원하지 않는 이미지 크기입니다.',
INVALID_FORMAT: '지원하지 않는 이미지 형식입니다.',
INVALID_STYLE: '선택한 스타일을 사용할 수 없습니다.',
// 콘텐츠 필터링
CONTENT_POLICY_VIOLATION: '요청하신 내용은 콘텐츠 정책에 위배되어 생성할 수 없습니다. 다른 설명으로 다시 시도해주세요.',
NSFW_DETECTED: '부적절한 콘텐츠가 감지되었습니다. 설명을 수정해주세요.',
// 사용량/권한 관련
RATE_LIMIT_EXCEEDED: '요청이 너무 많습니다. 잠시 후 다시 시도해주세요.',
DAILY_LIMIT_REACHED: '오늘의 이미지 생성 횟수를 모두 사용했습니다. 내일 다시 이용해주세요.',
INSUFFICIENT_CREDITS: '크레딧이 부족합니다. 충전 후 이용해주세요.',
FEATURE_NOT_AVAILABLE: '현재 플랜에서는 이 기능을 사용할 수 없습니다. 플랜을 업그레이드해주세요.',
// 서버/시스템 관련
MODEL_UNAVAILABLE: '이미지 생성 서비스가 일시적으로 사용 불가합니다. 잠시 후 다시 시도해주세요.',
GENERATION_TIMEOUT: '이미지 생성 시간이 초과되었습니다. 다시 시도해주세요.',
GENERATION_FAILED: '이미지 생성에 실패했습니다. 다시 시도해주세요.',
SERVER_ERROR: '서버 오류가 발생했습니다. 문제가 지속되면 고객센터로 문의해주세요.',
// 네트워크 관련
NETWORK_ERROR: '네트워크 연결을 확인해주세요.',
REQUEST_CANCELLED: '요청이 취소되었습니다.',
};또한 백엔드 개발자와 에러 코드 형식을 정규화하기로 협의했습니다. 덕분에
IMAGE_GENERATION_ERROR_MESSAGES[errorCode]처럼 직관적으로 메시지를 매핑할 수 있었고, 작업 속도도 빨라졌습니다. 사용자 경험(UX)을 개선하기 위한 작업이 개발자 경험(DX)까지 함께 향상시킨 셈입니다.
위험한 작업들은 꼭 한 번 더 물어보기
삭제, 로그아웃, 결제하기와 같은 민감한 기능은 클릭 한 번에 바로 처리되어선 안 됩니다. 사용자가 한 번 더 신중하게 판단할 시간이 필요하기 때문입니다. 그래서 FigureAI에서는 확인 팝업을 통해 재확인하는 로직을 추가했습니다.


그리고 팝업을 컴포넌트화하여 재사용하도록 구현했습니다.
SSR의 이점을 잘 이용하기
UI로 좋은 UX를 만들 수 있지만, 유의미한 데이터를 빠르게 보여주는 것으로도 UX를 개선할 수 있습니다. 만약 Next.js를 이용해 프로젝트를 구현하고 있다면 CSR과 SSR을 적절히 사용하여 UX를 향상시킬 수 있습니다.
모든 데이터를 클라이언트에서 Fetch API로 가져오면 어떻게 될까요? 빈 HTML을 서버에서 받고 자바스크립트를 실행할때까지의 시간이 길어지면 사용자는 빈 화면을 계속해서 보는 상황이 발생합니다. 그렇다면 모든 데이터를 서버에서 가져오면 어떻게 될까요? 사용자는 완전한 HTML을 한번에 볼 수는 있지만, 서버가 모든 데이터를 준비할 때까지 아무것도 볼 수 없습니다. 데이터가 많거나 API 응답이 느리면 흰 화면이 길어집니다.
따라서 FigureAI에서는 초기 렌더링에 필요한 핵심 데이터는 SSR로, 사용자 인터랙션 후 필요한 데이터는 CSR로 가져오는 방식을 혼합하여 사용하였습니다.

사용자는 스타일을 먼저 고른 뒤 사진을 업로드할 것이라고 예상했습니다. 그래서 스타일 목록은 서버에서 미리 가져오고, 업로드 로직은 클라이언트에서 처리하도록 구현했습니다.
글을 작성하면서

이번 프로젝트를 통해 좋은 UX는 거창한 기능이 아니라 작은 배려에서 시작된다는 것을 느꼈습니다.
버튼을 비활성화하고, 에러 메시지를 친절하게 바꾸고, 위험한 작업 전에 한 번 더 물어보는 것. 사소해 보이지만 이런 디테일들이 모여 사용자가 "불편함을 느끼지 못하는" 경험을 만든다고 생각합니다.
물론 아직 개선할 부분이 많습니다. 하지만 "완벽한 코드"보다 "실수를 느끼지 못하게 하는 코드"를 고민하는 습관을 들이게 된 것만으로도 의미 있는 프로젝트였습니다.