개발 노트/React Native

[React Native] 통합 앱에서 selectedProfiles로 빌드 대상을 관리하는 방법

pposooj 2026. 5. 19. 15:45

React Native 통합 앱에서 selectedProfiles로 빌드 대상을 관리하는 방법

React Native로 여러 앱을 하나의 코드베이스에서 운영하다 보면 빌드 시간이 길어지고, 테스트 범위도 금방 커집니다. 이때 모든 프로파일을 매번 빌드하기보다 selectedProfiles 같은 빌드 옵션으로 이번 작업에 필요한 앱만 선택하는 구조를 둘 수 있습니다.

처음에는 빌드 옵션 하나만 추가하면 충분할 것 같았는데 정리해보니 selectedProfiles는 단순히 빌드 대상을 줄이는 옵션일 뿐, 앱 안에서 접근 가능한 화면이나 권한까지 자동으로 막아주는 기능은 아니었습니다. 이 부분을 구분하지 않으면 생각보다 쉽게 헷갈릴 수 있겠네요.

selectedProfiles는 이번 빌드에 어떤 앱을 포함할지 정하는 스위치에 가깝습니다. 보안 권한을 검증하거나 사용자 접근을 막는 기능은 아닙니다. 이 차이를 놓치면 빌드 대상 제한을 보안 정책처럼 착각할 수 있습니다.

반대로 메뉴와 라우팅은 열려 있는데 실제 빌드에는 포함되지 않는 어색한 상태가 생길 수도 있습니다. 그래서 selectedProfiles는 빌드 범위를 줄이는 도구로 보고, 화면 접근 제어나 API 권한 검증은 별도로 설계하는 편이 좋습니다.

이 글에서는 React Native 통합 앱에서 selectedProfiles로 빌드 대상을 관리할 때 필요한 구조를 정리해보겠습니다. 새 프로파일을 추가하거나 기존 프로파일을 나눠 빌드할 때 확인하면 좋은 기준을 중심으로 살펴보겠습니다.

이 글에서 다룰 문제

여러 앱을 하나의 React Native/Android 통합 프로젝트에서 관리하다 보면 다음과 같은 상황이 생길 수 있습니다.

  • 전체 프로파일을 한 번에 빌드하면 시간이 오래 걸린다.
  • 한 앱만 수정했는데 관련 없는 앱까지 빌드되어 피드백이 늦어진다.
  • 메뉴, 라우팅, 리소스, 권한은 프로파일별로 다르지만 빌드 대상 관리 기준이 없다.
  • release 빌드에서는 하나의 앱만 필요하지만 debug에서는 여러 앱을 확인해야 한다.
  • 특정 프로파일이 빌드에서 제외됐는데 화면 코드에는 여전히 노출 조건이 남아 있다.
  • selectedProfiles를 보안 권한처럼 오해해 실제 API나 관리자 기능 보호를 놓칠 수 있다.

이 문제는 단순히 Gradle 옵션 하나를 추가한다고 끝나지 않는 것 같습니다. 빌드 대상, registry, route guard, 테스트 범위를 같은 기준으로 묶어서 봐야 유지보수가 쉬워집니다.

selectedProfiles의 역할

selectedProfiles는 보통 Gradle 속성이나 스크립트 인자로 전달되는 빌드 범위 값입니다. 예를 들어 전체 프로파일 catalog가 있고, 그중 sampleTourBusan만 빌드하고 싶다면 아래처럼 전달할 수 있습니다.

./gradlew :app:assembleDebug -PselectedProfiles=sampleTourBusan

여러 개를 한 번에 확인해야 한다면 쉼표로 묶는 방식도 사용할 수 있습니다.

./gradlew :app:assembleDebug -PselectedProfiles=sampleTourBusan,sampleFileData

중요한 점은 이 값이 앱의 런타임 권한을 보호하는 장치가 아니라는 것입니다. selectedProfiles는 빌드 시점에 어떤 flavor, 리소스, 설정을 포함할지 좁히는 용도로 봐야 합니다.

사용자가 앱 안에서 어떤 화면을 볼 수 있는지, API를 호출할 수 있는지는 별도의 인증, 권한, 서버 검증으로 처리해야 합니다. 빌드 범위를 줄이는 일과 접근 권한을 막는 일은 서로 다른 문제라고 보는 편이 좋습니다.

기본 구조: catalog와 selectedProfiles 분리하기

통합 앱에서 가장 먼저 정리해야 할 것은 전체 프로파일 목록과 이번 빌드 대상 목록을 분리하는 것입니다. 이 둘을 섞어두면 나중에 앱이 원래 없는 것인지, 잠시 빌드에서 제외한 것인지 구분하기 어려워집니다.

