타입스크립트

TypeScript Type instantiation is excessively deep 오류 해결: 제네릭 타입이 너무 깊어질 때

2026.05.11·수정 2026.05.12·약 7분

이 글에서 정리하는 내용

Type instantiation is excessively deep 오류는 TypeScript가 제네릭 타입을 끝까지 펼치다가 한계를 만났다는 신호입니다. 재귀 조건부 타입, 깊은 infer, 라이브러리 타입 조합, 과한 as const 추론을 줄여 타입 계산 범위를 작게 만들어야 합니다.

내 증상이 이거면 여기부터 보세요

TypeScript Type instantiation is excessively deep 오류 해결 원인 진단 흐름

코드는 실행되지도 않았는데 타입 검사에서만 멈춘다면 런타임 버그가 아니라 타입 계산량 문제일 수 있습니다. 특히 유틸 타입을 여러 겹 합치거나 라이브러리 제네릭을 그대로 전파할 때 자주 보입니다.

증상실제 에러 메시지먼저 볼 위치바로 해볼 조치이동할 섹션
재귀 유틸 타입에서 실패TS2589조건부 타입재귀 깊이 제한 또는 중간 타입 분리핵심 수정 코드
라이브러리 제네릭 조합 후 실패excessively deepAPI 응답/폼 타입필요한 DTO 타입으로 좁히기왜 생기는가
as const 객체에서 타입 폭발union type too complex상수 객체 추론satisfies와 명시 타입 사용핵심 수정 코드
빌드만 느려짐tsc hangs타입 검사 범위문제 파일 isolate예외 케이스

오류 해결 글에서는 실제 에러 문구가 본문에 있어야 검색해서 다시 찾기 쉽습니다. 프로젝트명, 사용자명, 토큰, 절대 경로처럼 민감하거나 불필요한 값은 빼고 대표 문구만 남깁니다.

TS2589: Type instantiation is excessively deep and possibly infinite.
Type instantiation is excessively deep and possibly infinite.
Expression produces a union type that is too complex to represent.

먼저 적용할 핵심 수정 코드

원인 설명을 오래 읽기 전에 아래 설정부터 현재 코드와 대조해보세요. 먼저 깊은 타입을 중간 타입으로 끊고, 외부 라이브러리의 복잡한 제네릭은 필요한 형태로 좁혀서 받습니다. 중요한 것은 오류를 덮는 옵션을 추가하는 것이 아니라, 실행 환경과 설정 파일이 같은 기준으로 동작하게 만드는 것입니다.

깊은 타입을 중간 타입으로 끊기

type ApiUser = { id: string; profile: { name: string } }

type UserView = {
  id: ApiUser["id"]
  name: ApiUser["profile"]["name"]
}

function renderUser(user: UserView) {
  return user.name
}

외부 타입 전체를 계속 전파하지 말고 화면에서 필요한 얕은 타입으로 끊어줍니다.

satisfies로 값 검증과 타입 폭 줄이기

type RouteMap = Record<string, { label: string; path: string }>

const routes = {
  home: { label: "Home", path: "/" },
  blog: { label: "Blog", path: "/blog" },
} satisfies RouteMap

값은 검증하되 너무 복잡한 리터럴 타입이 프로젝트 전체로 퍼지지 않게 조절합니다.

왜 이런 오류가 생기는가

이 오류는 TypeScript가 타입을 잘못 이해했다는 뜻보다, 타입을 계산하는 과정이 너무 깊어졌다는 뜻에 가깝습니다. 조건부 타입이 다시 조건부 타입을 만들고, 그 안에서 infer가 반복되면 컴파일러가 멈춥니다.

라이브러리 타입을 그대로 페이지, 폼, API, 컴포넌트 props까지 넘기면 타입이 계속 커집니다. 화면에서 필요한 필드만 뽑은 DTO나 ViewModel 타입을 두면 계산 범위가 작아집니다.

as const는 유용하지만 큰 설정 객체에 무조건 붙이면 모든 문자열과 중첩 구조가 리터럴 타입으로 고정됩니다. 필요한 곳에만 쓰고, 넓은 구조는 명시 타입이나 satisfies로 정리하는 편이 낫습니다.

실제 작업에서 점검하는 순서

첫 번째로 오류가 나온 단계를 나눕니다. 개발 서버에서만 보이는지, 빌드에서 실패하는지, 배포나 CI에서만 실패하는지에 따라 봐야 할 파일이 달라집니다. 같은 메시지라도 실행 위치가 다르면 원인도 다를 수 있습니다.

두 번째로 한 번에 여러 설정을 바꾸지 않습니다. Type instantiation is excessively deep를 해결하다 보면 관련 파일을 전부 고치고 싶어지지만, 그러면 어떤 변경이 실제 해결책이었는지 알기 어렵습니다. 핵심 설정 하나를 바꾸고 검증 명령을 실행한 뒤 다음 설정으로 넘어가야 합니다.

세 번째로 저장소에 남는 기준으로 정리합니다. 개인 PC에서만 통과하는 임시 조치가 아니라 설정 파일, 패키지 버전, 배포 설정처럼 팀원이 같은 기준으로 재현할 수 있는 형태가 되어야 합니다.

그래도 안 될 때 볼 예외 케이스

기본 수정 후에도 같은 메시지가 남는다면 캐시, 버전 차이, 경로 대소문자, 실행 위치를 같이 봐야 합니다. 오류 메시지는 하나처럼 보여도 실제로는 개발 서버, 타입 검사, 번들러, 배포 환경이 서로 다른 설정을 읽어서 생기는 경우가 많습니다.

  • 라이브러리 버그라면 최신 패치에서 타입 정의가 완화됐는지 확인합니다.
  • skipLibCheck은 임시 우회가 될 수 있지만 프로젝트 타입 폭발 원인을 숨길 수 있습니다.
  • Zod, React Hook Form, tRPC처럼 타입 추론이 강한 도구는 경계 타입을 따로 두는 것이 좋습니다.

다음에 같은 문제를 줄이는 체크리스트

TypeScript Type instantiation is excessively deep 오류 해결 해결 단계 체크리스트

깊은 타입 오류는 타입을 더 똑똑하게 만드는 방향보다 타입 경계를 작게 나누는 방향으로 해결되는 경우가 많습니다. 타입 추론이 필요한 부분과 명시 타입이 필요한 부분을 분리하면 검사 속도와 오류 메시지가 같이 좋아집니다.

  • 재귀 조건부 타입에 종료 조건이 있는지 확인합니다.
  • 라이브러리 제네릭을 화면 props까지 그대로 넘기지 않습니다.
  • 큰 객체의 as const 사용 범위를 줄입니다.
  • 문제 파일을 좁힌 뒤 tsc –noEmit으로 검증합니다.

결국 Type instantiation is excessively deep는 한 줄짜리 우회 코드보다 확인 순서가 중요합니다. 에러 문구를 단계별로 나누고, 설정 파일과 실행 명령을 같은 기준으로 맞추면 같은 문제를 훨씬 짧게 끝낼 수 있습니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기