React Native 통합 앱에서 여러 앱 설정(Profile Registry) 관리하기
React Native로 소스 하나에서 여러 개의 앱을 묶어서 빌드하고 운영하다 보면, 가장 골치 아픈 게 앱별 설정 관리입니다. 앱 이름, 패키지명, 첫 화면, 사용하는 기능이 조금씩 다 다른데, 처음에는 if 문 몇 개로 버티다가 앱이 늘어나면 코드가 누더기가 되고 예상치 못한 화면이 같이 깨지기 쉽습니다.
저의 경우도 예전에 특정 앱에서만 보여야 하는 메뉴가 다른 앱에 튀어나오거나, 새 앱을 추가했는데 첫 화면이 빈 화면으로 뜨는 삽질을 한 적이 있습니다.
이럴 때는 Profile Registry(프로파일 레지스트리)를 별도로 두고, 앱별 설정을 한 곳에서 데이터로 관리하면 유지보수가 훨씬 편해집니다. 나중에 제가 다시 보려고 실무에서 사용하는 구조와 주의점을 정리해 둡니다.
통합 앱 구조에서 관리가 안 될 때 생기는 문제
앱이 3개에서 10개, 20개로 늘어나면 코드만 보고 어느 앱이 무슨 기능을 쓰는지 추적하기가 불가능해집니다.
- 지도, 검색, 설정 화면 같은 기능 사용 여부 조건문이 코드 곳곳에 흩어짐.
- 권한 요청 기준이 앱별로 다른데 공통 코드에서 한 번에 처리하다 꼬임.
- 서버 URL이나 API 키, 광고 ID 같은 민감한 설정이 코드와 레지스트리에 뒤섞임.
- 한쪽 프로파일을 수정했는데 뚱딴지같은 다른 앱에서 회귀 오류가 발생함.
결국 화면 컴포넌트 내부에서 앱 아이디로 직접 분기 처리를 하면 안 됩니다. 화면은 단순히 "현재 프로파일이 이 기능을 지원하는가?"만 데이터로 받아서 렌더링하는 구조가 안전합니다.
Profile Registry 기본 설계 코드 (TypeScript)
실무에서 사용하는 형태에서 민감한 정보들을 걷어내고 정리한 TypeScript 기반 예시 코드입니다. 설정을 타입으로 꽉 짜놓고, 화면에서는 helper 함수를 거쳐서 확인하는 방식입니다.
type Engine = 'tour' | 'filedata' | 'utility' | 'hybrid';
type RouteName = 'Home' | 'Map' | 'List' | 'Settings';
type FeatureFlags = {
map: boolean;
search: boolean;
settings: boolean;
externalLinks: boolean;
};
type ProfileConfig = {
profileId: string;
displayName: string;
applicationId: string;
engine: Engine;
group: string;
initialRoute: RouteName;
features: FeatureFlags;
permissions: string[];
};
export const profileRegistry: Record<string, ProfileConfig> = {
sampleTourBusan: {
profileId: 'sampleTourBusan',
displayName: '샘플 관광 앱',
applicationId: 'com.example.tour.busan',
engine: 'tour',
group: 'tour-regional',
initialRoute: 'Home',
features: {
map: true,
search: true,
settings: true,
externalLinks: true,
},
permissions: ['location'],
},
sampleFileData: {
profileId: 'sampleFileData',
displayName: '샘플 파일 데이터 앱',
applicationId: 'com.example.filedata',
engine: 'filedata',
group: 'file-data',
initialRoute: 'List',
features: {
map: true,
search: true,
settings: true,
externalLinks: false,
},
permissions: [],
},
};
// 프로파일 정보 가져오기
export function getProfile(profileId: string): ProfileConfig {
const profile = profileRegistry[profileId];
if (!profile) {
throw new Error(`Unknown profileId: ${profileId}`);
}
return profile;
}
// 기능 지원 여부 확인 헬퍼 함수
export function hasFeature(
profile: ProfileConfig,
feature: keyof FeatureFlags,
): boolean {
return profile.features[feature] === true;
}
이렇게 짜두면 화면 단에서 profileId === 'sampleTourBusan' 같은 지저분한 코드를 쓸 필요가 없습니다. 그냥 hasFeature(profile, 'map') 형태로 깔끔하게 조건부를 태우면 됩니다.
initialRoute와 Feature Flag 관리 시 팁
1. initialRoute는 반드시 데이터로 제어
앱마다 첫 화면이 다를 때가 많습니다. 어떤 앱은 홈이 먼저고, 어떤 앱은 리스트나 지도가 먼저 뜹니다. 네비게이션 공통 코드에 시작 라우트를 박아두지 말고 아래처럼 레지스트리 값을 읽도록 처리합니다.
function RootNavigator({ profileId }: { profileId: string }) {
const profile = getProfile(profileId);
return (
<Stack.Navigator initialRouteName={profile.initialRoute}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Map" component={MapScreen} />
<Stack.Screen name="List" component={ListScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
);
}
주의: 첫 화면(initialRoute)만 바꾼다고 끝이 아닙니다. 해당 화면 진입 시 필요한 초기 데이터 상태나 로딩 처리, 권한 체크가 꼬이지 않는지 반드시 세트로 확인하셔야 합니다.
2. Feature Flag는 단순 'UI 숨김'이 아니라 '계약'이다
만약 map: false 처리를 했다면, 단순히 지도 탭만 안 보이게 가리는 걸로 끝내면 안 됩니다. 딥링크나 상세 페이지 내 버튼을 통해 권한 요청이 가거나 비정상 진입이 되어 빈 화면이 뜨는 경우가 생깁니다.
- map: 지도 탭 숨김 + 위치 권한 요청 비활성화 + 상세 페이지 내 길찾기 버튼 제거
- search: 검색 창 숨김 + 빈 검색 결과 대응 + 관련 필터 로직 차단
- externalLinks: 외부 브라우저 호출 차단 + 인앱 링크 허용 목록 검증 제외
새 프로파일 추가 절차 및 테스트 기준
새로운 앱 설정을 추가할 때는 코드부터 무작정 건드리지 말고 아래 순서대로 체크리스트를 밟는 게 실수를 줄이는 지름길입니다.
- profileId, applicationId, displayName 정의
- 공통 기능 계열 분류 (engine, group) 선택
- 첫 화면으로 사용할 initialRoute 지정
- 기능 플래그(features) 및 필요한 권한(permissions) 명시
- 공개 가능한 서비스 약관, 고객지원 링크 매핑
- selectedProfiles 등을 활용해 단일 프로파일 빌드 및 설치 테스트 진행
- 기존 대표 프로파일 회귀 테스트(Smoke Test) 실행
통합 앱 특성상 전체 프로파일을 매번 다 빌드해보긴 어렵습니다. 따라서 설정을 건드린 후에는 최소한 같은 group에 묶여 있는 대표 프로파일 하나쯤은 같이 띄워보고 기능이 안 깨졌는지 점검하는 습관이 중요합니다.
실무 운영 시 주의할 점 (자주 하는 실수)
- 민감 정보 분리 (가장 중요) 레지스트리 파일은 개발자들이 자주 열어보고 수정하는 파일입니다. 여기에 운영 서버 URL, 관리자 전용 토큰, API 키, AdMob 광고 ID, keystore 비밀번호 같은 민감 정보를 같이 때려 박으면 보안상 아주 위험합니다.
- 해당 정보들은 반드시 환경변수(.env)나 별도의 빌드 시크릿 매핑 키로 분리하고, 레지스트리에는 공개 가능한 앱 구성 데이터만 남겨야 합니다. 저처럼 블로그나 문서에 예제를 올릴 때도 실값이 노출되지 않도록 각별히 조심하세요.
- 규모에 맞는 관리 방식 선택 앱 개수에 따라 관리 방식을 조금씩 고도화하는 것이 좋습니다. 처음부터 무리하게 자동화할 필요는 없습니다.
| 1~3개 | TypeScript 단순 상수 파일 | 구조가 한눈에 들어오고 빠르게 수정 가능 |
| 4~10개 | TS 레지스트리 + Validator 함수 | 빌드 전 필드 누락이나 오타(잘못된 route 등) 검증 가능 |
| 10개 이상 | Catalog 파일 (JSON/YAML) + 빌드 스크립트 | 반복되는 방대한 설정을 표 형태로 뽑아서 관리하기 유리 |
마무리
코드 곳곳에 if (isTargetApp) 식의 분기문이 쌓이기 시작했다면 이미 신호가 온 겁니다. 더 지저분해지기 전에 작은 레지스트리 구조를 만들고, 필수 필드가 비어있으면 빌드 전에 에러를 뱉어주는 간단한 Validator부터 붙여보세요. 설정을 데이터로 바라보는 순간부터 새 앱을 추가할 때 생기는 피로감이 확 줄어듭니다.
'개발 노트 > React Native' 카테고리의 다른 글
| React Native 통합 앱 profileId 조건문 지우기 (Controller와 Model 분리) (0) | 2026.05.20 |
|---|---|
| React Native 통합 앱 프로파일별 화면 접근 제어 (Route Guard 구현하기) (0) | 2026.05.20 |
| [React Native] 멀티 프로파일 통합 앱 태블릿 및 반응형 화면 최적화 기준 정리 (0) | 2026.05.19 |
| [React Native] 통합 앱에서 selectedProfiles로 빌드 대상을 관리하는 방법 (0) | 2026.05.19 |
| React Native 통합 앱 프로파일 회귀 테스트 설계 및 적용하기 (0) | 2026.05.19 |