--- name: pwa-setup description: Progressive Web App setup with manifest, mobile meta tags, safe area handling for notched devices, and install prompts for app-like browser experience. license: MIT compatibility: TypeScript/JavaScript metadata: category: frontend time: 2h source: drift-masterguide --- # PWA Setup Progressive Web App configuration for app-like browser experience. ## When to Use This Skill - Users want to "install" your web app - Mobile users want home screen access - Need app-like behavior without native apps - Supporting notched devices (iPhone, etc.) ## Core Concepts PWA requires: 1. **Web App Manifest** - App metadata and icons 2. **Mobile meta tags** - Viewport and theme configuration 3. **Safe areas** - Handle notched devices 4. **Install prompt** - Custom install experience ## Implementation ### TypeScript (Next.js) ```typescript // app/manifest.ts import type { MetadataRoute } from 'next'; export default function manifest(): MetadataRoute.Manifest { return { name: "My SaaS App", short_name: "MySaaS", description: "Your app description here", start_url: '/dashboard', display: 'standalone', background_color: '#0f172a', theme_color: '#14b8a6', orientation: 'portrait-primary', icons: [ { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any', }, { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any', }, { src: '/icons/icon-maskable.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, ], shortcuts: [ { name: 'Dashboard', url: '/dashboard', description: 'Go to dashboard' }, { name: 'Settings', url: '/settings', description: 'App settings' }, ], categories: ['productivity', 'utilities'], }; } ``` ### Root Layout Metadata ```typescript // app/layout.tsx import type { Metadata, Viewport } from 'next'; export const metadata: Metadata = { title: "My SaaS App", description: "Your app description", appleWebApp: { capable: true, statusBarStyle: 'black-translucent', title: "MySaaS", }, applicationName: "MySaaS", openGraph: { title: "My SaaS App", description: "Your app description", type: 'website', siteName: "MySaaS", }, }; export const viewport: Viewport = { width: 'device-width', initialScale: 1, maximumScale: 1, userScalable: false, themeColor: '#14b8a6', viewportFit: 'cover', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return (
{children} ); } ``` ### Safe Area CSS ```css /* globals.css */ /* Safe area for bottom navigation (notched devices) */ .safe-area-bottom { padding-bottom: env(safe-area-inset-bottom, 0); } /* Safe area for top header */ .safe-area-top { padding-top: env(safe-area-inset-top, 0); } /* Full safe area padding */ .safe-area-all { padding-top: env(safe-area-inset-top, 0); padding-right: env(safe-area-inset-right, 0); padding-bottom: env(safe-area-inset-bottom, 0); padding-left: env(safe-area-inset-left, 0); } ``` ### Install Prompt Hook ```typescript // hooks/useInstallPrompt.ts 'use client'; import { useState, useEffect } from 'react'; interface BeforeInstallPromptEvent extends Event { prompt: () => PromiseInstall our app for a better experience