Frontend
앱 리뷰 왜 이렇게 받기 어려울까요
전성진··수정됨 2026.07.21

앱 리뷰, 왜 이렇게 받기 어려울까요
앱을 만들었어요. 사용자도 생겼어요. 그런데 스토어 리뷰가 없어요.
사용자에게 "리뷰 남겨주세요"라고 말하는 건 생각보다 어려운 일이에요. 외부 브라우저로 스토어를 열면 이탈률이 높고, 팝업을 직접 만들면 거부감을 줘요. 무엇보다 앱을 떠나는 순간 사용자의 맥락이 끊겨버리죠.
그래서 OS가 직접 제공하는 인앱 리뷰를 사용하기로 했어요.
인앱 리뷰가 뭔가요
인앱 리뷰는 앱을 떠나지 않고도 별점과 리뷰를 남길 수 있는 네이티브 UI예요.
iOS: StoreKit의
SKStoreReviewControllerAndroid: Google Play In-App Review API
OS가 직접 띄워주는 바텀시트이기 때문에, 사용자 경험이 자연스럽고 전환율도 높아요.
다만 한 가지 중요한 점이 있어요. OS가 노출을 제어한다는 거예요.
Android iOS 노출 제한 1회 노출 후 약 1개월 쿨다운 365일 내 최대 3회 개발자 제어 요청만 가능, 노출 보장 없음 요청만 가능, 노출 보장 없음
아무리 requestReview()를 호출해도, OS가 "지금은 아니야"라고 판단하면 아무 일도 일어나지 않아요. 이건 사용자를 보호하기 위한 설계예요.
어디서 띄울지가 핵심이에요
인앱 리뷰에서 가장 중요한 건 타이밍이에요.
저희 앱 '과천과학관'은 추천 동선 기능이 핵심이에요. 사용자가 관람 시간과 대상을 선택하면, AI가 최적의 관람 루트를 추천해줘요. 그리고 그 결과가 화면에 나타나는 순간 — 사용자는 "오, 괜찮은데?" 라는 감정을 느끼죠.
바로 이 순간이에요.
사용자가 시간·대상 선택
→ API 호출
→ 추천 동선 결과 렌더링
→ ✨ 여기서 리뷰 요청
결과를 보고 만족감을 느끼는 그 찰나에 리뷰를 요청하면, 자연스럽게 긍정적인 리뷰로 이어질 가능성이 높아요.
전체 코드, 27줄
유틸 함수
// screens/utils/inAppReview.js
import InAppReview from 'react-native-in-app-review';
import AsyncStorage from '@react-native-async-storage/async-storage';
const REVIEW_REQUESTED_KEY = 'HAS_REQUESTED_REVIEW';
export const requestInAppReview = async () => {
try {
const hasRequested = await AsyncStorage.getItem(REVIEW_REQUESTED_KEY);
if (hasRequested === 'true') {
return; // 이미 요청했으면 조용히 종료
}
const isAvailable = InAppReview.isAvailable();
if (!isAvailable) {
return; // 에뮬레이터, 미지원 OS 등
}
await InAppReview.RequestInAppReview();
await AsyncStorage.setItem(REVIEW_REQUESTED_KEY, 'true');
} catch (error) {
console.log('[InAppReview] 에러:', error);
}
};
간단하죠? 하지만 이 27줄 안에 세 가지 방어 로직이 들어 있어요.
1. 중복 요청 방지
const hasRequested = await AsyncStorage.getItem(REVIEW_REQUESTED_KEY);
if (hasRequested === 'true') return;
OS가 자체적으로 쿨다운을 관리하지만, 앱 레벨에서도 한 번만 요청하도록 했어요. 불필요한 네이티브 브릿지 호출을 줄이고, 의도를 코드에 명확히 담기 위해서예요.
2. 환경 체크
const isAvailable = InAppReview.isAvailable();
if (!isAvailable) return;
에뮬레이터, 구버전 OS, 테스트 환경에서는 조용히 넘어가요. 사용자는 아무것도 모르고, 앱은 크래시 없이 동작해요.
3. 조용한 실패
catch (error) {
console.log('[InAppReview] 에러:', error);
}
리뷰 요청은 실패해도 괜찮은 기능이에요. 에러가 나도 사용자에게 알릴 필요가 없어요. 토스트도, 얼럿도 없이 그냥 넘어가면 돼요.
트리거하는 코드는 딱 4줄
// RouteResultScreen.js
useEffect(() => {
if (data) {
requestInAppReview();
}
}, [data]);
RTK Query의 useGetRecommendedRouteQuery가 데이터를 반환하는 순간, 리뷰를 요청해요. data가 있다는 건 API 호출이 성공했다는 뜻이고, 화면에 추천 동선이 그려지고 있다는 뜻이에요.
설치와 네이티브 설정
npm install react-native-in-app-review
cd ios && pod install
Android는 자동 링킹 덕에 추가 설정이 없어요.
iOS는 pod install 후, Xcode 빌드 설정에서 OTHER_LDFLAGS를 배열 형식으로 정리해줬어요.
고민했지만 하지 않은 것들
"N회 사용 후 리뷰 요청" 같은 조건부 로직
처음에는 "앱을 3번 이상 사용한 사용자에게만"같은 조건을 넣으려 했어요. 하지만 어차피 OS가 노출을 제어하기 때문에, 앱에서 복잡한 조건을 걸어봐야 의미가 없었어요. OS가 알아서 "이 사용자에게 지금 띄워도 되는지"를 판단해줘요.
커스텀 리뷰 팝업
"별 5개면 스토어로, 낮으면 피드백 폼으로" 같은 패턴을 본 적 있을 거예요. Apple은 이걸 명시적으로 금지하고 있어요. 가이드라인을 위반하면 앱이 리젝될 수 있어요. 네이티브 인앱 리뷰만 사용하는 게 가장 안전해요.
성공/실패 콜백으로 분기 처리
RequestInAppReview()의 반환값으로 사용자가 실제로 리뷰를 남겼는지 알 수 있을 것 같지만, 실제로는 알 수 없어요. OS가 의도적으로 결과를 숨겨요. 사용자가 리뷰를 남기지 않았더라도 "성공"으로 반환되기 때문에, 결과에 의존하는 로직을 짜면 안 돼요.
정리
항목 선택 라이브러리 react-native-in-app-review 트리거 시점 추천 동선 결과 로딩 완료 중복 방지 AsyncStorage 플래그 에러 처리 조용한 실패 (silent fail) 조건부 로직 없음 (OS에 위임)
인앱 리뷰는 복잡한 기능이 아니에요. 언제 띄울지 결정하는 게 코드의 90%예요.
27줄의 유틸 함수, 4줄의 트리거 코드. 이게 전부예요. 나머지는 OS가 알아서 해줘요.
간단한 것을 간단하게 구현하는 게, 결국 가장 어려운 일이니까요.
읽기 도구
약 7분 읽기
이 글이 도움이 되었나요?