본문 바로가기
개발 노트/React Native

React Native 통합 앱에서 여러 앱 설정(Profile Registry) 관리하기

by pposooj 2026. 5. 19.

React Native 통합 앱에서 여러 앱 설정(Profile Registry) 관리하기

React Native로 소스 하나에서 여러 개의 앱을 묶어서 빌드하고 운영하다 보면, 가장 골치 아픈 게 앱별 설정 관리입니다. 앱 이름, 패키지명, 첫 화면, 사용하는 기능이 조금씩 다 다른데, 처음에는 if 문 몇 개로 버티다가 앱이 늘어나면 코드가 누더기가 되고 예상치 못한 화면이 같이 깨지기 쉽습니다.

저의 경우도 예전에 특정 앱에서만 보여야 하는 메뉴가 다른 앱에 튀어나오거나, 새 앱을 추가했는데 첫 화면이 빈 화면으로 뜨는 삽질을 한 적이 있습니다.

이럴 때는 Profile Registry(프로파일 레지스트리)를 별도로 두고, 앱별 설정을 한 곳에서 데이터로 관리하면 유지보수가 훨씬 편해집니다. 나중에 제가 다시 보려고 실무에서 사용하는 구조와 주의점을 정리해 둡니다.

통합 앱 구조에서 관리가 안 될 때 생기는 문제

앱이 3개에서 10개, 20개로 늘어나면 코드만 보고 어느 앱이 무슨 기능을 쓰는지 추적하기가 불가능해집니다.

  • 지도, 검색, 설정 화면 같은 기능 사용 여부 조건문이 코드 곳곳에 흩어짐.
  • 권한 요청 기준이 앱별로 다른데 공통 코드에서 한 번에 처리하다 꼬임.
  • 서버 URL이나 API 키, 광고 ID 같은 민감한 설정이 코드와 레지스트리에 뒤섞임.
  • 한쪽 프로파일을 수정했는데 뚱딴지같은 다른 앱에서 회귀 오류가 발생함.

결국 화면 컴포넌트 내부에서 앱 아이디로 직접 분기 처리를 하면 안 됩니다. 화면은 단순히 "현재 프로파일이 이 기능을 지원하는가?"만 데이터로 받아서 렌더링하는 구조가 안전합니다.

Profile Registry 기본 설계 코드 (TypeScript)

실무에서 사용하는 형태에서 민감한 정보들을 걷어내고 정리한 TypeScript 기반 예시 코드입니다. 설정을 타입으로 꽉 짜놓고, 화면에서는 helper 함수를 거쳐서 확인하는 방식입니다.

TypeScript
 
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는 반드시 데이터로 제어

앱마다 첫 화면이 다를 때가 많습니다. 어떤 앱은 홈이 먼저고, 어떤 앱은 리스트나 지도가 먼저 뜹니다. 네비게이션 공통 코드에 시작 라우트를 박아두지 말고 아래처럼 레지스트리 값을 읽도록 처리합니다.

TypeScript
 
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: 외부 브라우저 호출 차단 + 인앱 링크 허용 목록 검증 제외

새 프로파일 추가 절차 및 테스트 기준

새로운 앱 설정을 추가할 때는 코드부터 무작정 건드리지 말고 아래 순서대로 체크리스트를 밟는 게 실수를 줄이는 지름길입니다.

  1. profileId, applicationId, displayName 정의
  2. 공통 기능 계열 분류 (engine, group) 선택
  3. 첫 화면으로 사용할 initialRoute 지정
  4. 기능 플래그(features) 및 필요한 권한(permissions) 명시
  5. 공개 가능한 서비스 약관, 고객지원 링크 매핑
  6. selectedProfiles 등을 활용해 단일 프로파일 빌드 및 설치 테스트 진행
  7. 기존 대표 프로파일 회귀 테스트(Smoke Test) 실행

통합 앱 특성상 전체 프로파일을 매번 다 빌드해보긴 어렵습니다. 따라서 설정을 건드린 후에는 최소한 같은 group에 묶여 있는 대표 프로파일 하나쯤은 같이 띄워보고 기능이 안 깨졌는지 점검하는 습관이 중요합니다.

실무 운영 시 주의할 점 (자주 하는 실수)

  • 민감 정보 분리 (가장 중요) 레지스트리 파일은 개발자들이 자주 열어보고 수정하는 파일입니다. 여기에 운영 서버 URL, 관리자 전용 토큰, API 키, AdMob 광고 ID, keystore 비밀번호 같은 민감 정보를 같이 때려 박으면 보안상 아주 위험합니다.
    • 해당 정보들은 반드시 환경변수(.env)나 별도의 빌드 시크릿 매핑 키로 분리하고, 레지스트리에는 공개 가능한 앱 구성 데이터만 남겨야 합니다. 저처럼 블로그나 문서에 예제를 올릴 때도 실값이 노출되지 않도록 각별히 조심하세요.
  • 규모에 맞는 관리 방식 선택 앱 개수에 따라 관리 방식을 조금씩 고도화하는 것이 좋습니다. 처음부터 무리하게 자동화할 필요는 없습니다.
앱 규모추천 방식이유
1~3개 TypeScript 단순 상수 파일 구조가 한눈에 들어오고 빠르게 수정 가능
4~10개 TS 레지스트리 + Validator 함수 빌드 전 필드 누락이나 오타(잘못된 route 등) 검증 가능
10개 이상 Catalog 파일 (JSON/YAML) + 빌드 스크립트 반복되는 방대한 설정을 표 형태로 뽑아서 관리하기 유리
 

마무리

코드 곳곳에 if (isTargetApp) 식의 분기문이 쌓이기 시작했다면 이미 신호가 온 겁니다. 더 지저분해지기 전에 작은 레지스트리 구조를 만들고, 필수 필드가 비어있으면 빌드 전에 에러를 뱉어주는 간단한 Validator부터 붙여보세요. 설정을 데이터로 바라보는 순간부터 새 앱을 추가할 때 생기는 피로감이 확 줄어듭니다.