구분 역할 예시
profile catalog 전체 앱 후보와 메타데이터 관리 profileId, applicationId, group, engine
selectedProfiles 이번 빌드에 포함할 프로파일 선택 sampleTourBusan
route guard 선택된 프로파일에서 접근 가능한 화면 제한 map, search, settings 조건
feature flag 기능 사용 여부 정의 지도, 검색, 설정, 외부 링크
smoke test scope 변경 영향 확인 범위 선택 프로파일 + 대표 프로파일

전체 catalog는 가능한 한 완전한 기준표 역할을 해야 합니다. 반대로 selectedProfiles는 그중 이번 빌드에 포함할 대상을 고르는 필터로 보는 편이 좋습니다.

처음에는 catalog와 선택 목록을 하나로 합쳐도 괜찮아 보일 수 있습니다. 하지만 프로파일이 늘어나면 잠시 제외된 앱과 실제로 삭제된 앱을 구분하기 어려워집니다. 그래서 기준표와 빌드 필터는 분리해두는 편이 더 안전한 것 같습니다.

예제 코드: selectedProfiles 파싱하기

아래 코드는 실제 프로젝트의 민감 정보를 제거하고 단순화한 예시입니다. Node.js 24 기준으로 실행 가능한 형태이며, 쉼표로 전달된 SELECTED_PROFILES 환경 변수를 파싱해서 전체 catalog에서 빌드 대상을 고릅니다.

// build-scope.mjs
const profiles = [
  {
    profileId: 'sampleTourBusan',
    applicationId: 'com.example.tour.busan',
    group: 'tour-regional',
    engine: 'tour',
    enabledForIntegration: true,
  },
  {
    profileId: 'sampleFileData',
    applicationId: 'com.example.filedata',
    group: 'file-data',
    engine: 'filedata',
    enabledForIntegration: true,
  },
  {
    profileId: 'legacyDisabledApp',
    applicationId: 'com.example.disabled',
    group: 'legacy',
    engine: 'hybrid',
    enabledForIntegration: false,
  },
];

function parseSelectedProfiles(value) {
  return new Set(
    String(value || '')
      .split(',')
      .map((item) => item.trim())
      .filter(Boolean),
  );
}

function resolveBuildProfiles(catalog, selected) {
  const enabledProfiles = catalog.filter((profile) => profile.enabledForIntegration);

  if (selected.size === 0) {
    return enabledProfiles.slice(0, 1);
  }

  const resolved = enabledProfiles.filter((profile) => selected.has(profile.profileId));
  const resolvedIds = new Set(resolved.map((profile) => profile.profileId));
  const unknownIds = [...selected].filter((profileId) => !resolvedIds.has(profileId));

  if (unknownIds.length > 0) {
    throw new Error(`Unknown or disabled profile: ${unknownIds.join(', ')}`);
  }

  return resolved;
}

const selected = parseSelectedProfiles(process.env.SELECTED_PROFILES);
const targets = resolveBuildProfiles(profiles, selected);

console.table(
  targets.map(({profileId, applicationId, group, engine}) => ({
    profileId,
    applicationId,
    group,
    engine,
  })),
);

실행 예시는 아래와 같습니다.

SELECTED_PROFILES=sampleTourBusan node build-scope.mjs

이 예제에서 중요한 부분은 세 가지로,

 

첫째, 전체 catalog에서 enabledForIntegration이 아닌 항목은 제외합니다.

둘째, 선택한 profileId가 없거나 비활성 상태라면 조용히 넘어가지 않고 에러를 냅니다.

셋째, 최종적으로 어떤 앱이 빌드 대상이 되었는지 출력합니다.

 

이 부분은 처음에는 사소해 보일 수 있지만 빌드 로그에서 대상 프로파일을 바로 확인할 수 있으면 실수를 훨씬 빨리 찾을 수 있습니다. 처음에는 로그 출력이 꼭 필요할까 싶을 수 있지만, 정리해보면 프로파일이 늘어날수록 확인 로그가 꽤 중요해지는 것 같습니다.

Gradle과 연결할 때의 흐름

Android Gradle 프로젝트에서는 -PselectedProfiles=... 형태로 값을 넘기고, Gradle 설정에서 이 값을 읽어 product flavor 생성이나 빌드 범위 제한에 사용할 수 있습니다.

val selectedProfilesProp = providers.gradleProperty("selectedProfiles").orNull

val selectedProfiles = selectedProfilesProp
    ?.split(',')
    ?.map(String::trim)
    ?.filter(String::isNotBlank)
    ?.toSet()
    ?: setOf(defaultProfileId)

