RippleTrip | React Native에 웹뷰 띄우기

사공광열·2025년 3월 28일

저희팀은 웹뷰를 선택했어요

RippleTrip 앱의 핵심 기능은 AI 프롬프트를 활용한 사진 편집입니다.
사용자는 여행 중 찍은 사진에서 특정 영역을 브러시로 선택하고,
그 영역에 텍스트 프롬프트를 입력해 객체를 생성하거나 제거할 수 있습니다.

또한, 카카오 소셜 로그인을 지원하기 위해 웹 기반 인증 과정을 거쳐야 했는데,
로그인 후 다시 앱으로 리다이렉션되는 구조가 사용자 입장에서 다소 불편했습니다.

게다가 사용자가 여행지 정보를 쉽게 탐색할 수 있도록
TripAdvisor의 여행지 추천 데이터를 보여주는 기능도 필요했습니다.
이 기능은 웹과 모바일 환경 모두에서 동일한 UX로 제공되어야 했기 때문에
처음에는 React로 웹 애플리케이션을 구현했습니다.

하지만 모바일 환경에서는 웹 링크를 누를 때마다 외부 브라우저가 열리는 문제가 있었고,
이로 인해 사용자 입장에서 생각을 해 봤을때 앱과 웹이 분리되어 있다는 느낌을 받았습니다.

그래서 앱 안에서 바로 웹 페이지를 띄울 수 있는 방법이 없을까? 고민하다가
저희는 웹뷰 라는 기술을 발견하게 되었습니다.
웹뷰를 사용하면 앱 내부에 웹 페이지를 임베드하여
사용자가 별도의 브라우저를 열지 않고도 자연스럽게 웹 기능을 사용할 수 있었습니다.

웹뷰의 동작 원리

웹뷰는 네이티브 앱 내부에서 웹 페이지를 렌더링할 수 있게 해주는 컴포넌트입니다.
즉, 브라우저를 열지 않아도 앱 내부에서 HTML, CSS, JavaScript로 구성된 웹 콘텐츠를 그대로 보여줄 수 있습니다.

기본 구조

  • React Native에서는 react-native-webview 패키지를 사용합니다.
  • 내부적으로 네이티브 수준에서 Android의 WebView, iOS의 WKWebView를 호출합니다.
  • 웹 콘텐츠는 네이티브 계층에서 렌더링되어, 앱 안에 독립된 브라우저 환경이 만들어집니다.

렌더링 과정

  1. React Native에서 <WebView /> 컴포넌트가 렌더링됨
  2. source 속성에 지정된 uri를 네이티브 WebView 엔진이 로드
  3. 해당 URL의 웹 리소스(HTML, CSS, JS 등)가 다운로드 및 해석됨
  4. 앱 내부에 임베드된 작은 브라우저 엔진이 웹 페이지를 화면에 표시

네이티브 ↔ 웹 간 통신

이전에는 앱과 웹 사이에 데이터를 주고받아야 정보가 있어서,

WebView의 양방향 통신 구조를 직접 공부하여 적용하였지만.

지금은 해당 기능이 필요하지 않아 제거했습니다.

그때 WebView의 통신 원리를 공부하면서 내부 동작을 이해할 수 있었습니다.

WebView는 단순히 웹 화면을 보여주는 역할을 넘어,

앱과 웹이 서로 데이터를 주고받을 수 있는 브릿지 역할을 합니다.

React Native 쪽에서는 onMessage, injectedJavaScript, injectJavaScript() 같은 API를 통해

이 통신을 제어할 수 있습니다.

(참고: React Native WebView 공식 문서)


웹 → 앱

웹에서 React Native로 데이터를 보낼 때는

window.ReactNativeWebView.postMessage(data) 를 사용합니다.

이 코드를 실행하면 WebView 내부의 네이티브 브릿지를 통해

onMessage 이벤트 핸들러로 메시지가 전달됩니다.

<WebView
  source={{ uri: "https://example.com" }}
  onMessage={(event) => {
    console.log("웹에서 받은 메시지:", event.nativeEvent.data);
  }}
/>
// 웹 페이지 내부 코드
window.ReactNativeWebView.postMessage("AI 편집 완료!");

웹 쪽에서 postMessage()를 호출하면,

React Native의 onMessage()가 실행되어 데이터를 받을 수 있습니다.

이 방법은 주로 웹 내 이벤트 발생 시 앱에서 처리해야 하는 경우(예: 결제 완료, 로그인 성공)에 사용됩니다.


앱 → 웹

반대로 React Native에서 웹으로 명령이나 데이터를 전달할 때는

injectedJavaScript 또는 injectJavaScript()를 사용합니다.

  • injectedJavaScript : 웹 페이지가 로드될 때 자동으로 실행되는 코드
  • injectJavaScript() : 이미 로드된 웹뷰에서 특정 시점에 실행할 코드
const webRef = useRef<WebView>(null);

<WebViewref={webRef}
  source={{ uri: "https://example.com" }}
  injectedJavaScript={`
    window.ReactNativeWebView.postMessage("웹이 로드되었습니다");
    true;
  `}
