서버 상태(Server State)와 클라이언트 상태(Client State)의 차이를 설명해주세요.
답변 포인트
서버가 원본 데이터를 가지고 있는지, 캐싱과 재검증이 필요한지 기준으로 구분해보세요.
정답 및 해설
빠른 요약
클라이언트 상태는 모달 열림 여부, 선택된 탭, 폼 입력값처럼 브라우저 안에서만 의미 있는 UI 상태입니다. 서버 상태는 사용자 정보, 게시글 목록, 장바구니처럼 원본이 서버에 있고 클라이언트가 복사본을 캐시하는 상태입니다.
프론트엔드 상태는 크게 클라이언트 상태와 서버 상태로 나눌 수 있습니다. 둘을 구분하면 React state, Context, Redux, Zustand, TanStack Query 같은 도구를 언제 써야 하는지 판단하기 쉬워집니다.
클라이언트 상태
클라이언트 상태는 브라우저 안에서만 의미가 있고, 서버가 정답을 가지고 있지 않은 상태입니다.
예:
- 모달 열림 여부
- 현재 선택된 탭
- 다크 모드 여부
- 폼 입력 중 임시 값
- 드롭다운 열림 여부
const [isOpen, setIsOpen] = useState(false);
const [selectedTab, setSelectedTab] = useState("profile");이런 상태는 보통 useState, useReducer, Context, Zustand 등으로 관리합니다.
서버 상태
서버 상태는 원본 데이터가 서버에 있고, 클라이언트는 그 복사본을 캐시해서 사용하는 상태입니다.
예:
- 사용자 프로필
- 게시글 목록
- 장바구니 데이터
- 알림 목록
- 권한 정보
서버 상태는 단순히 값을 저장하는 것보다 더 많은 문제가 따라옵니다.
- 로딩 상태
- 에러 상태
- 캐싱
- 재요청
- 백그라운드 갱신
- 중복 요청 제거
- 낙관적 업데이트
- 페이지네이션
직접 useEffect로 처리할 때의 한계
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setIsLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser)
.catch(setError)
.finally(() => setIsLoading(false));
}, [userId]);
// 캐싱, 중복 요청 제거, stale 처리 등을 직접 구현해야 함
}간단한 화면에서는 괜찮지만, 앱이 커질수록 반복 코드와 상태 불일치가 늘어납니다.
TanStack Query 같은 도구의 역할
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = useQuery({
queryKey: ["user", userId],
queryFn: () => fetchUser(userId),
});
if (isLoading) return <p>로딩 중</p>;
if (error) return <p>오류 발생</p>;
return <p>{data.name}</p>;
}서버 상태 라이브러리는 데이터를 "전역 상태"로 저장한다기보다, 서버 데이터 캐시를 관리합니다.
판단 기준
| 질문 | 적합한 상태 |
|---|---|
| 서버가 원본 데이터를 가지고 있나요? | 서버 상태 |
| 시간이 지나면 stale해질 수 있나요? | 서버 상태 |
| refetch, cache, retry가 필요한가요? | 서버 상태 |
| UI에서만 의미가 있나요? | 클라이언트 상태 |
| 페이지를 새로고침하면 사라져도 괜찮나요? | 클라이언트 상태 |
정리
클라이언트 상태는 UI의 현재 상태를 표현하고, 서버 상태는 원격 데이터의 캐시를 표현합니다. 서버 데이터를 Redux나 Context에 무조건 넣기보다, 서버 상태 관리 도구를 사용하면 로딩, 캐싱, 재검증 같은 문제를 일관되게 처리할 수 있습니다.
서버 상태와 클라이언트 상태의 본질적 차이
서버 상태는 원본이 서버에 있고 여러 사용자가 공유할 수 있는 비동기 데이터입니다. 클라이언트 상태는 현재 브라우저 UI를 표현하기 위한 로컬 데이터입니다.
// 서버 상태: 원본은 API/DB에 있음
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// 클라이언트 상태: 현재 화면의 UI 상태
const [isModalOpen, setModalOpen] = useState(false);서버 상태가 어려운 이유
- 로딩/에러/재시도 상태가 필요합니다.
- 캐시가 오래되었는지(stale) 판단해야 합니다.
- 같은 데이터를 여러 컴포넌트에서 공유합니다.
- mutation 이후 무효화나 optimistic update가 필요합니다.
선택 기준
| 상태 | 예시 | 주로 사용하는 도구 |
|---|---|---|
| 서버 상태 | 사용자 정보, 게시글 목록, 주문 내역 | TanStack Query, SWR, Apollo |
| 클라이언트 상태 | 모달 열림, 탭 선택, 입력 중 값 | useState, useReducer, Zustand |
| URL 상태 | 페이지 번호, 필터, 검색어 | query string, router |
서버 상태를 전역 store에 직접 넣으면 캐시 무효화, 중복 요청 제거, 재시도 같은 문제를 직접 해결해야 합니다. 그래서 서버 상태 전용 라이브러리를 쓰는 편이 유지보수에 유리합니다.