#DESIGN#FRONTEND#ESSAY

Vite 8에서 라이브러리 에러가 발생한 이유

작성일
2026.06.24T09:00:00Z
수정일
2026.06.28T14:30:00Z
Vite 8에서 라이브러리 에러가 발생한 이유 대표 이미지
대표 이미지Post / 1

분명 전 버전에서는 잘 작동한 라이브러리가 왜 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. 내가 선택한 방식

이번에는 기존 라이브러리를 계속 유지하기보다 대체 라이브러리를 사용하는 방식을 선택했다.

  1. wrapper 방식은 문제가 발생하는 import마다 별도 처리가 필요하다.
  2. alias 방식은 특정 라이브러리의 내부 빌드 경로에 의존한다.
  3. legacy 옵션은 프로젝트 전체의 CommonJS interop 동작에 영향을 준다.
  4. Lottie 사용 범위가 크지 않아 라이브러리 교체 비용이 상대적으로 작았다.

6. 정리

이번 문제의 핵심은 default import 결과가 실제 컴포넌트 함수가 아니라 모듈 객체로 들어올 수 있다는 점이다.

// 기대
Lottie === Component;

// 실제 문제 상황
Lottie === { default: Component };

해결 방법은 상황에 따라 다르다.

  • 빠른 우회가 필요하면 wrapper 사용
  • 특정 라이브러리만 문제라면 Vite alias 사용
  • 기존 CJS 동작을 임시 복원해야 한다면 legacy 옵션 사용
  • 마이그레이션 비용이 낮다면 대체 라이브러리 사용

참고 링크