Documentation for PWA features in Crypto Data Aggregator.
- Overview
- Features
- Installation
- Service Worker
- Offline Support
- Push Notifications
- Configuration
- Development
Crypto Data Aggregator is a fully-featured Progressive Web App that provides:
- Installable - Add to home screen on any device
- Offline-capable - Browse cached content without internet
- Push notifications - Real-time price alerts
- Background sync - Queue actions when offline
- Fast loading - Aggressive caching strategies
| Feature | Status | Description |
|---|---|---|
| Install Prompt | ✅ | Custom install button |
| Offline Mode | ✅ | Browse cached content |
| Push Notifications | ✅ | Price & keyword alerts |
| Background Sync | ✅ | Queue offline actions |
| Periodic Sync | ✅ | Background data refresh |
| Share Target | ✅ | Share articles to app |
| App Shortcuts | ✅ | Quick actions from icon |
- Open the app in Safari (iOS) or Chrome (Android)
- Tap the share button
- Select "Add to Home Screen"
- Confirm installation
- Open the app in Chrome or Edge
- Click the install icon in the address bar
- Or click the "Install App" button in the header
import { usePWA } from '@/components/PWAProvider';
function InstallButton() {
const { isInstallable, installPrompt } = usePWA();
if (!isInstallable) return null;
return <button onClick={installPrompt}>Install App</button>;
}public/sw.js
The service worker is registered in PWAProvider.tsx:
useEffect(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then((registration) => {
setRegistration(registration);
setIsServiceWorkerReady(true);
});
}
}, []);| Route Pattern | Strategy | TTL |
|---|---|---|
/api/market/* |
Stale-While-Revalidate | 60s |
/api/news/* |
Network First | 5min |
/api/trending/* |
Stale-While-Revalidate | 2min |
| Static assets | Cache First | 30 days |
| Images | Cache First | 7 days |
| HTML pages | Network First | - |
const CACHES = {
static: 'static-v1', // JS, CSS, fonts
images: 'images-v1', // Coin logos, article images
api: 'api-v1', // API responses
pages: 'pages-v1', // HTML pages
};import { usePWA } from '@/components/PWAProvider';
function NetworkStatus() {
const { isOnline } = usePWA();
if (!isOnline) {
return <OfflineBanner />;
}
return null;
}import { OfflineIndicator } from '@/components/OfflineIndicator';
// Shows automatically when offline
<OfflineIndicator />;When offline and page isn't cached, shows /offline.html:
<!-- public/offline.html -->
<!DOCTYPE html>
<html>
<head>
<title>Offline - Crypto Data Aggregator</title>
</head>
<body>
<h1>You're Offline</h1>
<p>Check your connection and try again.</p>
<button onclick="location.reload()">Retry</button>
</body>
</html>import { usePWA } from '@/components/PWAProvider';
function OfflineAction() {
const { isOnline, requestBackgroundSync } = usePWA();
const handleAction = async () => {
if (!isOnline) {
// Queue for later
await requestBackgroundSync('sync-watchlist');
toast.info("Action queued for when you're back online");
return;
}
// Perform action immediately
await performAction();
};
return <button onClick={handleAction}>Add to Watchlist</button>;
}import { usePWA } from '@/components/PWAProvider';
function NotificationSettings() {
const { isPushSupported, isPushEnabled, requestPushPermission } = usePWA();
if (!isPushSupported) {
return <p>Push notifications not supported</p>;
}
return (
<button onClick={requestPushPermission} disabled={isPushEnabled}>
{isPushEnabled ? 'Notifications Enabled' : 'Enable Notifications'}
</button>
);
}| Type | Trigger | Priority |
|---|---|---|
| Price Alert | Threshold reached | High |
| Keyword Alert | Keyword mentioned | Normal |
| Breaking News | Urgent market news | High |
| Portfolio Update | Daily summary | Low |
interface PushPayload {
type: 'price' | 'keyword' | 'news' | 'portfolio';
title: string;
body: string;
icon?: string;
badge?: string;
data?: {
url?: string;
coinId?: string;
alertId?: string;
};
actions?: Array<{
action: string;
title: string;
}>;
}In sw.js:
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const { action, data } = event.notification;
if (action === 'view-coin') {
clients.openWindow(`/coin/${data.coinId}`);
} else if (action === 'dismiss') {
// Just close
} else {
// Default: open app
clients.openWindow(data?.url || '/');
}
});// public/manifest.json
{
"name": "Crypto Data Aggregator",
"short_name": "Crypto Data",
"description": "Real-time cryptocurrency market data",
"start_url": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#3b82f6",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Trending",
"url": "/trending",
"icons": [{ "src": "/icons/trending.png", "sizes": "96x96" }]
},
{
"name": "Portfolio",
"url": "/portfolio",
"icons": [{ "src": "/icons/portfolio.png", "sizes": "96x96" }]
},
{
"name": "Watchlist",
"url": "/watchlist",
"icons": [{ "src": "/icons/watchlist.png", "sizes": "96x96" }]
}
],
"share_target": {
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
}Required icon sizes:
| Size | Purpose |
|---|---|
| 72x72 | Android legacy |
| 96x96 | Android legacy |
| 128x128 | Chrome Web Store |
| 144x144 | Android legacy |
| 152x152 | iOS |
| 192x192 | Android home screen |
| 384x384 | Android splash |
| 512x512 | Android splash, PWA |
<!-- In layout.tsx -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Crypto Data" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" /># Build production version
npm run build
# Serve with HTTPS (required for SW)
npx serve out -s -l 3000- Open Chrome DevTools → Application
- Check "Service Workers" section
- Use "Update on reload" during development
- Check "Cache Storage" for cached assets
- DevTools → Network tab
- Select "Offline" from throttling dropdown
- Or use: DevTools → Application → Service Workers → Offline
import { usePWA } from '@/components/PWAProvider';
const { clearCache } = usePWA();
// Clear all caches
await clearCache();When a new service worker is available:
import { usePWA } from '@/components/PWAProvider';
import { UpdatePrompt } from '@/components/UpdatePrompt';
function App() {
const { isUpdateAvailable, updateServiceWorker } = usePWA();
return (
<>
{isUpdateAvailable && <UpdatePrompt onUpdate={updateServiceWorker} />}
<MainContent />
</>
);
}Run Lighthouse to verify PWA compliance:
npx lighthouse https://your-app.com --only-categories=pwa- HTTPS enabled
- Service worker registered
- Web app manifest
- Icons (192x192, 512x512)
- Start URL
- Theme color
- Viewport meta tag
- Offline fallback
- Install prompt