들어가며
앱스토어와 플레이스토어에 앱을 출시했다.


현재 앱 내 로그인을 제외한 90%의 기능이 웹에서 동작한다. 또한 위와같이 앱으로 서비스를 이용할 수도 있고, 웹에서도 이용이 가능하다. 앱 출시와 관련된 이야기는 다른 글에서 또 이야기를 해볼 예정이다.
기술 스택은 React Native 프레임워크인 Expo를 사용했다. React Native를 선택한 이유는 크로스 플랫폼 프레임워크이기 때문에 iOS와 Android를 동시에 개발할 수 있다는 점, 익숙한 React 생태계이며, 익숙한 언어인 JavaScript를 이용해서 개발할 수 있기 때문이었다. 그리고 Expo를 선택한 이유는 React Native 공식 문서에서도 Expo 사용을 권장하며, Expo에서 제공하는 다양한 SDK, 간편하게 빌드하고 출시할 수 있는 EAS, Next.js와 유사한 파일 기반 라우팅 등의 기능과 플랫폼을 활용하기 위함이다.
Expo로 앱 내에 웹뷰를 띄울 때 신경써야할 것들을 정리했다. 단순히 WebView 컴포넌트만 띄우면 되는 것 아닌가 싶었는데, 실제로 개발하면서 생각보다 많은 것들을 고려해야 했다. 웹과 앱의 경계에서 자연스러운 사용자 경험을 만들기 위해 필요한 설정들과 실제로 마주쳤던 문제들을 하나씩 담았다.
WebView 컴포넌트 기본 세팅
앱 내 웹뷰는 react-native-webview의 WebView 컴포넌트를 이용해서 띄웠을 것이다. 하지만 기본 설정만으로는 부족하다. 실제 서비스 수준의 앱을 만들려면 여러 옵션들을 추가로 설정해줘야 한다.
<WebView
ref={webViewRef}
source={{ uri: ENV.prodWebUrl }}
userAgent={WEBVIEW_USER_AGENT}
allowsBackForwardNavigationGestures
sharedCookiesEnabled
thirdPartyCookiesEnabled
webviewDebuggingEnabled
domStorageEnabled
pullToRefreshEnabled
originWhitelist={['*']}
onMessage={handleWebViewMessage}
onContentProcessDidTerminate={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.warn('WebView Content Process Did Terminate:', nativeEvent);
webViewRef.current?.reload();
}}
onError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.error('WebView Error:', nativeEvent);
}}
onHttpError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.error('WebView HTTP Error:', nativeEvent);
}}
/>
ref는 webView 컴포넌트에 접근하기 위한 ref 객체로, 웹뷰를 제어하거나 정보를 얻어올 때 사용한다. 웹뷰 내에서 뒤로 가거나, 새로고침하거나, 웹뷰에 메시지를 전달하는 등의 작업을 할 수 있다.source는 웹뷰에 로드할 URL이다. 웹에서 접근 가능한 어떤 URL이든 가능하다. 심지어 네이버도 웹뷰로 로드할 수 있다.userAgent는 웹뷰의 User-Agent 문자열을 지정하는데, 이걸 활용하면 앱의 버전이나 정보를 웹뷰에 전달할 수 있어서 나중에 유용하게 쓰인다.allowsBackForwardNavigationGestures는 iOS에서 스와이프로 뒤로/앞으로 이동하는 제스처를 허용하는 옵션이다. 이게 없으면 웹에서는 당연하게 되던 제스처가 앱에서 동작하지 않아 사용자가 답답함을 느낄 수 있다.sharedCookiesEnabled와thirdPartyCookiesEnabled는 쿠키 관련 설정이다. 전자는 네이티브와 쿠키를 공유할지, 후자는 Android에서 서드 파티 쿠키를 허용할지를 결정한다. 로그인 상태를 유지하려면 이게 꼭 있어야 한다.webviewDebuggingEnabled는 Android에서 웹뷰 디버깅을 활성화하는 옵션인데, 개발 중에는 반드시 켜두는 게 좋다.domStorageEnabled는 Android에서 localStorage와 sessionStorage를 사용할 수 있게 해준다.pullToRefreshEnabled는 당겨서 새로고침하는 기능을 활성화하는데, 앱 사용자들이 기대하는 일반적인 동작이다.
originWhitelist는 허용할 URL 패턴의 배열로,['*']로 설정하면 모든 URL을 허용한다. 보안이 중요하다면 특정 도메인만 허용하도록 설정할 수도 있다.onMessage는 웹뷰에서 앱으로 메시지를 전송할 때 호출되는 콜백 함수다. 웹과 앱의 통신에서 핵심적인 역할을 한다.onContentProcessDidTerminate,onError,onHttpError는 웹뷰에서 발생하는 여러 오류를 처리하는 콜백 함수들인데, 그중onContentProcessDidTerminate는 iOS에서 메모리 부족 같은 이유로 웹뷰 프로세스가 죽었을 때 알아서 다시 로드해준다.
webViewRef 관리하기
webViewRef는 앱 곳곳에서 웹뷰와 통신할 때 필요하다. 그래서 매번 props로 전달하는 것보다는 Context를 이용해서 전역에서 사용할 수 있도록 하는 방식을 추천한다.
// _layout.tsx
return (
<WebViewProvider>
<Slot />
</WebViewProvider>
)
// MainScreen.tsx
const { webViewRef, sendMessageToWeb } = useWebView();
// ..
return(
<WebView
// ..
ref={webViewRef}
/>
)
이렇게 두면 어디서든 useWebView 훅으로 webViewRef를 꺼내 쓸 수 있다.
웹뷰와 앱의 통신
웹뷰와 앱 간의 통신 방법에 대해서는 별도로 작성한 React Native와 WebView간에 메시지 전송하는 방법 글을 참고하면 된다. 양방향 통신 구조를 잘 설계해두면 웹과 앱의 기능을 유연하게 연동할 수 있다.
웹뷰에서 외부 링크를 인앱 브라우저로 열기
웹뷰에서 외부 링크를 열 때, 기존 웹뷰에서 열면 우리 서비스와 구분이 안된다는 단점이 있다. 사용자 입장에서도 헷갈릴 수 있고, 뒤로가기를 누르면 원래 서비스로 돌아가기 어려울 수도 있다. 또한, http프로토콜의 웹사이트를 웹뷰에서 열면 보안 에러를 보기도 한다. 그래서 외부 링크는 인앱 브라우저로 여는 게 좋다.
expo-web-browser를 사용하면 정말 간편하게 인앱 브라우저를 열 수 있다.
WebBrowser.openBrowserAsync('https://expo.dev');
웹뷰에서 인앱 브라우저를 열기 위해서는 웹뷰와 앱의 통신이 필요하다. 흐름은 이렇다.
- 사용자가 a 태그를 클릭하면, 웹뷰에서 앱으로 메시지를 전송한다. 이때 메시지 타입은
OPEN_BROWSER로 하고, url을 payload에 담아 전달한다.
const handleClick = (e: MouseEvent) => {
const link = (e.target as HTMLElement).closest('a');
if (link && link.href) {
e.preventDefault();
sendMessageToRN({
type: 'OPEN_BROWSER',
payload: { url: link },
});
}
};
- 앱에서는
OPEN_BROWSER메시지를 받으면 payload의 url을 확인해서WebBrowser.openBrowserAsync메서드로 인앱 브라우저를 연다.
// ..
case 'OPEN_BROWSER':
if (message.payload?.url) {
WebBrowser.openBrowserAsync(message.payload.url);
}
break;
그런데 만약 내가 직접 추가한 a 태그가 아니라, 뉴스레터 같은 외부 콘텐츠 안의 링크도 인앱 브라우저로 열고 싶다면 어떻게 할까? 실제로 우리 서비스에서는 뉴스레터 내의 모든 a 태그를 인앱 브라우저로 열도록 구현했다. useEffect를 통해 DOM이 렌더링된 이후에 동작하도록 했고, click 이벤트 리스너를 달았으며 e.target.closest('a')로 a 태그인지 체크했다.
useEffect(() => {
const contentEl = contentRef.current;
if (!contentEl) return;
const handleClick = (e: MouseEvent) => {
const link = (e.target as HTMLElement).closest('a');
if (link && link.href) {
e.preventDefault();
openExternalLink(link.href);
}
};
contentEl.addEventListener('click', handleClick);
return () => contentEl.removeEventListener('click', handleClick);
}, [contentRef]);
이렇게 하면 뉴스레터 안의 모든 링크가 자동으로 인앱 브라우저로 열린다.
앱 버전에 따른 기능 분기 처리
앱과 웹이 서로 통신을 해야 해서, 둘을 동시에 배포해야 하는 경우가 있다. 실제로 위에서 이야기한 인앱 브라우저 기능을 배포할 때 이 문제를 겪었다. 앱을 출시하려고 할 때 현재 사용자들은 구버전의 앱을 사용하고 있지만, 앱을 심사하는 사람은 최신 버전의 앱을 사용한다. 이 둘을 모두 지원하려면 현재 앱이 구 버전인지 최신 버전인지 알아야 했다.
이때 WebView 컴포넌트의 userAgent를 활용해서 버전 정보를 웹뷰에 전달했다.
// 웹
export const openExternalLink = (link: string) => {
const inAppBrowserUpdated = navigator.userAgent.includes('bombom/1.0.2');
if (isWebView() && inAppBrowserUpdated) {
sendMessageToRN({
type: 'OPEN_BROWSER',
payload: { url: link },
});
} else window.open(link, '_blank', 'noopener,noreferrer');
};
앱이 최신 버전인지 체크해서 맞다면 앱에 메시지를 전달하고, 아니라면 웹에서 직접 새 창을 연다. 앱에서는 알 설정한다.
// MainScreen.tsx
return (
<WebView
// ..
userAgent="bombom/1.0.2"
/>
)
앱 버전마다 동작을 다르게 줘야 할 때 이 방법을 쓴다.
소셜 로그인 구현
소셜 로그인을 구현하며 겪은 트러블 슈팅은 별도로 작성할 예정이다. 자세한 내용은 소셜 로그인을 웹뷰에서 RN으로 마이그레이션하며 글과 관련 PR을 참고 바란다.
웹같은 경험 제거하기
텍스트 선택 방지
휴대폰에서 웹사이트 텍스트를 꾹 누르면 아래 이미지처럼 선택이 된다. 웹사이트라면 사용자도 크게 신경 쓰지 않겠지만, 앱을 만들고 있다면 이런 웹스러운 경험을 없애고 싶을 것이다.
이는 CSS로 간단하게 제거할 수 있다.
html {
-webkit-tap-highlight-color: rgb(0 0 0 / 0%); /* 모바일에서 탭할 때 파란 하이라이트 효과 제거 */
-webkit-touch-callout: none; /* iOS에서 텍스트 길게 눌렀을 때 나오는 기본 컨텍스트 메뉴 비활성화 */
user-select: none; /* 텍스트 드래그 및 선택 비활성화 */
}
물론 복사가 필요한 텍스트 영역은 별도로 기본값을 설정하는 CSS를 추가하면 된다.
a {
-webkit-tap-highlight-color: rgb(0 0 0 / 10%);
-webkit-touch-callout: default;
user-select: text;
}
핀치 줌 방지
앱에서 웹처럼 확대/축소가 되면 어색하다. 이를 제거하려면 meta 태그와 CSS를 함께 설정해야 한다.
<!-- index.html -->
<meta
name="viewport"
content="initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=no;"
/>
/* index.css */
html {
touch-action: pan-x pan-y;
}
touch-action: pan-x pan-y는 수평/수직 스크롤은 허용하면서도 확대/축소는 막는다.
Android 뒤로가기 처리
iPhone에서 뒤로가기를 하면 웹 내에서 뒤로가기가 된다. 하지만 Android에서 뒤로가기 버튼을 누르면 웹에서 뒤로가기를 할 수 있음에도 불구하고 앱이 바로 종료되는 현상이 있다. 이를 해결하려면 Android 기기에서 뒤로가기를 눌렀을 때 웹에서 뒤로가기가 가능하다면 웹에서 뒤로가기를 하도록 설정해야 한다.
- 먼저 WebView 컴포넌트의
onNavigationStateChange에 이벤트 핸들러를 연결한다.
const [canGoBack, setCanGoBack] = useState(false);
const handleNavigationStateChange = useCallback(
(navState: WebViewNavigation) => {
setCanGoBack(navState.canGoBack);
},
[],
);
return (
<WebView
onNavigationStateChange={handleNavigationStateChange}
/>
)
이 핸들러는 canGoBack 상태를 웹뷰의 현재 상태와 동기화한다.
- 그다음
BackHandler를 이용해서 뒤로가기 버튼 동작을 커스터마이징한다.
useEffect(() => {
if (Platform.OS !== 'android') return;
const onBackPress = () => {
if (canGoBack) {
webViewRef.current?.goBack();
} else {
Alert.alert('앱 종료', '앱을 종료하시겠습니까?', [
{ text: '취소', style: 'cancel' },
{
text: '종료',
style: 'destructive',
onPress: () => BackHandler.exitApp(),
},
]);
}
return true; // 뒤로가기 이벤트를 여기서 처리했으므로 true 반환
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => backHandler.remove();
}, [canGoBack, webViewRef]);
뒤로갈 수 있는지 여부를 체크해서, 뒤로갈 수 있다면 웹뷰에서 뒤로가기를 수행한다. 만약 뒤로갈 수 없다면, 즉 웹의 초기 화면이라면 '앱 종료' 확인 alert를 띄워서 사용자 의도를 확인한다.
Android http 프로토콜 접속 허용
앱 내 웹뷰가 https 프로토콜을 사용한다면 문제가 되지 않는다. 하지만 웹뷰에서 외부 링크로 이동하는데 그 링크가 http 프로토콜을 사용한다면 Android 기기에서 에러가 발생한다. 이는 Android 정책상 http 프로토콜 접속을 기본적으로 차단하기 때문이다.
이를 해결하려면 expo-build-properties를 설치해서 usesCleartextTraffic를 설정하면 된다.
// app.json
{
// ..
plugins: [
[
'expo-build-properties',
{
android: {
usesCleartextTraffic: true,
},
},
],
]
}
이렇게 웹뷰 내에서 http프로토콜의 외부 링크를 이동할 때 에러가 발생하지 않도록 할 수도 있고, 혹은 인앱 브라우저를 열도록 해서 해결할 수도 있다.
그 외 유용한 설정들
Expo와 React Native에서 제공하는 기능들로 앱에 필요한 여러 설정을 할 수 있다. 특히 이 두 가지는 거의 필수다.
- Expo SplashScreen은 앱을 실행했을 때 보여주는 스플래시 화면을 설정한다. 첫인상을 결정하는 중요한 부분이다.
- react-native-safe-area-context는 노치나 상태바 영역을 피해서 콘텐츠를 배치할 수 있게 해준다. 이게 없으면 중요한 UI 요소가 노치에 가려질 수 있다.
자세한 설정은 공식 문서를 참고하면 된다.
추가적인 SDK 및 라이브러리는 Expo SDK, Third-party libraries에서 찾아볼 수 있다.
유용한 팁들
Expo GO vs Native Build
Expo GO는 Expo에서 제공하는 개발용 앱으로, npx expo start 명령어로 실행할 수 있다. 네이티브 코드를 수정할 필요가 없는 작업을 할 때 Expo GO로 실행하면 빠르고 편하다.
반면 Native Build는 @react-native-google-signin/google-signin처럼 네이티브 코드 수정이 필요한 라이브러리를 사용할 때 필요하다. npx expo run:ios 또는 npx expo run:android로 실행한다.
처음에는 둘의 차이를 몰라서 헤맸는데, 네이티브 코드 수정 여부로 구분하면 쉽다.
Managed Workflow vs Bare Workflow
Expo에는 두 가지 개발 방식이 있다.
- Managed Workflow는 Expo가 네이티브 코드를 관리해주는 방식이다. expo-cli로 모든 걸 처리하고, 네이티브 코드(iOS/Android)를 직접 건드릴 필요가 없다. 모든 네이티브 설정은 app.json으로 관리하며, Expo GO로 바로 실행할 수 있다.
- Bare Workflow는 네이티브 코드에 직접 접근이 가능하며
android/,ios/폴더가 프로젝트에 포함된다. React Native 프로젝트처럼 동작하지만 Expo SDK는 계속 사용할 수 있다.
우리 팀은 Managed Workflow 방식으로 사용이 가능하다. 그래서 android/, ios/폴더를 프로젝트에 포함시키지 않으며(.gitignore에 추가), npx expo run:android로 네이티브 빌드를 할 때마다 새롭게 생성이 된다. 또한 처음에 네이티브 빌드를 하고, 이후에는 네이티브 기능을 수정하지 않았다면 npx expo start로 실행하면 빠르게 실행할 수 있다.
Expo GO 지원 여부 확인하기
라이브러리가 Expo GO를 지원하는지는 React Native Directory에서 검색해서 확인해볼 수 있다. 아래처럼 툴팁에 마우스를 올렸을 때 Expo GO에서 동작한다고 적혀있으면 Expo GO만으로도 실행이 가능하다.


라이브러리를 설치하기 전에 꼭 확인하는 습관을 들이면 나중에 삽질을 줄일 수 있다.
iOS 웹뷰 디버깅
iOS 디바이스 내 웹뷰를 디버깅하려면 Mac의 Safari를 실행한다. 상단 메뉴의 '개발자용'에서 원하는 기기를 선택하면 된다.
Simulator의 앱 내 웹뷰를 디버깅하려면 위 설정에서 찾을 수 있고, 실제 기기라면 PC에 케이블로 연결하면 된다. iOS에서 앱 내 웹뷰는 Safari 엔진으로 동작하기 때문에 Safari로만 디버깅이 가능하다.
Android 웹뷰 디버깅
Android 기기 내 웹뷰를 디버깅하려면 Chrome 브라우저를 사용한다. Chrome에서 chrome://inspect/#devices 주소로 접속하면 연결된 Android 기기 목록과 각 기기에서 열려 있는 웹뷰를 확인할 수 있다. inspect를 클릭하면 Chrome 개발자 도구가 열리며 웹뷰 내용을 디버깅할 수 있다.
iOS/Android 앱 디버깅
Simulator 혹은 Emulator에서 Command + D를 누르면 개발자 도구 메뉴가 나온다. 여기서 디버깅 모드를 활성화하거나, 리로드하거나, 성능 모니터를 켤 수 있다.
시뮬레이터에서 웹뷰에 로컬 서버 실행
iOS 시뮬레이터에서 웹뷰에 로컬 서버를 실행하기 위해서는 http://localhost:3000를 WebView의 uri에 넘겨주면 된다. 하지만 Android 에뮬레이터에서는 192.0.0.1:3000과 같이 ip와 포트 번호를 입력하면 된다.
트러블 슈팅
기능 구현은 비교적으로 쉬웠다. 공식 문서에 잘 나와있기 때문이다. 앱 개발에서 가장 어렵다고 느낀 건 트러블 슈팅이었다. 처음 접하는 네이티브 환경이었기에 상당히 애먹었다. 조금만 수정하면 에러가 발생하고, 브랜치를 이동하면 다시 빌드해야 했다. 이 빌드는 웹과 달리 1분에서 3분까지 걸리기도 했다. 또한 좀 전에 빌드가 되던 코드가 변경한 것도 없는데 빌드가 안 되기도 하고, 빌드는 되는데 흰 화면만 뜨기도 했다.
아래는 그렇게 Expo 기반 앱 내 웹뷰를 개발하며 겪은 트러블 슈팅들이다.
pod install 실행 시 CocoaPods could not find compatible versions for pod "SocketRocket":
이전에 React Native 개발을 할 때 ios에서 pod install을 하던 경험을 떠올리며 pod install을 했을 때 마주한 에러이다. 하지만 Expo에서는 iOS의 패키지를 설치하는 pod install 명령어를 직접 실행하는 걸 권장하지 않는다. npx expo run:ios로 네이티브 빌드를 실행하면 알아서 필요한 패키지가 설치된다. 이는 Expo가 iOS의 빌드 과정을 관리하고, 의존성을 자동으로 처리해주기 때문이다.
XCode Archive 시 PIF transfer session 에러
XCode로 Archive를 만들 때 이런 에러가 발생한다면, 빌드 캐시를 정리해주면 해결된다.
- XCode 종료
- 캐싱 폴더 제거:
~/Library/Developer/Xcode/DerivedData
터미널로 제거하려면 다음 명령어를 사용한다:
rm -rf ~/Library/Developer/Xcode/DerivedData
- finder에서 Command + Shift + G를 눌러
~/Library/Caches/org.swift.swiftpm로 이동 - 우클릭 > 패키지 내용 보기
- repositories폴더와 repositories.lock 파일 제거
- XCode 재실행
이 에러는 XCode의 빌드 시스템이 내부적으로 프로젝트 정보를 관리하는 PIF(Project Information File) 세션에 문제가 생겼을 때 발생한다. 캐시를 정리하면 XCode가 처음부터 다시 프로젝트 정보를 빌드하면서 해결된다.
Android Emulator 실행 실패
expo 실행 후 Android를 선택했을 때 The emulator (Pixel_6_API_32) quit before it finished opening. 에러가 발생한다면
- Android Studio 실행
- Tools > Device Manager 클릭
- 디바이스를 선택해 실행
만약 디바이스가 없다면 새로 추가한다. 혹은 재생 버튼을 눌러 디바이스를 실행했을 때 제대로 실행이 안 된다면, 해당 디바이스를 제거하고 새로 추가해준다.
Apple 로그인 구현 시 "Apple login error"
애플 로그인을 구현하다가 "Apple login error"라는 에러 메시지를 만났다. 이 에러는 XCode에서 Sign in with Apple capability를 추가하지 않아서 발생한다.
해결 방법: XCode에서 Runner > Targets > Signing & Capabilities > +Capability > Sign in with Apple 추가
Google Sign-in 라이브러리 인식 실패
iOS 실행 시 TurboModuleRegistry.getEnforcing(...): 'RNGoogleSignin' could not be found. Verify that a module by this name is registered in the native binary. 에러가 발생한다면, 이는 네이티브 빌드를 하지 않아서 발생하는 문제다.
@react-native-google-signin/google-signin은 Expo GO를 지원하지 않는 라이브러리다. 네이티브 코드 링크가 필요하기 때문에 개발 빌드(development build)나 프로덕션 빌드를 만들어야 한다. 그래서 네이티브 빌드(npx expo run:ios 또는 npx expo run:android)를 먼저 해야 한다.
이런 라이브러리들은 보통 공식 문서에 "Not compatible with Expo GO" 같은 안내가 있으니, 라이브러리를 도입하기 전에 Expo GO 지원 여부를 확인하는 것이 좋다. 만약 Expo GO에서 테스트가 필요하다면, 네이티브 모듈이 없는 대체 라이브러리를 찾거나 개발 빌드를 만들어서 테스트해야 한다.
EAS Build 권한 오류
eas build 시 You don't have the required permissions to perform this operation. 에러가 발생한다면
이는 app.json에 적혀있는 projectId에 해당하는 프로젝트에 대한 권한이 없어서 발생하는 이슈다. Expo 웹사이트에서 해당 프로젝트에 초대를 받으면 해결된다.
Android 네트워크 연결 오류
Android를 실행할 때 {"canGoBack": false, "canGoForward": false, "code": -2, "description": "net::ERR_NAME_NOT_RESOLVED", "loading": false, "target": 22, "title": "", "url": ".."}와 같은 에러가 발생한다면:
간헐적으로 발생하는 이슈로, 네트워크 환경 문제일 가능성이 크다. 핫스팟으로 연결하니 해결됐다. 지금은 핫스팟이 아니어도 실행이 잘된다.
혹은 아래처럼 웹뷰가 로딩되지 않는 에러를 만났을 때도 핫스팟으로 연결하면 해결되는 경우가 많다.
회사 네트워크나 특정 Wi-Fi 환경에서 개발 서버 접속이 막히는 경우가 있는데, 이럴 때 모바일 핫스팟을 활용하면 유용하다.
Expo SDK 버전 불일치
Expo 버전을 올린 후, Expo SDK의 버전을 이에 맞게 업데이트하지 않으면 빌드 오류가 발생한다. 이런 경우 npx expo install --check로 Expo SDK의 버전을 현재 SDK 버전에 맞게 자동으로 업데이트하면 된다.
npx expo install --check
이 명령어는 package.json을 분석해서 현재 Expo 버전과 호환되지 않는 패키지들을 찾아 자동으로 업데이트해준다. Expo 버전을 올릴 때마다 습관적으로 실행하는 게 좋다.
맺으며
웹뷰 기반의 앱을 개발하며 정말 수없이 많은 에러를 겪었다. 조금 과장을 보태서 올해 중 앱 개발 이전에 만난 에러들보다 앱 개발을 하며 만난 에러가 더 많을 정도이다.
문득 처음 웹을 개발하던 때가 떠올랐다. 디버깅할 줄도 몰라서 같은 시도만 반복하고, 한없이 구글링을 하던 그때가 말이다. 웹을 개발하다 보니 개발자 도구의 콘솔, 네트워크 탭 등을 활용한 다양한 디버깅 방법을 알게 되었고, 웹, React.js, HTTP, Webpack의 개념과 동작 방식을 이해하게 되었다. 많은 경험이 쌓이면서 이제는 웹 생태계에서 자주 만나는 문제들을 어렵지 않게 해결할 수 있게 되었다. 웹 생태계는 익숙하지만, 앱 생태계는 거의 처음이다 보니 쉽지 않았다. 언제 어떤 빌드를 해야 하는지, 디버깅은 어떻게 해야 하는지조차 몰랐다. 이번에 앱을 개발하며 겪은 이슈들과 배운 점들을 이 글에 모두 담으려고 노력했다. 내가 겪었던 시행착오를 다른 분들은 덜 겪으셨으면 하는 마음에서다.
아직 사용하는 기술들의 동작 원리나 앱의 내부 동작 방식에 대해서는 모르는 것이 많다. 하지만 이번 경험을 통해 웹 생태계에만 갇혀있지 않고 다른 생태계도 배워보고 싶다는 생각이 강력하게 들었다. 왜냐하면 재미있었기 때문이다. 웹 생태계를 벗어나 앱 생태계에 푹 빠져보는 시간이 너무나 즐거웠다. 마치 어릴 때부터 살던 본가를 떠나 여행을 떠난 기분이랄까.
웹에서의 동작 방식, 원리, 디버깅 등의 생태계를 한 번 학습하고 나니 확실히 앱 생태계를 학습하는 데는 비교적 적은 시간이 들었다. 디버깅 도구를 여는 단축키가 F12에서 Command + D로, 빌드 도구가 Webpack에서 Metro로, 배포처가 S3 + CloudFront에서 앱스토어/플레이스토어로 바뀌었다. 도구는 달라졌지만 문제 해결 방식은 그대로였다. 웹에서 문제를 해결하고 소셜 로그인을 구현하던 방식을 앱에서도 그대로 적용했을 뿐이다.
재미있는 경험이었다. 그리고 앞으로도 다양한 개발을 해보고 싶다.