Leon Chaewon Kong's dev blog

React로 프로그레시브 웹 앱(Progressive Web App) 만들기

왜 프로그레시브 웹 앱인가

최근 설문조사에 따르면 65%의 미국 사용자들이 한 달 동안 설치하는 모바일 앱의 개수가 0개라고 한다. 앱의 홍수 속에서 사용자들은 갈수록 앱을 설치하는 것에 보수적으로 변하고 있다.

한편 모바일에서 웹을 사용하는 것은 매우 불편하다는 인식이 팽배해 왔다. 잦은 로딩과 느린 작동속도, 모바일에 최적화되지 않은 화면 등은 고질적인 문제이며, 무엇이든 타이핑을 통해 입력해야 하는 환경이 수많은 좌절을 불러왔다.

프로그레시브 웹 앱(PWA: Progressive Web App)이라는 개념이 등장한 것은 이런 바탕에서 였다. 웹을 네이티브 처럼 쓸 수 있게 하는 것, 그러나 기존 앱을 활용하여 간단히 만들 수 있을 것. 그것이 PWA의 목표라고 할 수 있다.

그렇다면 PWA에서는 어떤 기능들이 제공되는가?

  • 설치 가능 PWA는 설치가능하다. 물론 구글 플레이스토어나 앱스토어에서 다운로드 할 수 있는 것은 아니다. 하지만 Android 기기의 Chrome 브라우저로 PWA 사이트에 접속할 경우, 바탕화면에 설치하고 싶은지 묻는 팝업이 뜬다. 이 팝업을 통해 앱처럼 설치할 수 있다. 굳이 구글 플레이스토어에 들어갈 필요도 없기에, 오히려 유리하기도 하다. 다만, iOS에서는 즐겨찾기를 추가하듯, 바탕화면에 추가하기를 통해서만 추가가 가능한 것은 단점이다.

  • 캐싱을 통한 오프라인 지원 iOS와 Android 모두 캐싱을 지원한다. 앱의 골격이나 static asset들은 캐싱을 해놓아 앱의 성능을 향상시킬 수 있다. 이는 데이터를 효율적으로 사용해야만 하는 모바일 환경에서도 큰 도움이 되며, 속도 또한 개선할 수 있다.

  • 네이티브 기능 카메라 접근, 푸시 알림 등 네이티브 기능들을 일부 제공한다. 다만, 전반적으로 Android에 비해 iOS가 제공하는 기능들은 제한적이며, 푸시 알림의 경우 Android에서만 가능하다.

프로그레시브 웹 앱의 조건은 무엇인가

그렇다면 현재의 앱을 프로그레시브 웹 앱(PWA)로 만들기 위해서는 어떤 것들이 필요한가?

프로그레시브 앱이 되기 위해서는 서비스 워커, 앱과 유사한 동작 및 뷰, Manifest가 필요하다. 각각에 대해 알아보자.

  • 서비스 워커 웹 페이지를 관리하는 독립된 스크립트다. client-side proxy object 처럼 작동한다.
  • 앱과 유사한 동작 반응형 디자인, 빠른 속도, 최적화된 데이터 사용. 오프라인 기능 등이 전제되어야 한다. 필수는 아니다. 크롬 개발자 도구의 “Audits” 탭을 통해 작성한 PWA의 성능과 접근성 등을 테스트하고 점수를 확인할 수 있다.
  • Web App Manifest 스플래시, 앱 아이콘 등이 사이즈별로 준비되어 있어야 하며, manifest.json에 추가되어야 한다.

React로 PWA를 만드는 방법은 무엇인가

CRA(create-react-app)로 프로젝트를 생성하면 기본적으로 서비스 워커와 manifest를 만들어준다. 약간의 수정만 하면, 우리는 바로 PWA를 만들 수 있다.

필요한 것

  • 스플래시 스크린
  • 사이즈별 앱 아이콘

더불어, iOS에서는 현재 manifest를 지원하지 않기에 html head 부분에 meta 태그를 이용해 내용을 추가해 주어야 한다.

좋은 소식은, 이것들을 자동으로 생성해주는 사이트가 있다는 것이다. 바로 FAVIC-O-MATIC이라는 사이트다.

먼저 “Evert damn size, sir!”을 체크한 후 “UPLOAD YOUR IMAGE” 버튼을 클릭해 이미지를 업로드 한다.

이미지를 선택하면 자동으로 다음 페이지로 이동하며 생성된 이미지가 다운로드된다.

이 페이지로 이동한 후, 빨간 박스 내의 html 태그를 그대로 복사하여 “public/index.html”의 head 태그 안에 붙여넣으면 된다. 불필요한 부분은 삭제하면 된다.

public/index.html

