React + GraphQL: Apollo Client 최적화
React와 GraphQL 환경에서 Apollo Client를 사용해 데이터 페칭 성능을 개선하는 핵심 개념, 캐시 전략, 쿼리 설계 및 모니터링 방법을 정리한 기술문서
목차
개요
React 앱에서 GraphQL을 도입하면 데이터 흐름이 간결해진다. 다만 빈번한 네트워크 호출과 불필요한 렌더링은 사용자 경험을 저해한다. Apollo Client는 강력한 캐싱과 다양한 페칭 정책을 제공해 성능을 개선할 수 있다. 이 글은 초보자도 이해하기 쉬운 방식으로 캐시 개념부터 실전 적용 전략까지 설명한다.
핵심 개념
InMemoryCache의 역할
Apollo의 InMemoryCache는 클라이언트 측에서 데이터를 정규화하고 재사용한다. 동일한 엔티티를 한 번만 저장해 중복 요청을 줄인다. 캐시는 네트워크 요청을 완전히 대체하지는 않지만, 적절히 설정하면 네트워크 부담을 크게 낮출 수 있다.
fetchPolicy와 네트워크 정책
fetchPolicy는 클라이언트가 캐시와 네트워크를 어떻게 조합할지 결정한다. 주요 옵션은 다음과 같다.
- cache-first: 캐시를 먼저 확인하고 없으면 네트워크 요청
- network-only: 항상 네트워크에서 가져옴
- cache-and-network: 캐시를 즉시 사용하고 백그라운드에서 네트워크 요청
프로덕션에서는 화면 특성에 따라 정책을 혼합해 사용한다. 예를 들어 초기 리스트는 cache-and-network, 상세 조회는 cache-first로 구성할 수 있다.
기본 코드 예시
아래는 useQuery를 사용할 때 fetchPolicy를 지정하는 예시다.
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
const GET_POSTS = gql`query GetPosts { posts { id title content } }`
export default function PostList() {
const { data, loading, error } = useQuery(GET_POSTS, {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-first'
})
if (loading && !data) return <div>로딩 중</div>
if (error) return <div>에러 발생</div>
return (
<ul>
{data.posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
)
}
적용 전략
1. 캐시 구조 설계
엔티티의 id와 __typename을 기반으로 정규화한다. 이렇게 하면 관계를 가진 데이터 업데이트가 쉬워진다. 필요한 경우 typePolicies로 키 필드를 지정해 충돌을 줄인다.
2. 쿼리 분할과 지연 로딩
한 번에 많은 데이터를 요청하면 초기 렌더링이 지연된다. 목록과 상세를 분리해 목록은 요약 정보만 요청하고 사용자가 상세를 열 때 추가 쿼리를 실행한다. 이를 통해 초기 응답 시간을 줄인다.
3. 낙관적 업데이트(Optimistic UI)와 캐시 수정
사용자 인터랙션으로 즉각적인 반응이 필요한 경우 낙관적 응답을 활용한다. 성공 응답이 돌아오기 전이라도 UI를 업데이트하면 체감 성능이 좋아진다. 서버 응답이 오면 캐시를 최종 상태로 조정한다.
mutation AddTodo($text: String!) {
addTodo(text: $text) { id text completed }
}
// useMutation 사용 예시
const [addTodo] = useMutation(ADD_TODO_MUTATION, {
optimisticResponse: { addTodo: { id: 'temp-id', text: '새 할 일', completed: false, __typename: 'Todo' } },
update(cache, { data: { addTodo } }) {
const existing = cache.readQuery({ query: GET_TODOS })
cache.writeQuery({
query: GET_TODOS,
data: { todos: [addTodo, ...existing.todos] }
})
}
})
성능 측정과 모니터링
메트릭 선정
페칭 최적화의 효과를 확인하려면 다음 메트릭을 관찰한다.
- 네트워크 요청 수
- 페이지별 평균 응답 시간
- 첫 의미 있는 페인트(FMP)와 상호작용까지 걸리는 시간
개발 환경에서는 Apollo Client의 DevTools를 사용해 캐시 히트율과 쿼리 동작을 확인한다. 운영 환경에서는 RUM(실사용자 모니터링)을 통해 실제 사용자 지표와 비교한다.
권장 패턴
- 중복 쿼리는 하나로 합치고 fragment를 활용해 재사용성 확보
- fetchPolicy를 화면 목적에 맞춰 세분화
- 정규화된 캐시와 typePolicies로 데이터 일관성 유지
- 낙관적 업데이트로 사용자 체감 성능 개선
요약
Apollo Client는 잘 설계된 캐시와 페칭 정책으로 React 앱의 데이터 페칭 성능을 크게 개선한다. 먼저 캐시 구조와 fetchPolicy를 이해하고, 쿼리 분할과 낙관적 업데이트 같은 전략을 적용한다. 마지막으로 성능 지표를 측정해 개선 효과를 확인하면 안정적인 사용자 경험을 제공할 수 있다.