0720 프론트엔드 실무 심화 (3/N): 로딩, 에러, 빈 상태와 사용자 피드백 설계
✅ 1. 화면 상태란 무엇인가?
- 화면 상태(UI State)는 사용자가 현재 화면에서 어떤 상황을 보고 있는지를 의미합니다.
- 프론트엔드 화면은 단순히 데이터가 있을 때만 생각하면 안 됩니다.
- 실제 운영 서비스에서는 로딩 중, 에러 발생, 데이터 없음, 권한 없음, 제출 중, 저장 완료 같은 다양한 상태가 존재합니다.
API 요청 전
↓
로딩 중
↓
성공
↓
데이터 있음 / 데이터 없음
↓
에러 발생 가능
➕ 1-1. 화면 상태가 중요한 이유
- 사용자가 지금 무슨 일이 일어나고 있는지 알 수 있습니다.
- 불필요한 재클릭과 이탈을 줄일 수 있습니다.
- 관리자 업무 중 실수를 줄일 수 있습니다.
- 서버 에러와 사용자 실수를 구분해 안내할 수 있습니다.
- 서비스가 더 안정적이고 완성도 있게 느껴집니다.
나쁜 화면:
버튼 눌렀는데 아무 변화 없음
목록이 비어 있는데 설명 없음
에러가 났는데 그냥 빈 화면
저장 중인지 완료됐는지 모름
- 기능이 실제로 있어도 화면 상태 처리가 부족하면 사용자는 “서비스가 안 된다”고 느낍니다.
✅ 2. 프론트엔드에서 반드시 처리해야 하는 상태
- 대부분의 화면은 최소 4가지 상태를 가져야 합니다.
| 상태 | 의미 | 예시 |
|---|
| Loading | 데이터를 불러오는 중 | 상품 목록 로딩 |
| Success | 요청 성공 | 상담 목록 표시 |
| Empty | 성공했지만 데이터 없음 | 검색 결과 없음 |
| Error | 요청 실패 | 서버 오류, 권한 오류 |
➕ 2-1. 관리자 상담 목록 예시
Loading:
상담 목록을 불러오는 중
Success:
상담 목록 테이블 표시
Empty:
조건에 맞는 상담 신청이 없음
Error:
상담 목록을 불러오지 못함
- 이 4가지 상태만 잘 처리해도 화면 품질이 크게 좋아집니다.
- 특히 관리자 페이지에서는 Empty와 Error를 구분해야 합니다.
✅ 3. Loading 상태
- Loading 상태는 데이터를 불러오는 중임을 보여주는 상태입니다.
- 로딩 표시가 없으면 사용자는 버튼을 다시 누르거나 페이지가 멈췄다고 생각할 수 있습니다.
➕ 3-1. 로딩이 필요한 상황
페이지 첫 진입
목록 조회
상세 조회
검색/필터 변경
폼 제출
파일 업로드
엑셀 다운로드 요청
상태 변경
배너/상품 저장
➕ 3-2. 나쁜 로딩 처리
아무 표시 없음
전체 화면이 갑자기 비어 있음
버튼은 계속 클릭 가능
로딩 중인지 에러인지 구분 안 됨
➕ 3-3. 좋은 로딩 처리
스켈레톤 표시
버튼 disabled
"저장 중..." 텍스트 표시
목록 영역에만 로딩 표시
기존 데이터를 유지하면서 새 데이터 refetch
- 로딩은 무조건 전체 화면을 막는 것이 아니라, 상황에 맞게 보여줘야 합니다.
✅ 4. Spinner와 Skeleton
- 로딩 UI에는 대표적으로 Spinner와 Skeleton이 있습니다.
| 방식 | 특징 | 적합한 상황 |
|---|
| Spinner | 단순 회전 아이콘 | 짧은 작업, 버튼 내부 |
| Skeleton | 실제 레이아웃과 비슷한 회색 박스 | 목록/카드/상세 로딩 |
| Progress | 진행률 표시 | 파일 업로드, 대용량 Export |
| Disabled Button | 중복 클릭 방지 | 폼 제출, 상태 변경 |
➕ 4-1. Spinner가 적합한 경우
저장 버튼 클릭 후 처리 중
상태 변경 요청 중
짧은 API 요청
작은 영역의 로딩
➕ 4-2. Skeleton이 적합한 경우
상품 목록 로딩
상담 목록 테이블 로딩
상품 상세 페이지 로딩
관리자 대시보드 카드 로딩
- 목록이나 상세 페이지는 Spinner보다 Skeleton이 더 안정적으로 느껴질 때가 많습니다.
- 사용자가 “곧 이 자리에 데이터가 들어오겠구나”라고 예측할 수 있습니다.
✅ 5. TanStack Query에서 로딩 상태
- TanStack Query는 로딩 상태를 여러 값으로 제공합니다.
isLoading, isFetching, isPending의 차이를 이해하면 화면 처리가 더 좋아집니다.
| 값 | 의미 |
|---|
isLoading | 최초 데이터 로딩 중 |
isFetching | 데이터를 가져오는 중, 기존 데이터가 있을 수도 있음 |
isError | 요청 실패 |
data | 성공 데이터 |
error | 에러 객체 |
➕ 5-1. 기본 예시
const consultsQuery = useQuery({
queryKey: ['admin', 'consults', { page, status, keyword }],
queryFn: () =>
adminConsultApi.getConsults({
page,
status,
keyword,
}),
});
if (consultsQuery.isLoading) {
return <ConsultTableSkeleton />;
}
if (consultsQuery.isError) {
return <ErrorState message="상담 목록을 불러오지 못했습니다." />;
}
return <ConsultTable data={consultsQuery.data.items} />;
➕ 5-2. isFetching 활용
<>
{consultsQuery.isFetching && (
<div className="table-refresh-indicator">
최신 데이터를 불러오는 중입니다...
</div>
)}
<ConsultTable data={consultsQuery.data?.items ?? []} />
</>
- 최초 로딩은 Skeleton을 보여주고, 이후 필터 변경이나 재조회는 기존 데이터를 유지하면서 작은 로딩 표시만 보여줄 수 있습니다.
- 화면이 매번 통째로 깜빡이는 것보다 훨씬 자연스럽습니다.
✅ 6. keepPreviousData 개념
- 페이지네이션이나 필터 변경 시 기존 데이터를 유지하면서 새 데이터를 가져오면 화면이 덜 흔들립니다.
- TanStack Query 버전에 따라
placeholderData나 keepPreviousData 패턴을 활용할 수 있습니다.
➕ 6-1. 필요한 상황
관리자 상담 목록 페이지 이동
상품 목록 필터 변경
주문 목록 검색 조건 변경
유입경로 통계 날짜 변경
➕ 6-2. 예시
const consultsQuery = useQuery({
queryKey: ['admin', 'consults', { page, status, keyword }],
queryFn: () =>
adminConsultApi.getConsults({
page,
status,
keyword,
}),
placeholderData: (previousData) => previousData,
});
- 새 데이터를 불러오는 동안 기존 목록을 유지할 수 있습니다.
- 다만 검색 조건이 완전히 달라졌는데 기존 데이터가 오래 보이면 혼란스러울 수 있으므로 작은 로딩 표시를 함께 보여주는 것이 좋습니다.
✅ 7. Empty 상태
- Empty 상태는 요청은 성공했지만 보여줄 데이터가 없는 상태입니다.
- Empty는 에러가 아닙니다.
- 데이터가 없는 이유와 사용자가 다음에 할 수 있는 행동을 알려줘야 합니다.
➕ 7-1. Empty 상태가 필요한 화면
검색 결과 없음
상담 신청 내역 없음
상품 목록 없음
배너 목록 없음
주문 내역 없음
알림 실패 내역 없음
Export 파일 없음
➕ 7-2. 나쁜 Empty 상태
빈 테이블만 보여줌
하얀 화면
"데이터 없음"만 표시
➕ 7-3. 좋은 Empty 상태
조건에 맞는 상담 신청이 없습니다.
검색어 또는 필터 조건을 변경해 보세요.
등록된 상품이 없습니다.
새 상품을 등록해 고객 화면에 노출해 보세요.
- Empty 메시지는 현재 상황과 다음 행동을 같이 알려주는 것이 좋습니다.
✅ 8. Empty 상태 유형
- Empty 상태도 한 가지가 아닙니다.
- “처음부터 데이터가 없음”과 “검색 결과가 없음”은 메시지가 달라야 합니다.
| 유형 | 예시 메시지 |
|---|
| 초기 데이터 없음 | 등록된 상품이 없습니다. |
| 검색 결과 없음 | 조건에 맞는 결과가 없습니다. |
| 권한상 볼 데이터 없음 | 조회 가능한 데이터가 없습니다. |
| 실패 목록 없음 | 실패한 작업이 없습니다. |
| 알림 없음 | 확인할 알림이 없습니다. |
➕ 8-1. 상담 목록 Empty 메시지
조건에 맞는 상담 신청이 없습니다.
검색어, 상태, 유입경로 또는 날짜 조건을 변경해 보세요.
➕ 8-2. 알림 실패 Empty 메시지
실패한 알림 발송 내역이 없습니다.
현재까지 재처리가 필요한 알림은 없습니다.
- 실패 목록이 비어 있는 것은 좋은 상태입니다.
- 이런 경우에는 긍정적인 메시지로 보여주는 것이 좋습니다.
✅ 9. Error 상태
- Error 상태는 API 요청이 실패했거나 화면을 정상적으로 표시할 수 없는 상태입니다.
- 에러는 사용자에게 너무 기술적으로 보여주면 안 되지만, 운영자가 조치할 수 있을 정도의 안내는 필요합니다.
➕ 9-1. 에러 유형
네트워크 오류
서버 500 오류
권한 없음 403
로그인 만료 401
데이터 없음 404
검증 실패 400
중복 요청 409
➕ 9-2. 나쁜 에러 메시지
Error
undefined
Request failed
오류가 발생했습니다.
➕ 9-3. 좋은 에러 메시지
상담 목록을 불러오지 못했습니다.
잠시 후 다시 시도해 주세요.
계속 문제가 발생하면 관리자에게 문의해 주세요.
로그인 시간이 만료되었습니다.
다시 로그인해 주세요.
- 에러 메시지는 사용자가 할 수 있는 다음 행동을 알려줘야 합니다.
✅ 10. 에러 코드별 UI 처리
- 백엔드 에러 코드와 프론트 UI 처리를 연결하면 일관된 UX를 만들 수 있습니다.
| HTTP Status | 상황 | UI 처리 |
|---|
| 400 | 입력값 오류 | 필드 에러 표시 |
| 401 | 로그인 필요/만료 | 로그인 페이지 이동 |
| 403 | 권한 없음 | 권한 없음 안내 |
| 404 | 데이터 없음 | Not Found 또는 Empty 처리 |
| 409 | 중복/충돌 | 필드 에러 또는 안내 메시지 |
| 500 | 서버 오류 | 재시도 안내 |
| 503 | 일시적 장애 | 잠시 후 재시도 안내 |
➕ 10-1. API 에러 매핑 예시
export function getErrorMessage(error: ApiError) {
switch (error.statusCode) {
case 401:
return '로그인이 필요합니다.';
case 403:
return '이 작업을 수행할 권한이 없습니다.';
case 404:
return '요청한 데이터를 찾을 수 없습니다.';
case 409:
return error.message ?? '이미 처리된 요청입니다.';
default:
return '일시적인 문제가 발생했습니다. 잠시 후 다시 시도해 주세요.';
}
}
- 에러 메시지 매핑을 공통화하면 화면마다 다른 표현이 나오는 것을 줄일 수 있습니다.
✅ 11. Retry UI
- 실패한 요청은 사용자가 다시 시도할 수 있어야 합니다.
- 목록 조회, 상세 조회, Export 요청 같은 기능에는 재시도 버튼이 유용합니다.
➕ 11-1. ErrorState 컴포넌트 예시
type ErrorStateProps = {
title?: string;
message: string;
onRetry?: () => void;
};
function ErrorState({
title = '문제가 발생했습니다',
message,
onRetry,
}: ErrorStateProps) {
return (
<div role="alert" className="error-state">
<strong>{title}</strong>
<p>{message}</p>
{onRetry && (
<button type="button" onClick={onRetry}>
다시 시도
</button>
)}
</div>
);
}
➕ 11-2. 사용 예시
if (consultsQuery.isError) {
return (
<ErrorState
message="상담 목록을 불러오지 못했습니다."
onRetry={() => consultsQuery.refetch()}
/>
);
}
- 사용자가 새로고침하지 않아도 다시 요청할 수 있게 만들어야 합니다.
- 관리자 화면에서는 재시도 버튼이 특히 중요합니다.
✅ 12. Toast 피드백
- Toast는 작업 결과를 짧게 알려주는 메시지입니다.
- 저장 성공, 삭제 완료, 상태 변경 완료, 복사 완료 같은 작업에 적합합니다.
➕ 12-1. Toast가 적합한 상황
상품 저장 완료
상담 상태 변경 완료
배너 삭제 완료
클립보드 복사 완료
알림 재발송 요청 완료
엑셀 생성 요청 완료
➕ 12-2. Toast가 부적합한 상황
중요한 경고
삭제 confirm
긴 설명이 필요한 에러
권한 없음 상세 안내
서비스 전체 장애 안내
- 중요한 정보는 Toast만으로 끝내면 사용자가 놓칠 수 있습니다.
- Toast는 짧은 피드백에 적합합니다.
➕ 12-3. 메시지 예시
저장되었습니다.
상담 상태가 변경되었습니다.
엑셀 파일 생성을 요청했습니다.
알림톡 재발송을 요청했습니다.
삭제되었습니다.
- 관리자용 Toast는 짧고 명확해야 합니다.
- 고객용 Toast는 조금 더 친절한 표현을 써도 됩니다.
✅ 13. Modal과 Alert
- Toast와 달리 Modal이나 Alert는 사용자의 주의를 확실히 끌어야 할 때 사용합니다.
➕ 13-1. Modal이 적합한 상황
삭제 확인
상태 변경 확인
사전예약 오픈/종료 확인
중요 설정 변경
완료 안내가 중요한 고객 신청
➕ 13-2. Alert 영역이 적합한 상황
폼 상단 서버 에러
권한 없음 안내
일시적 서비스 장애 안내
중요 공지
검증 실패 요약
➕ 13-3. 예시
현재 알림톡 발송 서비스가 일시적으로 불안정합니다.
상담 신청은 정상 접수되며, 알림은 복구 후 순차 발송됩니다.
- 화면 안에 계속 보여줘야 하는 정보는 Toast보다 Alert가 낫습니다.
✅ 14. Optimistic UI
- Optimistic UI는 서버 응답을 기다리기 전에 화면을 먼저 바꿔주는 방식입니다.
- 사용자는 빠르게 반응한다고 느끼지만, 실패 시 되돌리는 처리가 필요합니다.
➕ 14-1. 적합한 상황
좋아요 버튼
간단한 토글
읽음 처리
정렬 순서 변경
➕ 14-2. 조심해야 하는 상황
주문 상태 변경
상담 완료 처리
결제/환불
권한 변경
상품 삭제
알림톡 발송
- 운영 영향이 큰 작업은 Optimistic UI를 조심해야 합니다.
- 관리자 상태 변경은 서버 성공 후 화면을 바꾸는 것이 더 안전할 때가 많습니다.
➕ 14-3. 기준
실패해도 쉽게 되돌릴 수 있는 작업:
Optimistic UI 가능
실패하면 운영 데이터가 꼬이는 작업:
서버 성공 후 반영 권장
✅ 15. Disabled와 Pending 상태
- 사용자가 작업 중 같은 버튼을 여러 번 누르면 중복 요청이 발생할 수 있습니다.
- 버튼과 입력 필드는 요청 중 상태에 따라 비활성화해야 합니다.
➕ 15-1. 상태 변경 버튼 예시
<button
type="button"
disabled={updateStatusMutation.isPending}
onClick={() => updateStatusMutation.mutate({ id, status: 'DONE' })}
>
{updateStatusMutation.isPending ? '처리 중...' : '완료 처리'}
</button>
➕ 15-2. 주의할 점
프론트 disabled는 UX용
백엔드 중복 요청 방지는 별도 필요
요청 실패 시 버튼이 다시 활성화되어야 함
성공 후에는 상태에 맞게 버튼 노출 변경
- 프론트에서 중복 클릭을 막아도 백엔드에서 동시성/중복 방지는 필요합니다.
✅ 16. 권한 없음 상태
- 관리자 페이지에서는 권한 없음 상태를 잘 처리해야 합니다.
- 버튼을 숨기는 것과 권한 없음 화면을 보여주는 것은 다릅니다.
➕ 16-1. 권한 처리 방식
메뉴 접근 권한 없음:
권한 없음 페이지 표시
특정 버튼 권한 없음:
버튼 숨김 또는 disabled
API 403 응답:
권한 없음 안내 표시
백엔드:
반드시 권한 검사
➕ 16-2. 권한 없음 메시지 예시
이 페이지에 접근할 권한이 없습니다.
필요한 경우 관리자에게 권한을 요청해 주세요.
- 프론트에서 버튼을 숨기는 것은 UX입니다.
- 실제 보안은 백엔드 권한 검사로 처리해야 합니다.
✅ 17. Not Found 상태
- 상세 페이지에서 데이터가 없을 때는 Not Found 상태가 필요합니다.
- 예를 들어 삭제된 상품 상세, 존재하지 않는 상담 ID, 잘못된 주문 ID 등이 있습니다.
➕ 17-1. Not Found 메시지
요청한 데이터를 찾을 수 없습니다.
삭제되었거나 접근할 수 없는 데이터일 수 있습니다.
➕ 17-2. 버튼 제공
목록으로 돌아가기
이전 페이지로 이동
새로고침
- 사용자가 막다른 길에 갇히지 않게 다음 행동을 제공해야 합니다.
✅ 18. 관리자 테이블 상태 설계
- 관리자 테이블은 로딩, 빈 상태, 에러, 선택 상태, 처리 중 상태가 모두 필요합니다.
➕ 18-1. 상담 테이블 상태
Loading:
스켈레톤 row 표시
Empty:
조건에 맞는 상담 신청 없음
Error:
상담 목록 불러오기 실패 + 다시 시도 버튼
Fetching:
기존 목록 위에 작은 새로고침 표시
Row Pending:
특정 row 상태 변경 중 버튼 disabled
Selected:
선택된 row 개수 표시
➕ 18-2. 테이블 Empty 예시
if (!consultsQuery.data?.items.length) {
return (
<EmptyState
title="조건에 맞는 상담 신청이 없습니다"
description="검색어, 상태, 유입경로 또는 날짜 조건을 변경해 보세요."
/>
);
}
- 테이블은 그냥 빈 row만 보여주면 안 됩니다.
- 운영자가 왜 비어 있는지 이해할 수 있어야 합니다.
✅ 19. EmptyState 컴포넌트
- Empty 상태는 여러 화면에서 반복되므로 공통 컴포넌트로 만들면 좋습니다.
type EmptyStateProps = {
title: string;
description?: string;
action?: React.ReactNode;
};
function EmptyState({
title,
description,
action,
}: EmptyStateProps) {
return (
<div className="empty-state">
<strong>{title}</strong>
{description && <p>{description}</p>}
{action && <div>{action}</div>}
</div>
);
}
➕ 19-1. 사용 예시
<EmptyState
title="등록된 상품이 없습니다"
description="새 상품을 등록해 고객 화면에 노출해 보세요."
action={<button>상품 등록</button>}
/>
- 초기 데이터 없음 상태에서는 action 버튼을 제공하면 좋습니다.
- 검색 결과 없음 상태에서는 필터 초기화 버튼을 제공할 수 있습니다.
✅ 20. 필터 초기화 UX
- 검색 결과가 없을 때는 필터 초기화 버튼이 유용합니다.
- 관리자 페이지에서는 필터가 많기 때문에 사용자가 어떤 조건 때문에 결과가 없는지 모를 수 있습니다.
➕ 20-1. 예시
<EmptyState
title="조건에 맞는 상담 신청이 없습니다"
description="검색어나 필터 조건을 변경해 보세요."
action={
<button type="button" onClick={resetFilters}>
필터 초기화
</button>
}
/>
➕ 20-2. 필터 초기화 기준
page → 1
keyword → 제거
status → 전체
source → 전체
startDate/endDate → 제거
sort → 기본값
- 필터 초기화는 URL query string도 함께 초기화해야 합니다.
✅ 21. 고객 화면 피드백
- 고객 화면은 관리자 화면보다 더 간결하고 안심되는 피드백이 필요합니다.
- 특히 상담 신청, 사전예약, 전화번호 입력 같은 흐름에서 중요합니다.
➕ 21-1. 상담 신청 로딩
상담 신청을 접수하는 중입니다...
잠시만 기다려 주세요.
➕ 21-2. 상담 신청 성공
상담 신청이 완료되었습니다.
담당자가 확인 후 순차적으로 연락드릴 예정입니다.
➕ 21-3. 상담 신청 실패
일시적인 문제로 신청을 접수하지 못했습니다.
잠시 후 다시 시도해 주세요.
➕ 21-4. 중복 신청
이미 신청된 전화번호입니다.
기존 신청 내역 확인이 필요하면 상담원에게 문의해 주세요.
- 고객용 메시지는 기술 용어를 피해야 합니다.
- “409 Conflict” 같은 표현은 절대 그대로 보여주면 안 됩니다.
✅ 22. 관리자 화면 피드백
- 관리자 화면은 고객 화면보다 조금 더 구체적인 안내가 필요합니다.
- 운영자가 문제를 파악하고 다음 행동을 할 수 있어야 하기 때문입니다.
➕ 22-1. Export 요청 성공
엑셀 파일 생성을 요청했습니다.
파일이 준비되면 다운로드 목록에서 확인할 수 있습니다.
➕ 22-2. Export 실패
엑셀 파일 생성에 실패했습니다.
검색 조건을 줄이거나 잠시 후 다시 시도해 주세요.
➕ 22-3. Webhook 재처리 요청
Webhook 재처리를 요청했습니다.
처리 결과는 실패 이력 목록에서 확인해 주세요.
- 관리자 메시지는 다음 확인 위치를 알려주면 좋습니다.
- “다운로드 목록에서 확인”, “실패 이력에서 확인”처럼 구체적이면 운영성이 좋아집니다.
✅ 23. 접근성 관점의 상태 표시
- 로딩, 에러, 성공 메시지는 시각적으로만 보여주면 안 됩니다.
- 접근성을 고려하면 role과 aria 속성을 적절히 사용할 수 있습니다.
➕ 23-1. 에러 메시지
<div role="alert">
상담 목록을 불러오지 못했습니다.
</div>
➕ 23-2. 로딩 상태
<div role="status" aria-live="polite">
데이터를 불러오는 중입니다.
</div>
➕ 23-3. 버튼 로딩
<button type="submit" disabled={isPending} aria-busy={isPending}>
{isPending ? '저장 중...' : '저장'}
</button>
- 접근성은 단순히 장애인을 위한 것만이 아니라, 더 명확한 UI를 만드는 기준입니다.
✅ 24. 실무 체크리스트
➕ 24-1. 로딩 상태 체크리스트
- 페이지 첫 진입 로딩 UI가 있는가?
- 목록 로딩에 Skeleton 또는 적절한 표시가 있는가?
- 버튼 클릭 후 pending 상태가 표시되는가?
- 제출 중 중복 클릭이 막히는가?
- 기존 데이터 refetch 중 화면이 과하게 깜빡이지 않는가?
- 파일 업로드나 Export처럼 오래 걸리는 작업에 안내가 있는가?
- 로딩이 실패했을 때 Error 상태로 전환되는가?
- 로딩이 너무 오래 걸릴 때 사용자가 상황을 이해할 수 있는가?
➕ 24-2. Empty 상태 체크리스트
- 데이터가 없을 때 빈 화면만 나오지 않는가?
- 검색 결과 없음과 초기 데이터 없음 메시지가 구분되는가?
- 필터 초기화 버튼이 필요한 곳에 있는가?
- 등록된 데이터가 없을 때 생성 버튼을 제공하는가?
- 실패 목록이 비어 있을 때 긍정적인 메시지를 보여주는가?
- 권한상 볼 데이터가 없는 경우 메시지가 적절한가?
- Empty가 Error처럼 보이지 않는가?
- 관리자 입장에서 다음 행동을 알 수 있는가?
➕ 24-3. Error 상태 체크리스트
- API 실패 시 에러 UI가 표시되는가?
- 에러 메시지가 사용자가 이해할 수 있는 표현인가?
- 401, 403, 404, 409, 500 처리가 구분되는가?
- 재시도 버튼이 필요한 곳에 있는가?
- 서버 에러를 그대로 노출하지 않는가?
- 고객 화면과 관리자 화면의 메시지 톤이 구분되는가?
- 에러 발생 후 버튼이 다시 활성화되는가?
- 권한 없음과 데이터 없음이 구분되는가?
➕ 24-4. 피드백 UX 체크리스트
- 저장 성공 시 Toast 또는 메시지가 표시되는가?
- 삭제/상태 변경처럼 중요한 작업에는 Confirm이 있는가?
- Confirm 메시지가 작업 결과와 영향을 설명하는가?
- 고객용 메시지가 기술 용어 없이 친절한가?
- 관리자용 메시지가 다음 확인 위치를 알려주는가?
- Toast만으로 처리하면 안 되는 중요한 정보를 Toast로만 보여주지 않는가?
- 접근성을 고려한 role/aria 속성이 있는가?
- 같은 상황에 화면마다 다른 메시지를 쓰고 있지 않은가?
✅ 25. AI를 활용해 화면 상태 UX를 설계할 때 질문법
- AI에게 로딩/에러/빈 상태를 설계시킬 때는 화면 종류, API 상태, 사용자 역할, 가능한 에러 코드, 후속 행동을 함께 알려줘야 합니다.
➕ 25-1. 좋은 질문 예시
React + TanStack Query로 관리자 상담 목록 화면의 로딩/에러/빈 상태 UX를 설계하고 싶어.
상황:
1. 상담 목록은 API로 조회함
2. 검색 조건은 page, status, source, keyword, startDate, endDate가 있음
3. 검색 조건은 URL query string으로 관리함
4. 최초 로딩, refetch, empty, error 상태가 필요함
5. 검색 결과가 없으면 필터 초기화 버튼을 보여주고 싶음
6. API 에러는 401, 403, 500이 발생할 수 있음
7. 상태 변경 mutation 성공 후 목록을 갱신함
8. 관리자 화면이라 메시지는 간결하지만 다음 행동이 보여야 함
요청:
- Loading UI
- isLoading과 isFetching 처리
- EmptyState 메시지
- 필터 초기화 UX
- ErrorState와 retry 버튼
- 401/403/500 에러별 처리
- Toast 메시지 기준
- 테이블 row pending 처리
- 접근성 체크리스트
를 실무 기준으로 정리해줘.
➕ 25-2. AI 답변 검증 기준
- Loading, Empty, Error를 구분해서 설계하는가?
- 최초 로딩과 refetch 로딩을 구분하는가?
- 검색 결과 없음과 초기 데이터 없음 메시지를 다르게 제안하는가?
- 재시도 버튼과 필터 초기화 버튼을 적절히 제안하는가?
- 고객용 메시지와 관리자용 메시지 톤을 구분하는가?
- 401/403/500 에러 처리를 구분하는가?
- 서버 에러 원문을 그대로 사용자에게 보여주지 않는가?
- 접근성 속성까지 고려하는가?
📌 요약
- 화면 상태는 사용자가 현재 화면에서 어떤 상황을 보고 있는지를 의미하며, 로딩, 성공, 빈 상태, 에러 상태를 반드시 구분해야 합니다.
- 로딩 UI가 없으면 사용자는 화면이 멈췄다고 느끼고 버튼을 반복 클릭할 수 있습니다.
- 목록/상세 화면은 Skeleton, 버튼 작업은 Spinner나 “처리 중...” 텍스트가 적합합니다.
- TanStack Query에서는 최초 로딩과 재조회 상태를 구분해
isLoading, isFetching, isError, data를 상황에 맞게 사용해야 합니다.
- Empty 상태는 에러가 아니며, 데이터가 없는 이유와 사용자가 다음에 할 수 있는 행동을 알려줘야 합니다.
- 검색 결과 없음, 초기 데이터 없음, 실패 목록 없음, 권한상 볼 데이터 없음은 서로 다른 메시지를 써야 합니다.
- Error 상태에서는 기술적인 서버 에러 원문을 그대로 노출하지 말고, 사용자가 이해할 수 있는 메시지와 재시도 방법을 제공해야 합니다.
- 401, 403, 404, 409, 500 같은 에러는 UI 처리 방식이 다르므로 공통 에러 매핑을 만들어두는 것이 좋습니다.
- Toast는 짧은 성공/실패 피드백에 적합하고, 중요한 경고나 삭제 확인은 Modal 또는 Alert로 처리해야 합니다.
- 관리자 화면은 다음 확인 위치와 조치 방법을 알려주는 메시지가 중요하고, 고객 화면은 기술 용어 없이 안심되는 메시지가 중요합니다.