지금 아마 두 가지 상황 중 하나일 것입니다. 디자이너가 Lottie JSON 파일을 건네며 “이거 오늘 바로 앱에 넣을 수 있나요?”라고 묻거나, 이미 구현은 마쳤는데 개발 환경에선 잘 돌아가던 애니메이션이 실제 기기나 앱 시작 시간, 릴리스 빌드 단계에서 성능 부담으로 다가오는 상황 말이죠.
여기서 Lottie React Native의 진가가 드러납니다. 단순한 데모 구현은 쉽지만, 프로덕션 수준의 구현은 다릅니다. 결국 설치 방식, 재생 제어 방법, 그리고 애니메이션 파일을 단순한 에셋으로 볼 것인지 아니면 성능 예산의 일부로 관리할 것인지의 차이로 결정됩니다.
목차
- React Native 앱에서 Lottie가 필수적인 이유
- Lottie 개발 환경 설정하기
- 첫 Lottie 애니메이션 표시하기
- Lottie 애니메이션 컨트롤 마스터하기
- 프로덕션 앱을 위한 성능 최적화
- 자주 발생하는 Lottie 이슈 해결하기
React Native 앱에 Lottie가 필수적인 이유
React Native에서 정교한 제품 애니메이션을 직접 구현해보려 했다면 그 고통을 이미 알고 계실 겁니다. 미세한 모션 디테일이 타이밍 로직, 보간법(interpolation), 플랫폼별 특이사항의 늪으로 변하기 때문이죠. 결과물이 비슷해 보일 순 있어도, 그 '비슷함'은 디자이너가 의도한 결과물과는 거리가 먼 경우가 많습니다.
Lottie는 이 워크플로우를 완전히 바꿨습니다. 2016년 Airbnb가 Lottie를 오픈 소스로 공개하면서, 엔지니어가 프레임 단위로 애니메이션을 재구현하는 대신 디자이너가 직접 애니메이션을 전달할 수 있게 되어 모바일 애니메이션의 패러다임이 바뀌었습니다. 일부 엔터프라이즈 환경에서는 이러한 변화로 모바일 앱 개발 비용을 최대 40%, 에 따르면 Airbnb의 Lottie 개요.
디자인과 엔지니어링의 소모적인 논쟁을 끝내다
Lottie React Native의 핵심 이점은 단순히 "JSON으로 구현된 예쁜 애니메이션"이 아닙니다. 바로 관심사의 분리입니다. 디자이너는 After Effects에서 작업하고 Bodymovin으로 내보냅니다. 개발자는 모션을 커스텀 code로 변환하는 대신, 네이티브 기반 재생을 통해 결과물을 렌더링합니다.
애니메이션 작업은 영향 범위가 넓어지는 경향이 있기 때문에 이 점이 중요합니다. 단 하나의 축하 상태 애니메이션이 디자인 리뷰, 제품 리뷰, Android 동작, iOS 동작, 접근성, 그리고 앱 시작 성능에까지 영향을 줄 수 있습니다. Lottie는 이러한 영향 범위를 좁혀줍니다.
실무 원칙: 단순한 불투명도(opacity)나 이동(translate) 전환이 필요한 경우가 아니라, 애니메이션이 제품 경험의 핵심적인 부분일 때 Lottie를 사용하세요.
사용자 경험(UX) 측면도 고려해야 합니다. 모션은 피드백을 제공하고, 동작을 확인시켜 주며, 로딩 상태가 멈춰 있는 듯한 느낌을 줄여줍니다. 팀에서 인터페이스의 완성도, 리텐션, 또는 신뢰도를 진지하게 고민하고 있다면 애니메이션은 그 논의의 중요한 일부입니다. 더 넓은 범위의 앱 사용자 경험 논의는 결국 같은 결론에 도달합니다. 정적인 화면보다 빠른 피드백이 더 낫다는 것입니다.
Lottie가 가장 빛을 발하는 순간
Lottie React Native은 다음과 같은 경우에 가장 효과적입니다:
- 브랜드 마이크로 인터랙션 좋아요, 저장, 체크 표시, 결제 완료 상태 등
- 온보딩 일러스트레이션 비디오 파일을 쓰지 않고도 커스텀 느낌을 주고 싶을 때
- 로딩 및 빈 화면(empty states) 정적인 UI가 다소 미완성처럼 느껴지는 경우
- 기능 가이드(Feature education) GIF나 MP4를 임베드하지 않고 모션을 구현하고 싶을 때
하지만 모든 애니메이션 문제를 해결해 주지는 않습니다. 기본적인 화면 전환에는 React Native 자체 애니메이션 도구가 더 간단할 때가 많습니다. 매우 크거나 상호작용이 많은 모션 시스템의 경우, JSON 형식이 오히려 단점이 될 수도 있습니다. 이러한 트레이드오프는 실제 프로덕션 단계에서 더욱 중요해지는데, 대부분의 튜토리얼은 이 지점을 다루지 않고 너무 일찍 끝납니다.
Lottie 개발 환경 설정하기
설치 방법은 먼저 다음 결정에 따라 달라집니다: Expo managed workflow 또는 bare React Native. 두 방식의 개념을 혼동하지 마세요. 대부분의 설정 문제는 Expo 환경에서 bare workflow 가이드를 따르거나, Expo가 모든 native 세부 사항을 알아서 처리해 줄 것이라고 오해할 때 발생합니다.

