Vite 8에서 라이브러리 에러가 발생한 이유
- 작성일
- 2026.06.24T09:00:00Z
- 수정일
- 2026.06.28T14:30:00Z
분명 전 버전에서는 잘 작동한 라이브러리가 왜 Vite 8에서 에러가 났는가...
1. 문제 상황
Vite 8 환경으로 프로젝트를 만든 뒤 기존에 사용하던 Lottie 렌더링 라이브러리 쪽에서 문제가 발생했다.
처음에는 기존처럼 lottie-react를 사용하고 있었다.
import Lottie from 'lottie-react';
<Lottie animationData={animationData} loop autoplay />
그런데 Vite 8 환경에서 실행해보니 런타임에서 Lottie 컴포넌트가 정상적인 React 컴포넌트(function)가 아니라 object처럼 들어오는 문제가 발생했다.

에러 메시지는 아래와 같았다.
Error: Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
React 컴포넌트로 렌더링되어야 하는 Lottie가 실제로는 컴포넌트 함수가 아니라 객체로 들어오면서 문제가 발생한다는 내용이다.
2. 원인 파악
문제의 핵심은 Vite 8에서 CommonJS 기반 패키지를 ESM 방식으로 import할 때 default import 처리 방식이 기존과 달라질 수 있다는 것이다.
관련 자료:
CommonJS 모듈을 ESM처럼 import할 때 아래 코드는 상황에 따라 다르게 해석될 수 있다.
import Foo from './importee.cjs';
// module.exports 전체를 가져오는 경우
Foo === module.exports;
// module.exports.default를 가져오는 경우
Foo === module.exports.default;
Rolldown 문서에서는 이를 Ambiguous default import from CJS modules 문제로 설명한다.
3. 왜 React 에러가 발생했는가?
React에서 JSX 컴포넌트로 렌더링하려면 해당 값은 문자열, 함수 컴포넌트 또는 클래스 컴포넌트여야 한다.
<Lottie />
하지만 실제 import 결과가 다음과 같은 객체라면 문제가 된다.
{
default: Component
}
React는 컴포넌트 함수가 아니라 객체를 받았기 때문에 런타임 오류를 발생시킨다.
4. 해결 방법
1) wrapper로 default 직접 꺼내기
import한 값이 객체인 경우 직접 default를 꺼내는 wrapper를 만들 수 있다.
import rawLottie from 'lottie-react';
const Lottie =
typeof rawLottie === 'object' && rawLottie !== null && 'default' in rawLottie
? rawLottie.default
: rawLottie;
export default Lottie;
2) Vite alias로 ESM 빌드 직접 지정하기
import { defineConfig } from 'vite';
export default defineConfig({
resolve: {
alias: {
'lottie-react': 'lottie-react/build/index.es.js',
},
},
});
이 방식은 특정 라이브러리의 내부 빌드 파일 경로에 의존한다는 단점이 있다.
3) Vite legacy 옵션 사용하기
export default defineConfig({
legacy: {
inconsistentCjsInterop: true,
},
});
프로젝트 전체의 CommonJS interop 동작에 영향을 줄 수 있으므로 주의해야 한다.
4) 대체 라이브러리 사용
사용 범위가 작고 마이그레이션 비용이 낮다면 ESM 환경에서 안정적으로 동작하는 라이브러리로 교체할 수 있다.
5. 내가 선택한 방식
이번에는 기존 라이브러리를 계속 유지하기보다 대체 라이브러리를 사용하는 방식을 선택했다.
- wrapper 방식은 문제가 발생하는 import마다 별도 처리가 필요하다.
- alias 방식은 특정 라이브러리의 내부 빌드 경로에 의존한다.
- legacy 옵션은 프로젝트 전체의 CommonJS interop 동작에 영향을 준다.
- Lottie 사용 범위가 크지 않아 라이브러리 교체 비용이 상대적으로 작았다.
6. 정리
이번 문제의 핵심은 default import 결과가 실제 컴포넌트 함수가 아니라 모듈 객체로 들어올 수 있다는 점이다.
// 기대
Lottie === Component;
// 실제 문제 상황
Lottie === { default: Component };
해결 방법은 상황에 따라 다르다.
- 빠른 우회가 필요하면 wrapper 사용
- 특정 라이브러리만 문제라면 Vite alias 사용
- 기존 CJS 동작을 임시 복원해야 한다면 legacy 옵션 사용
- 마이그레이션 비용이 낮다면 대체 라이브러리 사용