eatthefrog

Apollo Client v4 업그레이드: Shim 파일과 Client Preset으로 아키텍처 현대화하기 본문

시행착오 노트

Apollo Client v4 업그레이드: Shim 파일과 Client Preset으로 아키텍처 현대화하기

eater_forg 2026. 1. 2. 16:55

최근 프로젝트에서 Apollo Client를 v4로 업그레이드하며 발생한 Namespace '...' has no exported member 에러와, 이를 해결하기 위해 아키텍처를 현대화한 과정을 공유합니다.

 

1. 문제의 본질: Apollo v4의 구조 변화와 "세대 차이"

가장 먼저 마주한 문제는 기존의 **레거시 코드 생성 방식(Legacy Codegen)**이 Apollo Client v4의 변화된 타입 시스템을 인지하지 못한다는 점이었습니다.

  • Internalization (타입의 내재화): Apollo v4는 패키지 경량화를 위해 기존에 최상위(Root)에서 Export하던 MutationFunction, QueryResult 같은 타입들을 useMutation.Options, useQuery.Data와 같이 특정 네임스페이스 내부로 이동시켰습니다.
  • 플러그인의 한계: 기존에 널리 사용되던 typescript-react-apollo 플러그인은 여전히 타입을 최상위 경로(@apollo/client)에서만 찾으려 시도했고, 결과적으로 대량의 컴파일 에러가 발생했습니다.

 

2. 해결 전략의 진화: 임시 방편(Shim)에서 표준(Preset)으로

문제를 해결하기 위해 두 단계의 의사결정 과정을 거쳤습니다.

전략 방식 특징
Step 1: Shim(심) 파일 도입 부족한 타입을 별도 파일에 재정의하여 연결 빠른 에러 해결이 가능하지만, 별도의 유지보수 비용 발생
Step 2: Client Preset 전환 Apollo v4 환경에 최적화된 최신 Codegen 방식 도입 (최종 선택) 아키텍처 수준의 해결이며 향후 확장성 확보

초기에는 빠른 복구를 위해 Shim 파일을 만들어 타입을 수동으로 매핑해 주었습니다. 하지만 이는 근본적인 해결책이 아니라고 판단했고, Apollo v4 시대의 표준인 Client Preset으로 아키텍처를 완전히 전환하기로 결정했습니다.


3. 기술적 핵심: "Typed Document Nodes"

Client Preset 도입으로 얻은 가장 큰 기술적 이득은 **자동 타입 추론(Automatic Type Inference)**입니다.

  • 불필요한 제네릭 제거: 기존 방식에서는 client.mutate<LoginMutation, LoginMutationVariables>(...)처럼 개발자가 매번 제네릭 타입을 수동으로 선언해야 했습니다. 이는 코드의 가독성을 해치고 오타로 인한 런타임 에러 위험을 내포합니다.
  • 문서 중심의 타입 결합: 새로운 방식에서는 graphql() 함수가 쿼리문을 파싱하는 단계에서 입력값과 결과값의 타입을 문서 객체(Document Node)에 직접 내장시킵니다.
  • DX(Developer Experience) 혁신: 이제 IDE는 client.mutate({ mutation: LOGIN }) 코드만 보고도 어떤 데이터가 들어오고 나가는지 완벽하게 이해합니다. 개발자는 더 이상 타입을 찾아 헤맬 필요가 없습니다.

마치며: 에러 해결을 넘어 성장의 기회로

라이브러리의 내부 변화를 심도 있게 파악하고, 프로젝트의 기술 부채를 청산하여 최신 표준 아키텍처로 리팩토링하는 유의미한 과정이었습니다. 이제 우리 프로젝트의 GraphQL 레이어는 Apollo v4의 성능을 온전히 활용하면서도, 타입 안전성(Type Safety)과 개발 생산성을 동시에 확보하게 되었습니다.