설치 전에 워크플로우를 선택하세요
Expo 기반 앱에서 가장 빠르게 설정하고 싶다면, 커스텀 native 작업이 필요한 상황이 아닌 한 Expo 방식을 유지하세요. Bare 앱을 사용 중이거나 직접 제어가 필요한 native module에 이미 의존하고 있다면, 일반적인 native dependency로 설치한 후 즉시 iOS와 Android 빌드를 모두 검증해야 합니다.
많은 팀이 프로젝트 유형에 맞춰 설정을 일치시켰을 때 디버깅이 얼마나 쉬워지는지 과소평가하곤 합니다. 또한 많은 팀이 커스텀 native 통합 기능을 구축할 때 미리 비교해보는 이유이기도 합니다. Expo EAS의 대안으로 Capgo를 앱 수정이 어려워질 때까지 기다리지 말고 미리 검토하세요.
Expo managed 설정
Expo managed 앱의 경우, 설정을 최소화하세요.
-
패키지 설치
npx expo install lottie-react-native -
Metro 재시작
npx expo start -c -
기기 또는 시뮬레이터에서 확인하세요 먼저 로컬 JSON 파일로 아주 작은 애니메이션부터 렌더링해 보세요. 대용량 에셋과 새로운 설치 과정을 동시에 디버깅하지 마세요.
Expo 사용 시 주의할 실무 팁입니다:
- 로컬 파일을 우선 사용하세요: 라이브러리 작동 여부만 확인하려는 단계에서 원격 애니메이션을 디버깅하면 네트워크 노이즈가 발생할 수 있습니다.
- 릴리스 동작을 조기에 테스트하세요: 개발 모드에서는 타이밍 및 성능 관련 이슈가 숨겨질 수 있습니다.
- 에셋 경로를 확인하세요: JSON 파일 경로 오류는
아무것도 렌더링되지 않는 가장 흔한 원인 중 하나입니다.
Bare React Native 설정
Expo는
-
패키지 설치
npm install lottie-react-native -
iOS pods 설치
cd ios && pod install && cd .. -
앱 다시 빌드
npx react-native run-ios또는
npx react-native run-android
많은 퀵스타트 가이드에서 놓치는 부분입니다. 설치 후 무언가 잘못되었다고 판단하기 전에 반드시 전체 Native Build를 진행하세요. 앱에 제대로 컴파일되지 않은 네이티브 의존성은 Hot reload만으로는 해결할 수 없습니다.
시간을 아껴주는 Bare workflow 점검
다음 단계로 넘어가기 전, 이 체크리스트를 확인하세요:
| 확인 | 왜 중요한가 |
|---|---|
| 설치 후 다시 빌드 | 네이티브 모듈은 새로 컴파일해야 합니다 |
실행 pod install |
이것 없이는 iOS의 안정성을 보장할 수 없습니다 |
| 먼저 간단한 로컬 JSON을 사용하세요 | 패키지 설치 문제와 에셋 문제를 분리합니다 |
| 초기에 양쪽 플랫폼을 모두 테스트하세요 | Android와 iOS는 실패 원인이 다를 수 있습니다 |
패키지가 정상적으로 설치되었는데 첫 애니메이션이 보이지 않는다면, 대개 설치 문제가 아닙니다. 보통 에셋 경로, 컴포넌트 크기 또는 재생 설정 문제인 경우가 많습니다.
첫 Lottie 애니메이션 표시하기
처음 성공하는 애니메이션은 단순해야 합니다. 로컬 파일, 고정 크기, 자동 재생 활성화, 루프는 선택 사항으로 설정하세요. 조건부 재생, 원격 JSON, 또는 레이어가 복잡한 애니메이션 내보내기로 시작하지 마세요.