<head>
  <!-- ...기존 내용들 -->
  <!-- PWA Settings -->
  <link
    rel="apple-touch-icon-precomposed"
    sizes="57x57"
    href="/icons/apple-touch-icon-57x57.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="114x114"
    href="/icons/apple-touch-icon-114x114.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="72x72"
    href="/icons/apple-touch-icon-72x72.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="144x144"
    href="/icons/apple-touch-icon-144x144.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="60x60"
    href="/icons/apple-touch-icon-60x60.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="120x120"
    href="/icons/apple-touch-icon-120x120.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="76x76"
    href="/icons/apple-touch-icon-76x76.png"
  />
  <link
    rel="apple-touch-icon-precomposed"
    sizes="152x152"
    href="/icons/apple-touch-icon-152x152.png"
  />
  <link
    rel="icon"
    type="image/png"
    href="/icons/favicon-196x196.png"
    sizes="196x196"
  />
  <link
    rel="icon"
    type="image/png"
    href="/icons/favicon-96x96.png"
    sizes="96x96"
  />
  <link
    rel="icon"
    type="image/png"
    href="/icons/favicon-32x32.png"
    sizes="32x32"
  />
  <link
    rel="icon"
    type="image/png"
    href="/icons/favicon-16x16.png"
    sizes="16x16"
  />
  <link
    rel="icon"
    type="image/png"
    href="/icons/favicon-128.png"
    sizes="128x128"
  />
  <meta name="apple-mobile-web-app-title" content="막차" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="application-name" content="&nbsp;" />
</head>

여기서 중요한 것은 마지막 세 줄 부분이다.

<meta name="apple-mobile-web-app-title" content="[앱 이름]" />
<meta name="apple-mobile-web-app-capable" content="yes" />

“[앱 이름]” 부분에 본인의 앱 이름을 입력한다. 그렇게 되면 iOS에서 바탕화면에 추가할 경우 해당 이름이 디폴트로 제공하게 된다.

다음으로 “apple-mobile-web-app-capable”을 “yes”로 설정해야 safari의 주소창을 보이지 않게 할 수 있고, “앱 같은” 느낌을 줄 수 있다.

여기서 주의할 것은 다운로드 된 아이콘의 path이다. 다운로드 받은 아이콘은 public 디렉토리 안에 존재해야 한다. Public 디렉토리 안에 icons와 같은 디렉토리를 두고 아이콘을 저장하는 것을 추천한다. manifest에 아이콘을 추가하거나 meta 태그에 아이콘을 추가할 때는 path를 잘 밝혀줘야 한다.

아쉽게도 FAVIC-O-MATIC에서는 스플래시를 만들어 주지는 않는다. 스플래시는 직접 만들어서 아이콘과 같은 방법으로 추가해야 한다. 다만, 없어도 PWA를 만드는 데 지장은 없으며, 앱 아이콘을 대신 보여주도록 디폴트 셋팅은 되어 있다.

Manifest 수정

다음으로 Manifest를 수정해 아이콘들을 추가해 줘야 한다.

public/manifest.json

{
  "name": "막차",
  "short_name": "막차",
  "start_url": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "/icons/apple-touch-icon-57x57.png",
      "sizes": "57x57",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-60x60.png",
      "sizes": "60x60",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-76x76.png",
      "sizes": "76x76",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-114x114.png",
      "sizes": "114x114",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-120x120.png",
      "sizes": "120x120",
      "type": "image/png"
    },

    {
      "src": "/icons/apple-touch-icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/icons/apple-touch-icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "/icons/favicon-196x196.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/mstile-310x310.png",
      "sizes": "310x310",
      "type": "image/png"
    },
    {
      "src": "/icons/favicon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "theme_color": "#000033",
  "background_color": "#000033"
}

display에는 여러 옵션이 있는데 standalone으로 할 경우 브라우저의 주소창과 버튼들을 숨길 수 있다.

아이콘은 최대한 다양한 사이즈를 지원해야 한다. 특히 512X512 사이즈는 반드시 지원되어야 PWA로 인식되는 것 같다.

“short_name”을 설정한 것이 앱의 디폴트 설치 이름이 된다.

서비스 워커 등록

서비스 워커 등록이라고 해서 거창할 것은 없고, 메서드만 바꿔주면 된다. CRA(create-react-app)은 기본적으로 거의 모든 보일러플레이트들을 다 만들어 준다. 우리가 할 일은 서비스 워커를 사용하도록 설정하는 것 뿐이다.

src/index.js를 연다.

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

위처럼 되어있는 부분에서 unregister를 register로 변경한다. serviceWorker.js를 열어보면 알겠지만 두 메소드 모두 CRA가 제공하고 있기에, 우리는 그냥 바꿔주기만 하면 된다. 아래처럼 변경한다.

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.register();

마치며

드디어 필요한 것들은 모두 완료했다. 서비스를 배포하고 나서 크롬 개발자 도구로 서비스 워커를 확인할 수 있다.

크롬 개발자 도구에서 “Application” 탭을 들어가 보면 Manifest와 서비스 워커가 제대로 작동하는 것을 확인할 수 있다.

참고자료