사이트를 모바일 웹앱으로 실행되도록 하는 방법 (feat.apple-meta-tag)
프로그레시브 웹 앱은 웹 기술을 사용하여 개발된 애플리케이션으로, 사용자가 웹 브라우저에서 접속할 수 있으며 모바일 앱과 비슷한 사용자 경험을 제공합니다. 이러한 웹 앱은 오프라인에서도 작동할 수 있으며, 푸시 알림을 받을 수 있고, 홈 화면에 아이콘을 추가하여 쉽게 접근할 수 있습니다. 프로그레시브 웹 앱은 기기 또는 운영체제에 구애받지 않고 모든 플랫폼에서 동작할 수 있습니다.
스마트폰 사용자의 50%는 앱 다운로드를 원하지 않기 때문에 검색하거나 쇼핑할 때 회사의 모바일 사이트를 사용할 가능성이 더 큽니다
앱을 제거하는 가장 큰 이유 중 하나는 제한된 저장 공간입니다 (설치된 PWA는 일반적으로 1MB 미만 사용).
스마트폰 사용자는 제품에 대한 관련 추천을 제공하는 모바일 사이트에서 구매할 가능성이 더 높으며 스마트폰 사용자의 85%는 모바일 알림이 유용하다고 말합니다.
beforeinstallprompt 이벤트를 실행하고 브라우저 내 설치 프로모션을 표시하기 전에 다음 기준을 충족해야 합니다.
또한 다음을 포함하는 웹 앱 매니페스트가 있어야 합니다.
데스크톱에서:
모바일에서:
{
"name": "VoTogether",
"short_name": "VoTogether",
"icons": [
{
"src": "./android-icon-36x36.png",
"sizes": "36x36",
"type": "image/png",
"density": "0.75",
"purpose": "any maskable"
},
{
"src": "./android-icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"density": "1.0",
"purpose": "any maskable"
},
{
"src": "./android-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5",
"purpose": "any maskable"
},
{
"src": "./android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0",
"purpose": "any maskable"
},
{
"src": "./android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0",
"purpose": "any maskable"
},
{
"src": "./android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0",
"purpose": "any maskable"
},
{
"src": "/images/android-icon-512x512.png",
"type": "image/png",
"sizes": "512x512",
"density": "5.0",
"purpose": "any maskable"
}
],
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ffffff"
}
beforeinstallprompt는 Progressive Web App (PWA)를 설치하기 전에 사용자에게 설치 프롬프트를 제공하기 위한 이벤트입니다. 이 이벤트는 웹 앱이 PWA로서 설치 가능한지 여부를 확인한 후, 설치할 것인지 묻는 팝업 창을 사용자에게 보여줄 수 있습니다. 사용자가 설치 프롬프트를 수락하면 매니페스트 파일에서 정의한 설치 프로세스가 시작되며, 웹 앱이 사용자의 기기에 설치됩니다.
beforeinstallprompt은 실험적인 기술이며, 특정 브라우저에서 예상대로 작동하지 않을 수 있습니다.
isDeviceIOS가 true라면 defaultBeforeInstallPromptEvent을 deferredPrompt의 초기값으로 설정하여 컴포넌트가 보이도록 합니다. 사용자가 프롬프트를 보고 닫기를 눌렀을 경우 로컬스토리지에 localStorage.setItem('iosInstalled', 'false');
을 설정해주고 사용자가 페이지 이동 후 다시 보여질수도 있기 때문에 const isActive = JSON.parse(localStorage.getItem('iosInstalled') || 'true');
와 같은 코드로 보이지 않도록 설정해줍니다.
const defaultBeforeInstallPromptEvent: BeforeInstallPromptEvent = {
platforms: [],
userChoice: Promise.resolve({ outcome: 'dismissed', platform: '' }),
prompt: () => Promise.resolve(),
preventDefault: () => {},
};
const isIOSPromptActive = () => {
const isActive = JSON.parse(localStorage.getItem('iosInstalled') || 'true');
if (isActive) {
return defaultBeforeInstallPromptEvent;
}
return null;
};
export default function AppInstallPrompt() {
const isDeviceIOS = /iPad|iPhone|iPod/.test(window.navigator.userAgent);
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(
isDeviceIOS ? isIOSPromptActive() : null
);
const handleCancelClick = () => {
localStorage.setItem('iosInstalled', 'false');
setDeferredPrompt(null);
};
const handleBeforeInstallPrompt = (event: BeforeInstallPromptEvent) => {
event.preventDefault();
setDeferredPrompt(event);
};
{deferredPrompt && (
<MobileInstallPrompt
handleInstallClick={handleInstallClick}
handleCancelClick={handleCancelClick}
platform={isDeviceIOS ? 'ios' : 'android'}
/>
)}
const handleInstallClick = () => {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(() => {
setDeferredPrompt(null);
});
}
};
import { Fragment, useEffect, useState } from 'react';
import { BeforeInstallPromptEvent } from '../../../../window';
import MobileInstallPrompt from './MobileInstallPrompt';
const defaultBeforeInstallPromptEvent: BeforeInstallPromptEvent = {
platforms: [],
userChoice: Promise.resolve({ outcome: 'dismissed', platform: '' }),
prompt: () => Promise.resolve(),
preventDefault: () => {},
};
const isIOSPromptActive = () => {
const isActive = JSON.parse(localStorage.getItem('iosInstalled') || 'true');
if (isActive) {
return defaultBeforeInstallPromptEvent;
}
return null;
};
export default function AppInstallPrompt() {
const isDeviceIOS = /iPad|iPhone|iPod/.test(window.navigator.userAgent);
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(
isDeviceIOS ? isIOSPromptActive() : null
);
const handleInstallClick = () => {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(() => {
setDeferredPrompt(null);
});
}
};
const handleCancelClick = () => {
localStorage.setItem('iosInstalled', 'false');
setDeferredPrompt(null);
};
const handleBeforeInstallPrompt = (event: BeforeInstallPromptEvent) => {
event.preventDefault();
setDeferredPrompt(event);
};
useEffect(() => {
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}, []);
return (
<Fragment>
{deferredPrompt && (
<MobileInstallPrompt
handleInstallClick={handleInstallClick}
handleCancelClick={handleCancelClick}
platform={isDeviceIOS ? 'ios' : 'android'}
/>
)}
</Fragment>
);
}
global로 선언해주어서 any가 아닌 beforeinstallprompt의 타입을 적용할 수 있습니다.
window.d.ts
export interface BeforeInstallPromptEvent {
readonly platforms: string[];
readonly userChoice: Promise<{
outcome: 'accepted' | 'dismissed';
platform: string;
}>;
preventDefault(): void;
prompt(): Promise<void>;
}
declare global {
interface WindowEventMap {
beforeinstallprompt: BeforeInstallPromptEvent;
}
}
참고 자료
https://web.dev/what-are-pwas/ (PWA란)
https://web.dev/drive-business-success/ (PWA의 이점)
https://web.dev/customize-install/ (자신만의 인앱 설치 경험을 제공하는 방법)
https://web.dev/promote-install/ (PWA 설치 촉진 디자인 패턴)
https://web.dev/install-criteria/ (PWA 설치가 가능하기 위한 조건)
https://web.dev/codelab-make-installable/ (PWA 설치 코드 예제)
https://kagrin97-blog.vercel.app/react/pwa-beforeInstallPrompt (PWA 설치 코드 예제 2)
https://stackoverflow.com/questions/51503754/typescript-type-beforeinstallpromptevent (beforeinstallprompt 이벤트 타입스크립트 적용하기)
https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable (지원하는 브라우저 종류)
https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent (beforeinstallprompt MDN)
잘 읽었습니다!