그리고 catalog에서 읽은 전체 프로파일 중 선택된 항목만 flavor나 빌드 태스크 대상으로 연결합니다.

val buildTargets = profiles.filter { profile ->
    profile.enabledForIntegration && selectedProfiles.contains(profile.flavorName)
}

if (buildTargets.isEmpty()) {
    throw GradleException("No profiles selected for this build.")
}

처음 구현할 때는 선택한 프로파일이 없으면 첫 번째 프로파일을 기본값으로 사용하는 방식이 편할 수 있습니다. 하지만 release 빌드나 배포 자동화에서는 기본값이 오히려 위험할 수 있습니다. 실수로 다른 앱이 빌드될 수 있기 때문입니다.

그래서 운영용 빌드에서는 selectedProfiles를 명시하지 않으면 실패시키는 정책도 고려해볼 수 있습니다. 디버그 빌드와 릴리즈 빌드의 기본값을 꼭 같게 가져가기보다, 배포 실수를 줄이는 기준으로 나눠보는 것이 좋을 것 같습니다.

selectedProfiles와 route guard를 함께 봐야 하는 이유

selectedProfiles로 빌드 대상을 좁혀도 런타임 화면 조건이 자동으로 정리되는 것은 아닙니다. 예를 들어 관광 앱만 선택해서 빌드했는데 메뉴 registry에는 파일 데이터 앱 메뉴가 남아 있다면 사용자는 빈 화면이나 잘못된 라우트로 이동할 수 있습니다.

그래서 다음 기준을 함께 확인해야 합니다.

확인 항목 필요한 이유
빌드 대상 이번 APK/AAB에 어떤 프로파일을 포함할지 결정
첫 화면 선택된 프로파일의 initialRoute가 실제 존재하는지 확인
메뉴 노출 선택된 프로파일에 맞는 탭과 메뉴만 표시
기능 플래그 지도, 검색, 설정 같은 기능 사용 여부 확인
권한 요청 필요 없는 권한 요청이 발생하지 않도록 제한
빈 상태 화면 선택 프로파일에 데이터가 없을 때 깨지지 않게 처리

이 부분은 생각보다 자주 헷갈립니다. 빌드에서 제외했다는 사실과 화면에서 접근할 수 없다는 사실은 같은 말이 아닙니다. 빌드 스코프는 빌드 스코프대로, 라우팅과 권한은 앱 내부 정책대로 따로 검증해야 합니다.

처음에는 빌드 대상만 줄이면 화면도 자연스럽게 정리될 것처럼 느껴질 수 있습니다. 하지만 실제로는 메뉴 노출, 라우팅, 권한 요청, 빈 화면 처리까지 별도로 확인해야 하죠. 이 기준을 나눠두면 문제를 찾을 때 훨씬 덜 헤매게 됩니다.

환경별 빌드 대상 기준

환경별로 selectedProfiles를 다르게 쓰면 빌드 시간을 줄이고 검증 범위를 조절할 수 있습니다.

환경 추천 범위 이유
로컬 개발 수정 중인 단일 프로파일 빠른 피드백이 중요함
PR 검증 수정 프로파일 + 대표 프로파일 공통 코드 회귀를 확인해야 함
nightly 검증 group별 대표 프로파일 전체 검증 전 중간 안전망 역할
릴리즈 빌드 배포 대상 단일 프로파일 잘못된 앱 빌드 방지
전체 점검 전체 통합 대상 registry와 공통 코드 일괄 검증

단일 프로파일만 계속 확인하면 빠릅니다. 하지만 공통 컴포넌트 수정이 다른 앱을 깨뜨릴 수 있습니다. 그래서 로컬에서는 좁게, PR이나 배포 전에는 대표 프로파일을 함께 확인하는 방식이 현실적인 것 같습니다.

selectedProfiles를 보안 기능으로 오해하면 안 되는 이유

selectedProfiles는 빌드 대상을 제한할 뿐입니다. 다음과 같은 보안 처리는 별도로 필요합니다.

  • API 접근 권한은 서버에서 사용자 권한으로 검증해야 한다.
  • 관리자 URL은 앱에 하드코딩하지 않아야 한다.
  • 광고 ID, API 키, Firebase 설정값은 공개 저장소나 블로그 예제에 노출하지 않아야 한다.
  • 비활성 프로파일이라고 해서 서버 데이터 접근까지 자동으로 막히는 것은 아니다.
  • 앱 화면에서 숨긴 메뉴도 API 권한 검증을 대체하지 못한다.