/>

<Buttontitle="웹으로 데이터 보내기"
  onPress={() => {
    webRef.current?.injectJavaScript(`
      alert("React Native에서 보낸 데이터입니다!");
      true;
    `);
  }}
/>

이렇게 하면 앱에서 웹 쪽의 전역 스코프(window)에 접근해

DOM 조작, 함수 호출 등 다양한 상호작용이 가능합니다.

(참고: WebView props – injectedJavaScript / onMessage)

다만, 제 경우에는 웹이 완전히 로드된 상태에서 정보를 주고받는 시나리오가 대부분이었기 때문에,
onLoad 시점에서 postMessage를 활용하여 데이터를 송신하는 방식을 주로 사용했습니다.
이 접근 방식은 불필요한 초기 통신을 줄이고,
웹이 렌더링된 이후 안정적인 데이터 전달을 보장한다는 장점이 있습니다.


통신 구조

┌──────────────────────────────┐
│     React Native (App)       │
│                              │
│  injectJavaScript() ─────▶ 웹 내부 JS 실행
│                              │
│  onMessage(event) ◀───── window.ReactNativeWebView.postMessage(data)
│                              │
└──────────────────────────────┘
  • 웹 → 앱: window.ReactNativeWebView.postMessage()
  • 앱 → 웹: injectJavaScript() 또는 injectedJavaScript
  • 두 방식이 결합되어 완전한 양방향 통신 구조를 형성합니다.

실제 적용 사례

RippleTrip 앱에서는 WebView를 단순한 웹 표시용이 아니라,

로그인, 여행지 정보 탐색, AI 편집 기능을 모두 하나의 앱 안에서 자연스럽게 통합하기 위해 사용했습니다.

소셜 로그인 (LoginScreen)

카카오 로그인은 웹 기반 OAuth 인증 구조를 사용했기 때문에,

앱 내부에서 WebView를 띄워 로그인 과정을 처리했습니다.

로그인이 완료되면 백엔드가 redirectUri로 토큰을 반환하고,

onNavigationStateChange로 해당 리다이렉션을 감지해 로그인 완료 상태로 전환합니다.

<WebView
  source={{
    uri: `${AXIOS_BASE_URL}/auth/kakao/login?redirectUri=${encodeURIComponent(
      REDIRECT_URI
    )}`,
  }}
  onNavigationStateChange={(navState) =>
    handleNavigationStateChange({
      navState,
      setShowWebView,
      setError,
      router,
      setUser,
    })
  }
  style={{ flex: 1 }}
/>

이 접근 방식 덕분에 사용자는 앱을 이탈하지 않고도

카카오 인증 과정을 자연스럽게 마칠 수 있었습니다.


여행지 정보 화면 (WebViewScreen)

사용자가 선택한 여행지를 웹 페이지로 띄워주는 화면에서는

외부 API(TripAdvisor)의 데이터를 렌더링하는 웹 페이지를 WebView에 표시했습니다.

React Native의 Suspense와 Skeleton 컴포넌트를 함께 사용해

웹 콘텐츠 로딩 중에도 깔끔한 UX를 유지했습니다.

<Suspense fallback={<WebViewSkeleton />}>
  <WebViewsource={{ uri: url }}
    mediaPlaybackRequiresUserAction
    allowsInlineMediaPlayback={false}
    javaScriptEnabled
    domStorageEnabled
    startInLoadingState
  />
</Suspense>

이를 통해 사용자는 별도의 브라우저 전환 없이

앱 내에서 여행지 상세 정보를 바로 확인할 수 있습니다.


AI 이미지 편집 기능 (ImageView)

AI 프롬프트를 활용한 이미지 편집 도구는

이미 웹 버전으로 구현된 에디터를 그대로 재사용했습니다.

ImageView 화면에서는 단순히 WebView로 웹 에디터를 임베드해

웹과 앱의 코드를 공유했습니다.

<View style={{ flex: 1 }}>
  <Header />
  <WebView source={{ uri: WEB }} style={{ flex: 1 }} />
</View>

이 덕분에 별도의 네이티브 UI를 새로 만들지 않고도

웹과 동일한 편집 기능을 앱 안에서 사용할 수 있었습니다.


정리

적용 위치사용 목적핵심 포인트
LoginScreen카카오 OAuth 인증WebView 내 리다이렉션 감지로 로그인 처리
WebViewScreen여행지 정보 표시Suspense + Skeleton으로 로딩 UX 개선
ImageViewAI 편집 기능 재사용웹 기반 AI 에디터를 앱에 그대로 임베드

WebView를 통해 RippleTrip은

  • 로그인부터 이미지 편집까지 하나의 앱 안에서 완결된 사용자 경험을 제공하고,
  • 기존 웹 코드를 재사용해 개발 효율성을 높였으며,
  • 네이티브 컴포넌트와 웹 콘텐츠를 유기적으로 결합할 수 있었습니다.
profile
Interactive Developer

0개의 댓글