React Native 완벽 가이드 | 크로스플랫폼 모바일 앱·Navigation·실전 활용
이 글의 핵심
React Native 완벽 가이드: 컴포넌트·네비게이션·API부터 Bridge·JSI·Yoga 레이아웃·스레드 모델·네이티브 모듈·프로덕션 패턴까지 심화 정리. React Native로 iOS/Android 앱을 개발하는 완벽 가이드입니다. Components, Navigation, Async Storage, API, 배포까지 실전 예제로 정리했으며, Bridge·JSI·New…
이 글의 핵심
React Native로 iOS/Android 앱을 개발하는 완벽 가이드입니다. Components, Navigation, Async Storage, API, 배포까지 실전 예제로 정리했으며, Bridge·JSI·New Architecture, 네이티브 모듈(Turbo/스펙), Yoga 레이아웃, 스레드 모델, 프로덕션 운영 패턴까지 내부 구조와 실무 관점에서 확장했습니다.
실무 경험 공유: React Native로 iOS/Android 앱을 동시에 출시하면서, 개발 시간이 60% 단축되고 코드 재사용률이 90%에 달한 경험을 공유합니다.
들어가며: “모바일 앱 개발이 어려워요”
실무 문제 시나리오
시나리오 1: iOS와 Android를 따로 개발해야 해요
Swift와 Kotlin을 배워야 합니다. React Native는 한 번 개발로 두 플랫폼을 지원합니다. 시나리오 2: 웹 개발 경험을 활용하고 싶어요
Native는 학습 곡선이 높습니다. React Native는 React 지식을 활용합니다. 시나리오 3: 빠른 프로토타이핑이 필요해요
Native는 시간이 오래 걸립니다. React Native는 빠릅니다.
1. React Native란?
핵심 특징
React Native는 크로스플랫폼 모바일 앱 프레임워크입니다. 주요 장점:
- 크로스플랫폼: iOS + Android
- React: 웹 개발 경험 활용
- Native 성능: 실제 Native 컴포넌트
- Hot Reload: 즉시 반영
- 대규모 생태계: 수천 개의 라이브러리
2. 설치 및 프로젝트 생성
Expo (권장)
npx create-expo-app my-app
cd my-app
npx expo start
React Native CLI
npx react-native init MyApp
cd MyApp
npm run android
npm run ios
3. 기본 컴포넌트
import { View, Text, Image, ScrollView, TextInput, Button } from 'react-native';
export default function App() {
return (
<ScrollView>
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>Hello React Native</Text>
<Image source={{ uri: 'https://example.com/image.jpg' }} style={{ width: 200, height: 200 }} />
<TextInput placeholder="Enter text" style={{ borderWidth: 1, padding: 10 }} />
<Button title="Click me" onPress={() => alert('Clicked!')} />
</View>
</ScrollView>
);
}
4. StyleSheet
import { View, Text, StyleSheet } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
});
5. Navigation
설치
npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context
npm install @react-navigation/native-stack
Stack Navigator
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/Home';
import DetailsScreen from './screens/Details';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// screens/Home.tsx
import { View, Button } from 'react-native';
export default function HomeScreen({ navigation }) {
return (
<View>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details', { id: 1 })}
/>
</View>
);
}
6. Async Storage
npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// 저장
await AsyncStorage.setItem('user', JSON.stringify({ name: 'John' }));
// 읽기
const user = await AsyncStorage.getItem('user');
const userData = JSON.parse(user);
// 삭제
await AsyncStorage.removeItem('user');
7. API 호출
import { useEffect, useState } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
interface User {
id: number;
name: string;
}
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://api.example.com/users')
.then((response) => response.json())
.then((data) => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) {
return <ActivityIndicator size="large" />;
}
return (
<FlatList
data={users}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 16 }}>
<Text>{item.name}</Text>
</View>
)}
/>
);
}
8. Platform 특정 코드
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
padding: Platform.OS === 'ios' ? 20 : 10,
},
});
// 또는
import { Platform } from 'react-native';
const Component = Platform.select({
ios: () => require('./ComponentIOS'),
android: () => require('./ComponentAndroid'),
})();
9. 실전 예제: 로그인 화면
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import { useState } from 'react';
export default function LoginScreen() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
try {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
Alert.alert('Success', 'Logged in successfully');
} else {
Alert.alert('Error', data.message);
}
} catch (error) {
Alert.alert('Error', 'Network error');
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Login</Text>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<TouchableOpacity style={styles.button} onPress={handleLogin}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
backgroundColor: '#fff',
},
title: {
fontSize: 32,
fontWeight: 'bold',
marginBottom: 32,
textAlign: 'center',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 12,
marginBottom: 16,
borderRadius: 8,
},
button: {
backgroundColor: '#3498db',
padding: 16,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
10. 아키텍처 심화: Bridge(구) vs JSI·New Architecture(신)
React Native를 “그냥 React 문법으로 모바일 UI를 그린다” 수준에서 한 단계 끌어올리려면, JavaScript 런타임과 네이티브 레이어가 어떻게 만나는지를 이해해야 합니다. 여기서 핵심 축은 Bridge(레거시)와 JSI + Fabric + TurboModules(신 아키텍처)입니다.
Bridge(레거시) 모델이란?
전통적인 React Native는 비동기·배치되는 메시지 채널(Bridge)을 통해 JS와 네이티브가 통신합니다. JS 스레드에서 생성된 UI 업데이트·네이티브 모듈 호출은 직접 포인터를 넘기는 방식이 아니라, 대개 직렬화 가능한 페이로드(예: JSON 유사 구조)로 포장되어 네이티브 쪽으로 전달됩니다. 이 설계의 장점은 런타임 경계가 명확하고, 언어/ABI가 다른 두 세계를 비교적 안전하게 연결할 수 있다는 점입니다.
반면 단점도 분명합니다. 대량의 작은 호출이 쌓이면 직렬화·역직렬화와 큐잉 비용이 눈에 띄고, 동기적으로 즉시 값을 읽어야 하는 패턴(초고빈도 getter, 타이트한 제스처 루프 등)에는 구조적으로 불리합니다. “왜 이 화면은 스크롤/애니메이션이 버벅일까?”를 추적할 때, Bridge 병목은 여전히 1순위 의심 지점입니다.
JSI(JavaScript Interface)와 New Architecture
JSI는 JavaScript 엔진(Hermes 등)에 더 가깝게 붙는 얇은 C++ 레이어로, 네이티브 코드가 JS 쪽 호스트 객체(Host Object)를 노출하거나, 반대로 JS가 C++ 구현체를 동기적으로 호출할 수 있는 길을 엽니다. 즉 “Bridge를 타고 JSON을 왕복”하는 모델에서 벗어나, 컴파일 타임·런타임에 맞물리는 바인딩을 지향합니다.
New Architecture에서 자주 묶어 말하는 구성요소는 다음과 같습니다.
- Fabric: 새 렌더러. UI 업데이트 파이프라인을 보다 일관되게 다루고, 레이아웃·커밋 단계를 현대화합니다.
- TurboModules: 네이티브 모듈 시스템의 재설계. 필요할 때 로딩되고, JSI 기반으로 더 직접적인 상호운용을 목표로 합니다.
- Codegen: JS 타입·스펙에서 네이티브 스텁을 생성해 보일러플레이트와 불일치를 줄입니다.
실무 관점에서의 체크포인트는 단순합니다. “우리 앱은 New Architecture를 켰는가?”, “핵심 병목이 Bridge인가, JS 연산인가, 네이티브 I/O인가?”를 분리해 측정하는 것입니다. 같은 코드라도 아키텍처에 따라 프로파일이 달라질 수 있습니다.
마이그레이션 현실론
레거시 Bridge 위에서 잘 돌아가던 앱이 New Architecture에서 깨지는 대표 원인은 가정이 달라진 스레드/타이밍, 직접 메모리 공유를 가정한 네이티브 코드, Codegen 스펙과 실제 구현의 불일치입니다. 따라서 전환은 “플래그만 켠다”가 아니라 의존 라이브러리 호환표 + 단계적 검증이 전제입니다.
11. 네이티브 모듈 생성: Bridge 모듈 vs Turbo Native Module
“JS에서 OS API를 직접 다 길게 깔 필요가 있다”면 결국 네이티브 모듈로 내려가야 합니다. 여기서는 패턴을 두 층으로 나누어 봅니다.
레거시: NativeModules + Bridge 기반 모듈
과거(그리고 여전히 많이 보이는) 패턴은 Android에서 @ReactMethod로 메서드를 노출하고, iOS에서 RCT_EXPORT_METHOD로 노출하는 방식입니다. JS에서는 NativeModules로 접근합니다. 이 모델은 이해하기 쉽지만, 스펙이 코드 곳곳에 흩어지기 쉽고, 타입·인자 이름이 JS와 네이티브에서 어긋나기 쉬운 구조입니다.
신: Turbo Native Module + Codegen
New Architecture에서는 스펙 파일(JavaScript/TypeScript로 작성되는 인터페이스 정의)을 중심에 두고, Codegen이 네이티브 쪽 보일러플레이트를 생성합니다. 장점은 명확합니다.
- 단일 진실 공급원: 메서드 시그니처가 스펙에 고정된다.
- 타입 안전성: TS 정의와 네이티브 구현의 괴리를 줄인다.
- 로딩 모델 개선: 모듈 초기화 비용을 보다 현실적으로 통제하기 쉽다.
아래는 “스펙을 먼저 고정한다”는 사고방식을 보여주는 최소 형태의 예시 스케치입니다(프로젝트 템플릿·Codegen 설정은 공식 문서의 최신 스캐폴딩을 따르는 것이 안전합니다).
// NativeSampleModule.ts (스펙 스케치 — 프로젝트 설정에 맞게 조정)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
readonly getConstants: () => { appVersion: string };
multiply(a: number, b: number): number;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeSampleModule');
네이티브 구현은 플랫폼별로 TurboModule 계약을 만족시키면 되고, 실제 세부 시그니처·파일 배치는 Codegen 출력물과 1:1로 맞추는 것이 핵심입니다. 즉 “예쁜 예제”보다 빌드가 생성한 인터페이스를 기준으로 역으로 맞춘다는 태도가 프로덕션에서 덜 고생합니다.
설계 팁(공통)
- 메인 스레드 규칙: UIKit/Android UI 스레드에서만 안전한 API는 반드시 명시하고, 무거운 작업은 큐잉합니다.
- 에러 계약: 네이티브 예외를 그대로 삼키지 말고, JS에서 처리 가능한 형태로 매핑합니다.
- 버전 가드: OS별 분기는 최소화하되, 불가피하면 기능 플래그로 감쌉니다.
12. 레이아웃: Yoga와 Flexbox 모델
React Native의 레이아웃은 플랫폼 UI 프레임워크(Auto Layout, Android Layout)를 “그대로 한 줄로” 쓰는 방식이 아니라, 크로스플랫폼으로 통일된 Flexbox 엔진을 사용합니다. 그 엔진이 Yoga입니다.
Yoga가 의미하는 것
Yoga는 제약 기반의 Flexbox 레이아웃을 빠르게 계산하기 위한 C++ 라이브러리로, RN의 스타일 시스템(폭/높이, flex, justifyContent, alignItems, position, padding 등)과 결합합니다. 웹 CSS의 Flexbox와 같아 보이는 이름이 많지만, CSS 전체가 아닌 부분집합이라는 점이 실무에서 발목을 잡습니다.
자주 터지는 함정
flex: 1의 의미: 부모 축에서 남는 공간을 어떻게 나눌지에 대한 문제입니다. 부모가 높이를 갖지 못하면 자식의flex는 기대와 다르게 보일 수 있습니다.- 절대 배치:
position: 'absolute'는 강력하지만, 터치 영역·키보드·세이프 에어리어와 합쳐지면 충돌이 납니다. - 플랫폼별 기본값: 텍스트·폰트 메트릭은 미세하게 달라 픽셀 퍼펙트를 과도하게 요구하면 비용이 큽니다.
실무 접근
복잡한 화면은 “스타일 객체 한 덩어리”보다 레이아웃 트리를 먼저 그린 뒤 View 경계를 나누는 편이 Yoga 계산을 예측 가능하게 만듭니다. 특히 리스트·중첩 스크롤처럼 스크롤 뷰와 flex가 겹치는 구간은 성능 이슈의 중심부입니다.
13. 스레드 모델: JS, UI, 그리고 그 사이의 작업들
React Native 성능 이슈의 상당수는 “알고리즘이 느려서”가 아니라 잘못된 스레드에 작업을 몰아넣어서 발생합니다. 모델을 단순화하면 다음과 같습니다.
JavaScript 스레드
JS 스레드는 리액트 렌더링, 비즈니스 로직, 대부분의 이벤트 처리(제스처 체인에 따라 다름)가 모이는 곳입니다. 이 스레드를 오래 점유하면 프레임 드랍으로 직결됩니다. “렌더는 가벼운데 끊긴다”면, 동기 루프, 과도한 로그, 큰 JSON 파싱, 무거운 정규식, 메모이제이션 누락으로 인한 대규모 리렌더를 의심합니다.
UI 스레드(메인 스레드)
iOS/Android의 실제 뷰 갱신과 많은 시스템 콜백이 도는 축입니다. 네이티브 측에서 메인 스레드를 블로킹하면 터치 반응, 애니메이션, 전환이 함께 무너집니다.
Shadow/레이아웃·네이티브 모듈 작업
레거시 아키텍처에서는 레이아웃 계산(Shadow 관련)·일부 네이티브 호출이 별도의 흐름으로 설명되는 경우가 많고, New Architecture에서는 파이프라인이 바뀌면서 개발자가 체감하는 타이밍도 달라질 수 있습니다. 중요한 실무 포인트는 하나입니다: “이 작업은 어느 스레드 안전성이 요구되는가?”를 먼저 적고 코드를 씁니다.
Hermes를 포함한 런타임 관점
Hermes는 시작 시간·메모리 특성 측면에서 이점이 많지만, “무조건 빠름”은 아닙니다. 바이트코드 특성, 디버그/프로파일링 설정, Intl/정규식 사용 패턴에 따라 체감이 달라질 수 있습니다. 성능은 추측이 아니라 프로파일로 닫습니다.
14. 프로덕션 React Native 패턴
실서비스에서 반복적으로 등장하는 패턴을 정리합니다. 이 목록은 “기능 구현”이 아니라 운영 가능성에 가깝습니다.
상태·데이터 계층
- 서버 상태: TanStack Query 같은 캐시·재시도·스테일 관리가 기본값에 가깝습니다.
useEffect + fetch만으로는 경쟁 조건·백그라운드 재진입 처리가 쉽게 누락됩니다. - 클라이언트 상태: 도메인별로 스토어를 쪼개고, 불변 업데이트와 셀렉터로 리렌더 범위를 제한합니다.
리스트 성능
FlatList는 단순 렌더 함수가 아니라 운영 컴포넌트입니다. keyExtractor, getItemLayout(고정 높이일 때), windowSize, removeClippedSubviews, memo된 renderItem 등은 “최적화 팁”이 아니라 기본 장비입니다. 데이터가 커질수록 페이지네이션·가상화가 없으면 메모리 곡선이 선형으로 치솟습니다.
네비게이션·딥링크·인증
- 딥링크/유니버설 링크는 플랫폼 설정과 앱 라우팅 상태가 분리되어 있어 가장 자주 깨지는 경계입니다. “앱이 켜진 상태/꺼진 상태/백그라운드 복귀”를 각각 테스트합니다.
- 토큰 갱신은 네비게이션 가드와 분리해 단일 인터셉터에서 처리하는 편이 디버깅에 유리합니다.
에러·관측성
- 전역 에러 바운더리 + 네이티브 크래시 리포팅(Sentry 등)는 최소선입니다.
- 성능 마커: 화면 전환, TTI, API 구간에 구간 측정을 박아야 회귀를 잡습니다.
배포·릴리즈
- 코드 푸시/OTA를 쓰더라도 네이티브 변경은 별도 트랙으로 관리합니다.
- 기능 플래그로 점진적 롤아웃을 하고, 롤백 플랜을 문서화합니다.
보안 기본기
- 민감값은 Secure Storage/Keystore/Keychain 계열로 내리고, 로그에 토큰을 남기지 않습니다. 스크린샷/최근 앱 썸네일 이슈는 민감 화면 가림 정책까지 포함해 설계합니다.
정리 및 체크리스트
핵심 요약
- React Native: 크로스플랫폼 모바일
- iOS + Android: 한 번 개발
- React: 웹 경험 활용
- Native 성능: 실제 Native
- Hot Reload: 즉시 반영
- 대규모 생태계: 수천 개 라이브러리
- 내부 구조: Bridge(메시지) ↔ JSI·Fabric·TurboModules(현대화), Yoga로 Flex 레이아웃, JS/UI 스레드 분리 이해
- 프로덕션: 리스트 가상화·관측성·릴리즈·보안·딥링크 경계를 전제로 설계
구현 체크리스트
- React Native 설치
- 프로젝트 생성
- 기본 컴포넌트 사용
- StyleSheet 작성
- Navigation 구현
- Async Storage 사용
- API 호출
- Bridge vs JSI·New Architecture 이해 및 마이그레이션 검토
- 네이티브 모듈(레거시/Turbo) 요구사항·스펙·스레드 안전성 정리
- Yoga(Flex) 레이아웃 트리 설계·리스트 가상화 점검
- JS/UI 스레드 병목 프로파일링
- 프로덕션(관측성·릴리즈·보안·딥링크) 점검
- 배포
같이 보면 좋은 글
이 글에서 다루는 키워드
React Native, Mobile, iOS, Android, Cross-platform, TypeScript, Expo
자주 묻는 질문 (FAQ)
Q. Flutter와 비교하면 어떤가요?
A. React Native가 JavaScript 생태계를 활용할 수 있습니다. Flutter는 더 빠르고 일관된 UI를 제공합니다.
Q. 성능은 어떤가요?
A. Native에 가깝지만, 완전히 동일하지는 않습니다.
Q. Expo를 사용해야 하나요?
A. 초보자에게는 Expo를 권장합니다. Native 모듈이 필요하면 Bare Workflow를 사용하세요.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Facebook, Instagram, Airbnb 등이 사용했습니다.
Q. Bridge와 JSI의 차이를 한 줄로 정리하면?
A. Bridge는 JS와 네이티브가 비동기 메시지(직렬화 중심)로 통신하는 레거시 축이고, JSI는 엔진에 가까운 C++ 바인딩으로 더 직접적인 상호운용을 가능하게 합니다. New Architecture는 JSI를 중심에 둡니다.
Q. Yoga는 CSS와 동일한가요?
A. 아닙니다. Yoga는 Flexbox 중심의 레이아웃 엔진이며, 웹 CSS 전체와 1:1로 같지 않습니다. 이름이 비슷해도 부분집합으로 이해하는 것이 안전합니다.
Q. JS 스레드와 UI 스레드를 헷갈리면 어떻게 디버깅하나요?
A. 먼저 프로파일러로 프레임 드랍 구간을 잡고, 그 구간이 리렌더/무거운 JS 연산인지 메인 스레드의 네이티브 작업인지 나눕니다. 원인이 다르면 처방도 다릅니다.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「React Native 완벽 가이드 | 크로스플랫폼 모바일 앱·Navigation·실전 활용」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「React Native 완벽 가이드 | 크로스플랫폼 모바일 앱·Navigation·실전 활용」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.