Loading...
Please waitLoading...
Please waitDetect when your app is installable as a PWA and trigger the install prompt while tracking attempts.
pnpm dlx uselab@latest add use-pwa-install
import * as React from "react"
import { usePWAInstall } from "@/hooks/use-pwa-install"
export function InstallBanner() {
const { canInstall, isInstalled, promptCount, promptInstall } =
usePWAInstall()
const handleInstall = async () => {
const outcome = await promptInstall()
console.log("user choice:", outcome)
}
if (!canInstall || isInstalled) {
return null
}
return (
<div className="rounded-lg border bg-card p-4">
<p>Install this app for an improved, full-screen experience.</p>
<button onClick={handleInstall}>Install</button>
<p className="text-xs text-muted-foreground">
Prompt shown {promptCount} time(s)
</p>
</div>
)
}| Name | Type | Description |
|---|---|---|
canInstall | boolean | true when the browser raised beforeinstallprompt and the prompt is ready. |
isInstalled | boolean | true when the app already runs in standalone mode or after appinstalled. |
promptCount | number | Number of times promptInstall has been triggered (stored in localStorage). |
promptInstall() | () => Promise<"accepted" | "dismissed" | "unavailable"> | Triggers the install prompt and resolves with the user's choice. |
This hook does not make your app installable by itself. It only listens for the browser's install events. If canInstall always stays false, your app is usually missing one of the requirements below.
public/sw.js.You do not need a dedicated PWARegistration component. You only need to register the service worker somewhere in your client app before expecting the browser to expose beforeinstallprompt.
Make sure your app links a manifest and that it includes a usable name, icons, start URL, scope, and display mode.
{
"name": "My App",
"short_name": "MyApp",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#0b0b0f",
"theme_color": "#ffffff",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}In Next.js, you can expose it with metadata or an app/manifest.ts file. Example:
export async function generateMetadata() {
return {
manifest: "/manifest.webmanifest",
}
}Register the worker from any client entry point in your app:
"use client"
import * as React from "react"
export function PWARegistration() {
React.useEffect(() => {
if (
!("serviceWorker" in navigator) ||
(!window.isSecureContext && window.location.hostname !== "localhost")
) {
return
}
navigator.serviceWorker
.register("/sw.js", { scope: "/" })
.catch((error) => {
console.warn("Service worker registration failed:", error)
})
}, [])
return null
}If you already register your service worker elsewhere, keep that setup. This hook only consumes the install event; it does not register anything for you.
public/sw.jsself.addEventListener("install", () => {
self.skipWaiting()
})
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim())
})
self.addEventListener("fetch", (event) => {
event.respondWith(fetch(event.request))
})That example is intentionally minimal. It is enough for installability testing, but a real PWA usually adds caching, versioning, and update strategy on top.
beforeinstallprompt when your PWA meets installability criteria and the service worker is actually registered.promptInstall returns "unavailable" if the prompt event isn't ready—use canInstall to disable buttons.promptCount to avoid nagging users repeatedly.appinstalled event to update isInstalled.navigator.clipboard and install prompts require user gestures, always trigger promptInstall from a button click.The install prompt appears only in browsers that support the Progressive Web App flow (Chrome, Edge, etc.) and when the page meets installability criteria.
Hint: open this demo on a PWA-capable browser and ensure the site is served over HTTPS.