41.1 설치는 어떻게 권하는 게 좋을까요?
PWA의 진짜 힘은 홈 화면에 설치됐을 때 나와요. 주소창 없이 앱처럼 뜨고, 아이콘도 생기고, 재방문율도 확 올라가죠. 그런데 사용자는 "이 웹사이트 설치할 수 있어요"라는 걸 잘 몰라요. 그래서 우리가 적절한 순간에 살짝 권해줘야 해요.
핵심은 안드로이드 크롬과 아이폰 사파리를 다르게 다뤄야 한다는 거예요. 크롬은 설치용 이벤트를 던져주니 버튼으로 자연스럽게 유도할 수 있고, 사파리는 그게 없으니 손으로 하는 방법을 안내해야 해요. 이번 편에서 둘 다 만들어볼게요.
41.2 beforeinstallprompt가 대체 뭐예요?
크롬은 어떤 사이트가 설치할 만하다고 판단하면 beforeinstallprompt라는 이벤트를 던져요. 이 이벤트에는 특별한 능력이 하나 있어요. 바로 나중에 설치 창을 직접 띄우는 기능이죠.
기본 동작은 브라우저가 알아서 작은 설치 배너를 띄우는 거예요. 그런데 우리는 그 기본 배너를 막고, 이벤트를 잠깐 저장해뒀다가 우리가 원하는 순간에 우리 버튼으로 설치를 부르고 싶어요. 그 흐름은 이래요.
상황: 기본 배너 대신 내 설치 버튼을 쓰고 싶어요.
let deferred = null;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault(); // 기본 배너를 막아요
deferred = e; // 이벤트를 보관해둬요
installBtn.hidden = false; // 내 설치 버튼을 보여줘요
});
여기서 e.preventDefault()가 브라우저 기본 배너를 막는 부분이에요. 그리고 이벤트 자체를 deferred라는 변수에 넣어 기억해둬요. 이 이벤트는 딱 이때 한 번 오기 때문에, 안 잡아두면 나중에 설치를 부를 방법이 사라지거든요.
41.3 그럼 버튼을 누르면 어떻게 설치되나요?
이제 사용자가 내 설치 버튼을 눌렀을 때, 저장해둔 이벤트의 prompt()를 부르면 진짜 설치 창이 떠요. 그리고 사용자가 설치를 눌렀는지 취소했는지는 userChoice로 알 수 있어요.
installBtn.addEventListener('click', async () => {
if (!deferred) return; // 이벤트가 없으면 아무것도 안 해요
deferred.prompt(); // 설치 창을 띄워요
const { outcome } = await deferred.userChoice;
console.log(outcome); // 'accepted' 또는 'dismissed'
deferred = null; // 이벤트는 한 번 쓰면 끝이에요
installBtn.hidden = true;
});
결과로 나오는 outcome이 accepted면 사용자가 설치를 받아들인 거고, dismissed면 취소한 거예요. 여기서 꼭 기억할 게 있어요. 저장해둔 이벤트는 한 번만 쓸 수 있어요. prompt()를 부른 뒤엔 deferred를 null로 비워줘야, 실수로 두 번 부르는 사고를 막아요.
41.4 설치가 끝난 건 어떻게 알죠?
사용자가 설치를 마치면 appinstalled라는 이벤트가 따로 와요. 여기서 설치 버튼을 확실히 숨기거나, "설치해주셔서 고맙습니다" 같은 안내를 띄우면 깔끔해요.
window.addEventListener('appinstalled', () => {
installBtn.hidden = true;
console.log('설치 완료!');
});
그런데 여기엔 함정이 하나 있어요. 이미 설치해서 앱 모드로 열고 있는 사용자에게 또 설치 버튼을 보여주면 이상하잖아요. 그래서 지금 앱 모드로 열렸는지를 먼저 확인해서, 그런 경우엔 버튼을 처음부터 숨겨야 해요.
const installed =
window.matchMedia('(display-mode: standalone)').matches
|| navigator.standalone; // 아이폰용 확인
if (installed) installBtn.hidden = true;
display-mode: standalone은 "주소창 없이 앱처럼 떠 있는 상태"를 뜻해요. 이게 참이면 이미 설치해서 들어온 거죠. 아이폰은 navigator.standalone으로 따로 확인해요.
41.5 아이폰 사용자에겐 뭐라고 안내하죠?
지난 편에서 봤듯 아이폰 사파리엔 beforeinstallprompt가 없어요. 그래서 버튼을 눌러 자동 설치하는 건 불가능하고, 손으로 하는 방법을 그림과 함께 알려주는 수밖에 없어요.
보통 이렇게 해요. 접속한 기기가 아이폰(iOS 사파리)인지 확인하고, 아직 설치 전이라면 화면 아래에 작은 안내를 띄우는 거예요.
const ua = navigator.userAgent;
const isIOS = /iphone|ipad|ipod/i.test(ua);
const inApp = navigator.standalone; // 이미 설치했으면 true
if (isIOS && !inApp) {
iosGuide.hidden = false; // "공유 → 홈 화면에 추가" 안내를 보여줘요
}
안내 문구는 최대한 짧고 그림처럼 보여주는 게 좋아요. "아래 공유 버튼을 누르고, 홈 화면에 추가를 눌러주세요" 정도로요. 글로만 길게 쓰면 아무도 안 읽어요.
41.6 아무 사이트나 설치 이벤트가 뜨나요?
여기서 자주 막혀요. "코드는 그대로 넣었는데 beforeinstallprompt가 안 와요"라는 거죠. 크롬은 아무 사이트에나 이 이벤트를 주지 않아요. 설치할 자격을 갖춘 사이트에만 던져줘요. 조건은 이래요.
먼저 사이트가 HTTPS(보안 연결)로 제공돼야 해요. 그리고 manifest(웹앱 설정 파일)에 앱 이름과 시작 주소, 그리고 192px과 512px 크기의 아이콘이 들어 있어야 해요. 또 display 값이 standalone처럼 앱 모드로 지정돼 있어야 하고요.
마지막으로 서비스 워커(service worker, 백그라운드에서 도는 스크립트)가 등록돼 있어야 해요. 이 넷 중 하나라도 빠지면 크롬은 이벤트를 안 줘요. 저는 512px 아이콘을 깜빡했다가 "왜 이벤트가 안 오지" 하며 코드만 노려본 적이 있어요. 알고 보니 문제는 코드가 아니라 manifest였죠.
이럴 땐 크롬 개발자 도구의 Application 탭을 열면, manifest에 뭐가 빠졌는지 친절하게 알려줘요. 이벤트가 안 오면 코드부터 의심하지 말고 여기부터 확인하는 게 빨라요.
41.7 설치 유도, 언제 보여주는 게 좋을까요?
기술만큼 중요한 게 타이밍이에요. 사이트에 처음 들어오자마자 설치 배너를 들이밀면 대부분 반사적으로 닫아버려요. 아직 이 서비스가 좋은지도 모르는데 왜 설치하겠어요.
그래서 사용자가 가치를 느낀 뒤에 권하는 게 정석이에요. 글을 몇 개 읽었거나, 장바구니에 뭘 담았거나, 두 번째로 재방문했을 때처럼요. 한번 닫은 사용자에게 매번 다시 띄우는 것도 금물이에요. "닫음"을 기억해뒀다가 한동안은 조용히 있어야 해요.
정리하면 설치 유도는 크롬은 이벤트로, 아이폰은 안내로 나눠서 만들고, 이미 설치한 사람은 빼고, 사용자가 가치를 느낀 순간에 딱 한 번 자연스럽게 권하는 거예요. 억지로 밀어붙이지 않는 설치 유도가, 역설적으로 설치율을 제일 높여줘요.