특히 통합 앱에서는 프로파일별 설정 파일에 많은 값이 모이기 쉽습니다. 빌드 편의를 위해 만든 catalog에 서버 URL, 관리자 URL, 토큰, keystore 경로와 비밀번호를 같이 넣으면 나중에 공개 문서나 블로그 글로 정리할 때 유출 위험이 커집니다.

이 부분은 조금 귀찮더라도 처음부터 분리해두는 것이 좋습니다. 빌드 편의용 catalog와 비밀값 저장 위치가 섞이면, 나중에 공개 가능한 예제 코드로 바꾸는 과정에서도 실수하기 쉽습니다.

새 프로파일을 추가할 때 확인할 것

새 프로파일을 추가할 때는 아래 항목을 함께 확인하는 편이 좋습니다. 단순히 catalog에 한 줄 추가하는 것으로 끝내면 빌드, 라우팅, 권한, 테스트 중 어딘가에서 놓치는 부분이 생길 수 있습니다.

  • profile catalog에 profileId, applicationId, displayName, group, engine을 추가한다.
  • enabledForIntegration 상태를 명확히 정한다.
  • selectedProfiles로 단일 빌드가 가능한지 확인한다.
  • 선택한 프로파일의 첫 화면이 실제 route에 존재하는지 확인한다.
  • 메뉴와 기능 플래그가 선택 프로파일 기준으로 동작하는지 확인한다.
  • 필요 없는 권한 요청이 뜨지 않는지 확인한다.
  • 대표 프로파일 회귀 테스트를 함께 확인한다.
  • release 빌드에서는 배포 대상을 명시적으로 지정한다.
  • 빌드 로그에 최종 선택된 profileId가 출력되는지 확인한다.

자주 하는 실수

1. selectedProfiles를 단순 문자열로만 처리하고 검증하지 않는 경우가 있습니다. 오타가 있어도 그냥 빈 빌드가 되거나 기본 프로파일로 대체되면 문제를 늦게 발견하게 됩니다. 선택한 profileId가 catalog에 없으면 에러를 내는 편이 안전합니다.

2. debug와 release의 기본값을 똑같이 두는 경우도 있습니다. debug에서는 편의를 위해 첫 번째 프로파일을 기본값으로 둘 수 있습니다. 하지만 release에서는 명시적으로 선택하지 않으면 실패하도록 하는 것이 더 안전할 수 있습니다.

3. 빌드 대상 제한과 화면 노출 제한을 같은 것으로 보는 경우도 놓치기 쉽습니다. 빌드에서 제외해도 공통 화면 코드나 링크 처리 정책은 별도 검증이 필요합니다. 이럴 때는 빌드 스코프와 런타임 정책을 나눠서 보는 것이 좋습니다.

4. 민감 설정을 profile catalog에 섞는 경우도 있습니다. catalog는 여러 스크립트와 문서에서 참조되기 때문에 공개 가능한 메타데이터 중심으로 유지해야 합니다. 비밀값은 환경 변수나 안전한 설정 저장소로 분리하는 편이 낫습니다.

결론

React Native 통합 앱에서 selectedProfiles는 여러 앱을 한 코드베이스에서 관리할 때 빌드 범위를 줄여주는 유용한 기준입니다. 하지만 이것은 보안 기능이 아니라 빌드 스코프 관리 도구입니다. 이 점을 분명히 해두면 설계가 훨씬 안전해집니다.

좋은 구조는 전체 profile catalog를 기준으로 두고, selectedProfiles는 그중 이번 빌드 대상을 선택하는 필터로 사용하는 것입니다. 여기에 route guard, feature flag, 대표 프로파일 smoke test를 함께 연결하면 새 앱을 추가하거나 기존 앱을 수정할 때 실수를 줄일 수 있습니다.

처음부터 복잡한 자동화가 부담스럽다면 우선 세 가지만 적용해보는 것이 좋습니다. 선택된 profileId를 빌드 로그에 출력하기, 알 수 없는 profileId는 에러 처리하기, release 빌드에서는 배포 대상을 명시적으로 지정하기입니다.

이 세 가지만 있어도 통합 앱 운영에서 생기는 빌드 실수를 꽤 줄일 수 있을 것 같습니다. 결국 중요한 것은 이번 빌드에 무엇이 포함되는지, 그리고 그 기준이 화면과 테스트에도 일관되게 이어지는지 확인하는 일입니다.

참고 자료

  • Android Gradle Plugin product flavor 공식 문서
  • Gradle project property 사용 방식 관련 공식 문서
  • React Native 앱 설정과 네비게이션 구성 관련 공식 문서
  • 프로젝트 내부 profile registry, selectedProfiles, smoke test 문서