インストール
PWA を「ホーム画面に追加」させる仕組み。Android Chrome は beforeinstallprompt イベントで
独自プロンプトを出せる。iOS はユーザー手動のみ。
インストール可能になる条件
- HTTPS(または localhost)で配信
- Web App Manifest の必須フィールドを満たす(
name/icons(192+512)/display/start_url) - Service Worker が登録され、
fetchハンドラがある - ユーザーがそのページに数秒間滞在する("engagement")
Android Chrome — beforeinstallprompt
Chrome は条件を満たすと beforeinstallprompt を発火。preventDefault してイベントを保持し、
後で prompt() を呼ぶとインストール UI が出る。
let deferredPrompt = null
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault()
deferredPrompt = e
showInstallButton()
})
document.querySelector("#install-btn").addEventListener("click", async () => {
if (!deferredPrompt) return
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
console.log(outcome) // "accepted" or "dismissed"
deferredPrompt = null
hideInstallButton()
})
window.addEventListener("appinstalled", () => {
console.log("installed")
hideInstallButton()
})
iOS — 手動インストール
iOS Safari は beforeinstallprompt を持たない。「共有」→「ホーム画面に追加」を案内する UI を作る。
function isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
}
function isStandalone() {
return ("standalone" in navigator && (navigator as any).standalone) ||
window.matchMedia("(display-mode: standalone)").matches
}
if (isIOS() && !isStandalone()) {
showIOSInstallHint() // 「共有 → ホーム画面に追加」の案内
}
インストール済みかを判定
const isStandalone =
window.matchMedia("(display-mode: standalone)").matches ||
(window.navigator as any).standalone === true ||
document.referrer.startsWith("android-app://")
if (isStandalone) {
// PWA として起動されている
}
インストールボタンの UX
- 初回訪問では出さない — engagement が育ってからの方がOK率高い
- 「あなたのホームに置けます」というメリットを伝える
- 場所は邪魔にならないところ(ヘッダー右、フッター等)
- 断られたらしばらく出さない(cookie で記録)
断られた時の処理
const { outcome } = await deferredPrompt.userChoice
if (outcome === "dismissed") {
localStorage.setItem("install-dismissed-at", Date.now().toString())
}
// 次回チェック
const dismissedAt = Number(localStorage.getItem("install-dismissed-at") ?? 0)
const cooldown = 7 * 24 * 60 * 60 * 1000 // 1週間
if (Date.now() - dismissedAt < cooldown) {
return // 出さない
}
display-mode を使ったスタイル分岐
/* PWA として起動された時だけ */
@media (display-mode: standalone) {
.top-bar { display: none; } /* 戻るボタン等は不要 */
.install-banner { display: none; }
}
App Store / Google Play への配布
- Android: PWA Builder で TWA(Trusted Web Activity)として APK / AAB を生成
- iOS: PWA Builder + iOS Wrapper、または Capacitor / Ionic でラップ
- Microsoft Store: PWA Builder で msix 生成。Edge は標準 PWA 対応
WebAPK(Android)
Chrome は PWA を「ホームに追加」した時、自動でWebAPKを生成。
これは普通のアプリと同じ扱いになり、アプリ一覧・通知・設定から見える。
manifest の name や icons が確定的なら自動。
インストール後の挙動
- URL バーが消える(
standalone) - シェアシートやファイルピッカーは普通通り使える
- OS のバックジェスチャはアプリ内のナビとして使える
- iOS はセッションが分離(Safari のクッキーと別)
シェルの設計(App Shell パターン)
PWA のUI 骨格を最初にキャッシュして、瞬時に表示する。中身は後から動的にロード:
- HTML / CSS / JS のシェル部分を precache
- 初回起動から白画面なしでナビ等が見える
- API データは Stale-While-Revalidate でゆっくり差し替え
トラブルシュート
- インストールボタンが出ない — Lighthouse PWA 監査で必須項目を確認
- icon が反映されない — 192 / 512 が必須。
maskableも確認 - iOS でフルスクリーンにならない —
apple-mobile-web-app-capablemeta が必要 - 2回目以降 prompt が出ない — 仕様で1セッション1回。タブを閉じて再訪
UX チェック
✓ 初回はインストールボタン出さない
✓ 断られたら 1 週間以上出さない
✓ iOS には専用の手順説明を出す
✓ display-mode: standalone で PWA 専用 UI
✓ App Shell をキャッシュして起動瞬時に