[FrontEnd] Optimistic Update란?

TNFUDS·2026년 4월 17일

FinTrack 프로젝트

목록 보기
13/14

개요

프론트엔드에서 UX를 크게 개선하는 패턴 중 하나가 Optimistic Update다.
이게 실제로 적용되어 있는지, 어디에 있는지, 체감 성능 차이는 어떤지까지 정리해봤다.


Optimistic Update란?

서버 응답을 기다리지 않고, 성공할 것이라 가정하고 UI를 먼저 업데이트하는 방식

  • 사용자 액션 → 즉시 UI 반영
  • 이후 서버 요청
  • 성공 → 그대로 유지
  • 실패 → 롤백 처리

핵심은 “사용자가 기다리지 않게 만든다”

사진 출처: https://kiwee.eu/blog/what-is-the-optimistic-ui/

해당 이미지처럼 Optimistic Updates를 구현함으로써 유저는 자신의 액션에 따른 즉각적인 피드백을 받을 수 있고, 향상된 UX를 경험할 수 있다.

내 코드에서 적용된 부분

현재 DashboardPage.tsx에서 총 3군데 적용되어 있다.

1. 지출 등록 시 — 목록 즉시 반영

// optimistic: 서버 응답 없이 즉시 목록 앞에 추가
setRecentExpenses((prev) => [createdExpense, ...prev]);

// 서버 재동기화
await fetchDashboardData();
await fetchRecent();

서버 응답을 기다리지 않고 바로 UI에 추가됨


2. 지출 등록 시 — 월 총액 즉시 증가

if (isThisMonth) {
  setMonthlyTotal((prev) => prev + createdExpense.amount);
}

SummarySection의 예산 프로그레스 바가 즉시 반응


3. 지출 삭제 시 — 즉시 제거 + 금액 보정

이전 버전

// optimistic
setRecentExpenses(prev => prev.filter(e => e.id !== id));
setMonthlyTotal(prev => prev - target.amount);

// 서버 요청
await deleteExpense(id);
fetchDashboardData();
fetchRecent();

삭제 버튼 누르면 바로 사라짐 (지연 없음)

개선한 버전

const handleDelete = async (id: number) => {
  const target = recentExpenses.find(e => e.id === id);

  // 롤백용 스냅샷
  const previousExpenses = recentExpenses;
  const previousTotal = monthlyTotal;

  // ① optimistic: UI 즉시 반영
  setRecentExpenses(prev => prev.filter(e => e.id !== id));

  if (target?.expenseAt) {
    const expenseDate = new Date(target.expenseAt);
    const now = new Date();

    const isThisMonth =
      expenseDate.getFullYear() === now.getFullYear() &&
      expenseDate.getMonth() === now.getMonth();

    if (isThisMonth) {
      setMonthlyTotal(prev => prev - target.amount);
    }
  }

  try {
    // ② 서버 요청
    await deleteExpense(id);

    // ③ 서버 재동기화
    await fetchDashboardData();
    await fetchRecent();
  } catch {
    // ④ rollback
    setRecentExpenses(previousExpenses);
    setMonthlyTotal(previousTotal);
  }
};

기존에는 optimistic update만 적용되어 있었지만,
현재는 서버 요청 실패 시 이전 상태로 되돌리는 rollback까지 포함된 구조로 개선했다.

왜 rollback이 중요한가?

  • 네트워크 오류
  • 서버 에러
  • 인증 만료

이런 상황에서 rollback 없으면 UI와 서버 데이터 불일치 발생한다.
특히 삭제/수정 같은 작업에서는 사용자가 “이미 처리된 줄 알고” 잘못된 행동을 이어갈 수 있다.


Optimistic Update vs 일반 방식

상황Optimistic Update 있음없음
등록 후 목록 반영즉시 (~0ms)서버 응답 후 (~300~800ms)
삭제 반영즉시서버 응답 후
프로그레스 바즉시 변화지연 후 변화

차이는 단순한 속도가 아니라 “사용자가 느끼는 반응성” 자체가 달라진다


직접 체감하는 방법

크롬 DevTools → Network 탭에서 테스트 가능

방법

  1. Network → Slow 3G 또는 latency 1000ms 설정
  2. 지출 등록 버튼 클릭

결과 비교

✅ Optimistic 있는 경우 (현재 코드)

  • 클릭 → 즉시 UI 반영
  • 1초 뒤 → 서버 데이터로 자연스럽게 동기화

❌ Optimistic 없는 경우

  • 클릭 → 1~2초 아무 변화 없음
  • 이후 → 갑자기 UI 반영

이 차이는 생각보다 크다 → “버튼이 안 눌린 줄 착각”하는 UX 문제까지 발생


현재 코드의 한계

작성한 모든 코드 영역에 적용된 건 아니다.

Optimistic Update 미적용 영역

  • 소비 패턴 차트 (weekdayStats, categoryStats)
  • 스마트 인사이트 (feedback)

이유: 서버에서 집계/분석 로직이 필요한 데이터
결과: 등록 후에도 차트는 서버 응답 이후에만 갱신됨

장점 정리

  1. 체감 속도 개선 (가장 중요)
    실제 네트워크 속도와 무관하게 즉각 반응

  2. UX 향상
    “앱이 빠르다”는 인상을 줌

  3. 사용자 이탈 감소
    로딩 대기 스트레스 제거


단점 (현실적인 포인트)

  1. 실패 시 롤백 처리 필요

  2. 서버 데이터와 불일치 가능성

  3. 복잡한 로직에서는 적용 어려움

그래서 보통 CRUD (등록/삭제)에는 적극 사용하지만
집계/분석 데이터에는 잘 쓰지 않는다 (클라이언트에서 정확한 계산을 재현하기 어렵기 때문).

정리

Optimistic Update는 실제 속도를 빠르게 만드는 게 아니라
사용자가 느끼는 속도를 0ms로 만드는 기술

profile
내 세상을 넓혀가는 중

0개의 댓글