ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Expo, RN] 웹을 앱으로 배포하기, 웹뷰
    기타/앱 2025. 1. 24. 10:31
    728x90
    반응형

    [Expo, RN] 웹을 앱으로 배포하기

    회사에 일하다가, 웹을 앱으로 만들어야 하는 일이 생겼었다.

    네이티브로 앱을 만들 건 아니고, 그냥 웹을 앱으로 껍데기만 씌워서 만들 것이다.

    거기서 앱을 통해 접근한 것이 아닐 경우, 접근을 차단하는 방식을 사용해서 만들었다.

    오늘은 그 방법을 간략하게 적어놓을 것이다.

     

     

    1. 방식

    4가지 방식이 있다.
    웹앱, 네이티브앱, 하이브리드앱, 크로스플랫폼


    1 - 1. 웹앱

    웹앱은 모바일 화면에 맞게 개발된 웹페이지
    개발단계에서부터 모바일을 고려해서 홈페이지를 제작하였기 때문에, 
    다양한 모바일 기기에서 주소를 입력하더라도 그에 맞는 화면이 알맞게 보인다. 
    (요즘은 부트스트랩을 통해 쉽게 웹앱을 구현한다고 한다.) 

    하지만 웹앱은 순수 웹페이지기 때문에 휴대폰의 기능을 사용할 수 없으며, 
    스토어를 통한 앱 설치가 불가능하다. 
    즉 웹앱은 웹이기 때문에 브라우저에서 주소를 입력해야만 접속할 수 있다.


    1 - 2. 네이티브앱

    네이티브 앱은 모바일 기기에 최적화된 언어로 개발된 어플
    모바일 전용 개발 회사가 아니라면, 이용하지 않는다.

    보통 다른 방식으로 간단하게 출발했다가, 
    규모가 커지면 그때 네이티브앱으로 전환하는 방식이 많다.


    1 - 3. 하이브리드앱

    HTML, CSS, JS만으로도 사이트를 구축할 수 있음
    앱을 만들기 위해 네이티브 앱의 지식이 전혀 필요 없음
    하지만 자바스크립트의 한계로 인해 핸드폰만의 강력한 기능 구현에는 제한이 있다.


    1 - 4. 크로스플랫폼

    크로스 플랫폼은 네이티브 코드가 아닌 걸로 코딩하더라도 나중에 IOS Android가 이해할 수 있는 코드로 변환되는 것
    하이브리드앱에서 업그레이드된 개념

    근데 PC에 설치된게 윈도우인데, 윈도우는 ios 빌드가 안되서, 안드로이드밖에 못한다.
    일단 이 방식 채용



    2. 웹뷰

    네이티브나 크로스플랫폼 앱에 내재된 웹 브라우저

    일반 웹브라우저와 달리, 주소창, 새로고침, 즐겨찾기는 없고 그냥 페이지만 보여줌

    여러 플랫폼에서 사용 가능, 안드로이드, ios 등

    웹뷰를 사용하기 때문에, 
    앱을 업데이트하고 배포할 때 심사를 거치치 않고, 
    웹사이트의 수정된 내용이 바로 반영된다.

    단순히 웹을 보여주기만 하는 웹뷰는 스토어심사 통과가 어려움.
    앱스토어에 등록하기 위해서는 최소 1개 이상의 네이티브 앱의 기능을 사용해야 통과 가능.
    혹시 안된다면, 푸시 알림같은 기능들을 만들 것이다.

     

     

    3. 언어 선택

    플러터, 리액트 네이티브 중에서 리액트 네이티브 선택

    나중에 우리 html, js를 바꾸게 된다면 리액트로 바꾸게 될 것 같아서,
    비슷한 느낌으로 사용할 수 있는 리액트 네이티브 선택했음


     

    4. 제작 전 설명

    리액트 네이티브와 같이 쓰는 expo라는 것이 있어서 어렵지 않음
    새로운 tsx파일을 생성해서, 웹뷰를 통해 내 웹사이트 url을 호출해주고
    기본으로 제공되는 _layout.tsx 파일 내부에 새로 만든 tsx파일을 넣어주면 끝이다.

    작동하기 위한 다양한 행위나 설정들은 전부 자동으로 제공해준다.

    다 만들었으면 apk파일로 배포해서, 같은 네트워크의 패드나 IOS 기기로 접속한다.

    실제로 앱스토어에 올리지는 않고, 그냥 패드로 테스트만 할 것이다.

     

     

     

    5. 앱 제작

    사실 뭐 특별하게 응용한 건 없고, 그냥 expo에서 기본으로 소개해주는 것들 거의 그대로 사용했다.

    깃과 node.js 를 설치해준다.

    나는 윈도우로 최신 버전 깃이랑 노드 설치했다.

     

    그리고 앱을 감쌀 웹사이트가 필요하니까 아무거나 대충 만들어두자.

    스프링부트로 하나 만들었다.

     

    5 - 1. 테스트용 웹사이트

    RootController

    @Controller
    public class RootController {
        @GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
        public String getRoot() {
            return "index";
        }
    }

     

    index.html

    <!doctype html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
    루트임
    </body>
    </html>

     

     

    5 - 2. 앱 제작

    expo를 써서 만들 건데,

    expo를 이용하기 위해 명령어를 터미널에 입력해준다.

    뒤에 앱이름은 그냥 생성되는 폴더명이다.

     

    프로젝트 경로 > npx create-expo-app 앱이름
    
    프로젝트 경로 > npm install -g eas-cli

     

    위에껀 expo 앱을 처음 생성하는 용도이고, 아래는 배포 및 빌드 관리를 하는 도구를 설치하는 것이다.

    일단 전역으로 설치하긴 했는데, 요즘에는 로컬로 설치하는 추세라고 한다.

     

    프로젝트마다 사용하는 도구의 버전이 다를 수 있는데, 전역 설치는 이런 버전 차이를 해결하기 어렵다.

     

    그런데 로컬 설치를 하면 package.json의 dependencies나 devDependencies에 기록되기 때문에,

    협업 시 어떤 도구와 버전이 필요한지 명확히 알 수 있다.

    전역 설치는 프로젝트 외부에서 관리되므로 프로젝트 설정에 의존성이 명시되지 않는다.

     

     

     

    그리고 로컬 설치는 다른 개발자가 프로젝트를 받아갔을 때, npm install로 동일한 환경을 쉽게 재현할 수 있다.

    하지만 전역 설치는 개발자마다 시스템 환경이 달라질 수 있다.

     

    근데 일단 나혼자 테스트하는거라 그냥 전역 설치 했다.

    전역으로 설치하면 매번 npx를 사용할 필요가 없어서 빠르게 작업할 수 있다.

     

    그 다음 eas 로그인을 해야 한다.

    먼저 계정 생성부터 하자.

    expo 사이트 가서 하면 된다.

    생성했으면 아래 명령어 입력

    프로젝트 경로 > eas login // 한번 했으면 다음부터 안해도 됨
    

     

     

    5 - 3. 빌드와 배포

    이제 생성된 앱파일로 이동하고 명령어 사용

    cd 앱파일 경로
    

     

    빌드

    앱파일 경로 > eas build:configure
    

     

    입력하면 빌드 종류를 선택하는데, 윈도우에선 아이폰 빌드가 안된다.

    안드로이드로 골랐다.

     

    끝났으면 eas.json으로 가서 다음 부분을 수정하자.

     

    eas.json에 build 부분 교체

    "build": {
        "preview": {
            "android": {
                "buildType": "apk"
            }
        },
        "preview2": {
            "android": {
                "gradleCommand": ":app:assembleRelease"
            }
        },
        "preview3": {
            "developmentClient": true
        },
        "preview4": {
            "distribution": "internal"
        },
        "production": {}
    }

     

    배포

    앱파일 경로 > eas build -p android --profile preview
    

     

    명령어를 입력하면, 어플리케이션 id를 입력하라고 나오는데, 고유해야 한다.

    고유하지 않으면 앱스토어에 안올라간다.

     

    컴퓨터가 안좋아서 그런건지는 모르겠는데 배포는 시간이 좀 걸린다.

    11분? 정도 걸렸다.

    빌드를 완료하면 .apk 파일 url을 주는데, 그 url을 패드던 휴대폰이던 써서 설치하면 된다.

    만약 진짜 앱스토어에 올릴 때는 aab 파일을 만들어야 한다.

     

     

     

    6. 웹뷰

    이렇게 만들면 내가 만든 웹사이트를 보여주는 게 아니라,

    기본으로 제공되는 디자인이 나온다.

    그야 앱 만들면서 앱과 웹을 연결해주는 어떤 행동도 안했으니 당연하다.

     

    expo에서 웹뷰라는 것은, 웹사이트를 그대로 앱을 통해 보여준다.

    아래 명령어 입력

    npx expo install react-native-webview
    

     

    그리고 일 처음에 npx create-expo-app 명령어로 기본으로 제공된 앱 폴더를 열어보면,

    app/tabs 폴더 아래에 기본으로 3개 타입스크립트 파일이 있다.

     

    _layout 에서 index, explore 파일을 참조해서 보여주고 있다.

    _layout 코드를 보면

    <Tabs
      screenOptions={{
        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
        headerShown: false,
        tabBarButton: HapticTab,
        tabBarBackground: TabBarBackground,
        tabBarStyle: Platform.select({
          ios: {
            // Use a transparent background on iOS to show the blur effect
            position: 'absolute',
          },
          default: {},
        }),
      }}>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
        }}
      />
    </Tabs>

     

    여기서 탭은 말그대로 옵션들이 나열된 탭을 말하는건데,

    추가하고 삭제할 수 있다.

    앱을 기본으로 설치하면 보였던 것들이 index와 explore 둘이었던 것

     

    tsx 파일을 새로 만들고 여기 추가해도 되지만,

    그냥 index.tsx를 수정하자.

    import { WebView } from 'react-native-webview';
    import Constants from 'expo-constants';
    import { StyleSheet } from 'react-native';
    
    export default function App() {
        return (
            <WebView
                style={styles.container}
                source={{
                	uri: 'http://192.168.xxx.xxx:8080' ,
                	headers: {
                      'appKey': 'your-app-key' // 헤더에 인증 코드 추가
                    }
                }}
            />
        );
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            marginTop: Constants.statusBarHeight,
        },
    });

     

    expo 가이드 문서에서 제공해주는 걸 가지고, 내 로컬에서 테스트하기 위해 uri 부분만 수정했다.

    그리고 생긴걸 보면 알겠지만, 리액트와 비슷하게 생겼다.

    나도 예전에 리액트를 잠깐 해본 적이 있어서 알거 같다.

     

    이제 새로 빌드하면 이 내용이 보이게 된다.

     

     

     

    7. 필터 추가

    처음에 말한대로, 앱을 통한 접근이 아니면 막을 것이다.

    그리고 안드로이드로 빌드했기 때문에, 안드로이드 휴대폰이 아닌 것도 막을 것이다.

     

    AppKeyFilter.java

    @Component
    public class AppKeyFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            String appKey = request.getHeader("appKey");
            String userAgent = request.getHeader("User-Agent");
    
            // Preflight 요청 처리
            if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                // 클라이언트가 보내는 요청에 대해 허용할 출처 지정
                response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
    
                // 클라이언트가 사용할 수 있는 HTTP 메서드를 지정
                response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
    
                // 클라이언트가 사용할 수 있는 HTTP 헤더를 지정
                response.setHeader("Access-Control-Allow-Headers", "appKey, Content-Type");
    
                // preflight 요청에 대한 유효기간을 설정 (초 단위)
                response.setHeader("Access-Control-Max-Age", "3600");
    
                // 요청 성공을 알리는 상태 코드
                response.setStatus(HttpServletResponse.SC_OK);
    
                // preflight 요청을 마친 후 처리하지 않고 바로 종료
                return;
            }
    
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement(); // 헤더 이름 추출
                String headerValue = request.getHeader(headerName); // 헤더 값 추출
                System.out.println(headerName + ": " + headerValue); // 출력
            }
    
    
            // 고유 키 검증
            if (!"your-app-key".equals(appKey)) {
                System.out.println("키가 다름");
                logger.error("키가 다름");
                request.getHeaderNames();
                response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 접근 차단
                return;
            }
    
            if (!userAgent.contains("Android")) {
                logger.error("앱이 아님");
                System.out.println("앱이 아님");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            }
            System.out.println("필터 통과");
    
            filterChain.doFilter(request, response); // 요청 처리
        }
    }

     

     

    8. 추가

    근데 테스트를 해보면 안되는 경우가 있는데

    안드로이드 9 이상부터는 http 연결을 자체적으로 막고 있다.

    그래서 옛날 장비를 사용하면 될 것이다.

     

    그래도 대처법을 알아야 하니까 http 연결을 할 수 있도록 허용해보자.

     

    로컬이냐 전역이냐 따라 다르게 입력하면 된다.

    앱파일 경로 > expo prebuild
    
    앱파일 경로 > npx expo prebuild
    

     

    앱파일 경로 > android/app/src/main/AndroidManifest.xml 파일을 열고, 
    <application> 태그에 android:usesCleartextTraffic="true" 속성을 추가한 다음 다시 배포하면 된다.

     

    아래는 AndroidManifest.xml 파일 일부이다.

    <application android:usesCleartextTraffic="true"

     

     

    9. 추가 2

    웹, 앱 만들 때 차이점

    웹에서는 어떤 사이트를 들어갈 때 보통 홈페이지를 거쳐가지만,
    url만 맞게 입력하면 홈페이지를 거치지 않고도, 다른 페이지에 먼저 진입할 수 있다.

    하지만 생각해보면, 앱은 항상 켤 때마다 첫 페이지가 고정되어 있는데,
    앱은 스택 or 탭 등의 '네비게이션' 방식을 통해 페이지를 관리한다.

    앱은 고정된 첫 페이지 또는 홈 화면을 제공하고, 
    그 화면이 로드되면 다른 화면으로 이동할 수 있는 경로를 
    앱 내 네비게이션 구조에 정의해두는 방식으로 동작한다.

    첫 페이지를 고정하는 것은 네비게이트 설정을 하면 되는데,
    이걸 자동으로 해주나보다.

    다양한 앱 설정들을 어떤식으로 하나 찾아보려고 했는데,
    앱 쪽 지식을 꽤 깊게 파고들어야 해서 일단 지금은 포기했다.

    어차피 웹으로 다 만들어놓은 것을, 
    앱 껍데기만 씌울 뿐이라 앱에서 뭔가 새로 설정할 건 없어서 지금 당장은 몰라도 될 거 같다.

     

    728x90
    반응형
Designed by Tistory.