로컬 애니메이션 파일 추가
에셋 폴더가 없다면 새로 만드세요:
assets/
animations/
success.json
이름은 단순하게 유지하세요. 공백, 특수 문자를 피하고 폴더 계층을 너무 깊게 만들지 마세요. 그래야 require() 경로를 명확하게 유지하세요.
브랜딩을 위한 초기 로딩 화면이나 출시 후 핸드오프에 Lottie를 사용한다면, 시작 경로에 너무 큰 애니메이션을 넣기 전에 신중히 고려해야 합니다. 특히 다음 설정을 조정 중이라면 더욱 그렇습니다: React Native splash screen 동작.
LottieView로 렌더링하기
대형 화면 파일에 바로 넣지 말고, 전용 컴포넌트를 만드세요:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import LottieView from 'lottie-react-native';
export function SuccessAnimation() {
return (
<View style={styles.container}>
<LottieView
source={require('../assets/animations/success.json')}
autoPlay
loop={false}
style={styles.animation}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
animation: {
width: 220,
height: 220,
},
});
이렇게 하면 세 가지 이점이 있습니다:
- 라이브러리가 제대로 렌더링되는지 확인 가능
- 에셋 경로가 올바르게 해결되는지 확인 가능
- 나중에 재생 및 크기를 조정할 독립된 공간 확보
기본을 건너뛰면 즉시 다음과 같은 문제들이 발생합니다:
- 너비(width) 또는 높이(height) 미지정: 애니메이션은 존재하지만 화면에 보이지 않을 수 있습니다.
- 잘못된
require()경로: Metro가 파일을 찾을 수 없습니다. - 잘못된 export: 일부 JSON 파일은 기술적으로는 유효하지만, 모바일에서 예상대로 작동하지 않는 기능들을 포함하고 있습니다.
첫 렌더링은 로컬에서 결정론적(deterministic)으로 유지하세요. 지금은 아키텍처가 아니라 통합 테스트를 하는 단계입니다.
더 나은 첫 화면 테스트
중립적인 배경의 단순한 화면에 컴포넌트를 배치하세요:
import React from 'react';
import { SafeAreaView, StyleSheet } from 'react-native';
import { SuccessAnimation } from './src/SuccessAnimation';
export default function App() {
return (
<SafeAreaView style={styles.screen}>
<SuccessAnimation />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
iOS와 Android 시뮬레이터 모두에서 잘 작동한다면, 첫 번째 관문을 통과한 것입니다. 그다음 단계는 애니메이션을 더 추가하는 것이 아닙니다. 선언적(declarative) props를 사용할 때와 ref를 통해 직접 제어해야 할 때를 구분하는 법을 배우는 것입니다.
Lottie 애니메이션 컨트롤 마스터하기
대부분의 Lottie React Native 버그는 애니메이션이 상태(state)에 반응해야 할 때 발생합니다. Autoplay는 쉽습니다. 하지만 “사용자가 아이템을 좋아하면 이 세그먼트를 재생하고, 취소하면 역재생하며, 컴포넌트가 리렌더링될 때 끊김이 없어야 한다”는 요구사항이 들어가는 순간 복잡해집니다.

재생이 단순할 때는 props 사용
상호작용이 없는 재생에는 props만으로 충분합니다.
<LottieView
source={require('../assets/animations/loading.json')}
autoPlay
loop
speed={1}
/>
이 방식은 다음과 같은 경우에 적합합니다:
- 로딩 인디케이터
- 정적인 온보딩 일러스트레이션
- 장식용 empty state
선언적이고 가독성이 좋습니다. 컴포넌트가 마운트되면 재생이 시작되고 React가 제어권을 유지합니다. 애니메이션 로직을 props만으로 모두 설명할 수 있다면 이 방식을 유지하세요.
더 고도화된 선언적 사례는 progress애니메이션 프레임을 다른 값과 연결하는 경우입니다. 모션이 외부 진행 상황을 반영해야 할 때는 유용하지만, 일회성 트리거 이벤트에는 다소 불편할 수 있습니다.
refs로 넘어가기 전에 시각적으로 간단히 비교해 보겠습니다:
state가 애니메이션을 제어할 때는 refs 사용
사용자가 탭하거나, 토글하거나, 특정 액션을 완료할 때는 보통 ref를 사용하는 것이 더 안전합니다. 실제 데이터에 따르면 하이브리드 프레임워크를 사용하는 개발자의 68%가 잘못된 ref 처리로 인해 useEffect hooks에서 애니메이션 트리거가 실패했다고 보고합니다. 그래서 이를 중심으로 한 안정적인 패턴이 animation.current.play() 중요합니다. 이는 Capacitor 중심의 트리거 실패 논의에서 확인할 수 있습니다..
이 문제는 하이브리드 앱에만 국한되지 않습니다. 순수 React Native에서도, 특히 개발자가 ref를 다시 만들거나 마운트 전에 재생을 트리거하거나 불안정한 effect에 애니메이션 호출을 묶을 때 나타납니다.
import React, { useRef, useState } from 'react';
import { Pressable } from 'react-native';
import LottieView from 'lottie-react-native';
export function LikeButton() {
const animationRef = useRef<LottieView>(null);
const [liked, setLiked] = useState(false);
const onPress = () => {
if (!animationRef.current) return;
if (liked) {
animationRef.current.play(60, 0);
} else {
animationRef.current.play(0, 60);
}
setLiked(!liked);
};
return (
<Pressable onPress={onPress}>
<LottieView
ref={animationRef}
source={require('../assets/animations/like.json')}
loop={false}
autoPlay={false}
style={{ width: 96, height: 96 }}
/>
</Pressable>
);
}
신뢰할 수 있는 좋아요/좋아요 취소 패턴
이 패턴은 상태가 변경될 때마다 play() 내부에서 useEffect 호출하는 것보다 프로덕션에서 더 안정적입니다.
작동 원리:
- 이벤트가 애니메이션 트리거를 담당합니다: 프레스 이벤트는 재생을 시작하기에 안정적인 시점입니다.
- ref는 로컬 상태로 유지되며 지속됩니다:
useRef불필요한 리렌더링을 방지합니다. - 컴포넌트 내 autoplay 충돌을 방지합니다: 마운트 시점의 동작이 사용자 트리거 동작과 충돌하지 않도록 해야 합니다.
주의해야 할 흔한 실수들:
-
ref가 생성되기 전에 트리거하는 경우
만약animationRef.current이 null이면 재생되지 않습니다. 가드 처리를 해주세요. -
사용 시
autoPlay명령형 컨트롤과 함께 사용할 때
재생을 제어할 기본 소유자를 하나만 지정하세요. -
모든 것을 구동하는 방식
useEffect
이펙트는 유용하지만, UI 액션의 경우 타이밍 문제를 해결하기보다 오히려 일으키는 경우가 많습니다.
애니메이션이 탭에 반응한다면, 먼저 탭 핸들러 내부에서 트리거하세요. 다음의 경우에만
useEffect상태의 원천(source of truth)이 해당 인터랙션 외부에 있을 때 사용해야 합니다.
프로덕션 앱을 위한 성능 최적화
Lottie React Native는 처음엔 가벼워 보이지만, 팀에서 대용량 JSON 파일을 앱 번들에 넣기 시작하면 왜 앱 시작 속도가 느려지는지 의아해하게 되는 라이브러리 중 하나입니다. 문제는 애니메이션 자체가 아니라, 이를 전달하는 전략에 있습니다.

팀들이 실수하는 부분
가장 흔한 실수는 모든 애니메이션을 JavaScript에 직접 번들링하여 너무 일찍 로드하는 것입니다. Lottie JSON을 잘못 배포하는 방법에 대한 이 가이드에 따르면JS 번들에 Lottie JSON 같은 에셋을 과도하게 포함하면 앱 시작 시간이 중급 사양 기기에서 40% 이상 증가할 수 있습니다., 그리고 이를 온디맨드 로딩을 위한 native assets로 옮기는 것은 매우 중요한 최적화 단계입니다.
이는 많은 팀이 실제로 겪는 문제와 일치합니다. 문제는 단 하나의 작은 성공 애니메이션이 아닙니다. 바로 다음과 같은 것들이 쌓이는 것입니다:
- onboarding 모션
- 로딩 상태(loader states)
- e-commerce 반응형 애니메이션
- 브랜드 아이덴티티가 담긴 빈 화면
- 그 옆에 함께 자리 잡은 locale 파일 및 기타 번들 용량이 큰 에셋들
앱에 이미 startup budget 문제가 있다면, Lottie 파일은 상황을 빠르게 악화시킬 수 있습니다.
무엇을 먼저 최적화해야 할까
내보내기(export) 단계부터 시작하세요. 허술하게 내보낸 애니메이션은 나중에 파싱, 메모리, 렌더링 안정성 측면에서 비용을 치르게 됩니다. 디자이너가 내보낸 파일을 그대로 수용하지 마세요.
다음 프로덕션 체크리스트를 활용하세요:
- 배포 전 JSON 압축: 파일 크기가 작을수록 로딩이 빠르고 앱 시작 시 부하를 줄일 수 있습니다.
- 핵심적이지 않은 애니메이션은 JS bundle에서 분리하세요: 앱 시작 시 code이 즉시 필요한 작업에만 집중하도록 유지하세요.
- 애니메이션을 필요할 때만 로드하세요: 화면이나 특정 액션이 필요한 시점에 렌더링하세요.
- 구형 기기에서의 동작을 점검하세요: 최신 시뮬레이터에서는 과도한 리소스 소모가 드러나지 않을 수 있습니다.
- 앱 시작 시 장식용으로 큰 Lottie 파일을 사용하는 것을 피하세요: 첫 상호작용에 필수적이지 않다면, 앱 실행 성능을 저해해서는 안 됩니다.
모바일 성능 최적화에 집중하는 팀이라면, AppLighter의 모바일 성능 가이드는 애니메이션 결정을 앱 시작, 렌더링, 프레임워크 트레이드오프 관점에서 다루므로 매우 유용한 참고 자료가 될 것입니다.
한 가지 냉정한 사실: 첫 상호작용을 지연시키는 화려한 애니메이션은 디자인의 승리가 아니라 대개 제품의 버그입니다.
React Native만 따로 떼어 생각해서도 안 됩니다. 하이브리드 스택을 사용하는 팀도 비슷한 에셋 로딩 문제를 겪으며, 더 넓은 범위의 Capacitor 앱을 위한 애니메이션 성능 가이드는 Lottie 결정에도 그대로 적용됩니다.
로컬 파일 vs 원격 전송
로컬 파일은 예측 가능합니다. 오프라인에서도 작동하고, 네트워크 변수를 제거하며, 테스트도 쉽습니다. 다만 번들 크기가 커질 위험이 있습니다.
원격 전송은 바이너리를 가볍게 유지하지만, 애니메이션의 가용성, 캐싱, 폴백 문제를 고려해야 합니다. 중요도가 낮은 모션에는 적합한 트레이드오프지만, 구매 확인이나 인증 성공 같은 핵심 UX 상태에서는 위험합니다.
실무적인 구분 방법은 다음과 같습니다:
| 에셋 유형 | 권장 기본값 |
|---|---|
| 핵심 상호작용 애니메이션 | 로컬, 최적화, 과하지 않은 용량 |
| 가끔 쓰는 프로모션 모션 | 폴백 포함 원격 |
| 시작 경로 애니메이션 | 꼭 필요한 경우에만 로컬 사용 |
| 드물게 쓰는 기능 일러스트 | 온디맨드 로드 |
이 섹션의 규칙 중 하나만 적용한다면, 이 규칙을 따르세요: Lottie JSON을 단순한 장식이 아닌 성능에 민감한 에셋으로 취급하기.
흔한 Lottie 문제 해결하기
Lottie가 깨질 때 원인은 대개 뻔합니다. 잘못된 경로, 크기 누락, 잘못된 ref 타이밍, 너무 무거운 JSON. 가장 빠른 디버깅 방법은 변수를 줄이는 것입니다.
Android에서 애니메이션이 렌더링되지 않음
먼저 JSON 파일 경로가 올바른지 확인하세요. 그다음 컴포넌트에 명시적인 크기(dimensions)를 지정하세요.
<LottieView
source={require('../assets/animations/success.json')}
autoPlay
style={{ width: 200, height: 200 }}
/>
그래도 안 된다면, 정상 작동하는 다른 애니메이션으로 교체해 보세요. 이를 통해 파일 문제인지 설정 문제인지 파악할 수 있습니다.
구형 기기에서 재생이 끊김
이는 보통 컴포넌트 API가 아닌 에셋 문제입니다.
다음 해결 방법을 시도해 보세요:
- 애니메이션 복잡도 줄이기: 원본 파일이 너무 무겁다면 더 가벼운 파일로 다시 export해 달라고 요청하세요.
- 지연 로딩(Load later): 초기 화면 렌더링 작업과 충돌하지 않도록 하세요.
- 압축된 버전 테스트: 압축된 파일이 더 잘 작동한다면, 병목 지점을 찾은 것입니다.
- 여러 개의 Lottie 뷰를 동시에 제거: 한 화면에 너무 많은 애니메이션이 있으면 과할 수 있습니다.
ref가 null이거나 play가 작동하지 않음
Null ref는 보통 마운트 전에 트리거가 실행되었거나, 컴포넌트가 조건부로 제거되었음을 의미합니다.
if (animationRef.current) {
animationRef.current.play();
}
다음 방법을 통해 ref를 안정적으로 유지하세요: useRef, 그리고 애니메이션 컴포넌트를 불필요하게 다시 생성하지 마세요. 로컬 빌드에서 반복적인 오류를 디버깅 중이라면, 오래된 캐시를 삭제하는 것이 도움이 됩니다. 간단한 Yarn 캐시 정리 루틴 만으로도 개발 중 발생하는 잘못된 에셋 동작을 해결할 수 있습니다.
화면 크기에 따라 애니메이션이 깨져 보임
애니메이션이 레이아웃을 결정하게 두지 마세요. 컨테이너 안에 넣고 크기를 의도한 대로 지정하세요.
- 아이콘과 리액션에는 고정된 영역(bounds)을 사용하세요
- 큰 일러스트레이션에는 종횡비를 고려한 wrapper를 사용하세요
- 에셋의 원래 구성을 확인하지 않고 전체 너비로 늘리는 것을 피하세요
“Lottie가 작동하지 않아요”라는 보고의 대부분은 레이아웃, 에셋 또는 타이밍 문제입니다. 라이브러리는 대개 요청한 대로 정확히 동작하고 있습니다.
디버깅을 위한 확실한 지름길이 필요하다면, 모든 고급 prop을 제거하고 중앙 정렬된 뷰에서 로컬 애니메이션 하나만 렌더링한 뒤 다시 하나씩 추가해 보세요. 이 방식이 복잡한 프로덕션 화면을 계속 들여다보는 것보다 문제를 훨씬 빠르게 격리할 수 있습니다.
Capgo를 사용하면 스토어 심사를 기다릴 필요 없이 JavaScript, 에셋 및 설정 수정을 Capacitor 앱에 즉시 배포할 수 있습니다. 하이브리드 앱을 운영 중이며 더 안전한 업데이트 방식, 단계적 배포(staged rollouts), 그리고 프론트엔드 이슈 발생 시 빠른 복구가 필요하다면, Capgo 한번 살펴보세요.