Progressive Web Apps (PWA) 2025
Progressive Web Apps, web teknolojileri ile native uygulama deneyimi sunan modern çözüm. Offline çalışma, push notifications ve kurulabilirlik ile PWA'ler, mobil uygulamaların yerini alıyor!
PWA Nedir?
Progressive Web App özellikleri:
- 📱 İnstallable - Ana ekrana eklenebilir
- 🔌 Offline - İnternetsiz çalışır
- 🚀 Fast - Hızlı yüklenir
- 🔔 Engaging - Push notifications
- 📲 Native-like - Uygulama gibi hissettirir
- 🌐 Progressive - Her tarayıcıda çalışır
PWA vs Native App
| Özellik | PWA | Native App | |---------|-----|------------| | Geliştirme | Web teknolojileri | Platform specific | | Dağıtım | URL | App Store | | Güncelleme | Instant | Store approval | | Boyut | ~1-5 MB | 50-200 MB | | Offline | ✅ Service Worker | ✅ Built-in | | Push Notif | ✅ | ✅ | | Maliyet | Düşük | Yüksek | | SEO | ✅ İndekslenebilir | ❌ |
Core Components
1. Web App Manifest
{
"name": "My PWA App",
"short_name": "MyPWA",
"description": "Amazing Progressive Web App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0066cc",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"screenshots": [
{
"src": "/screenshots/home.png",
"sizes": "540x720",
"type": "image/png"
}
],
"categories": ["business", "productivity"],
"shortcuts": [
{
"name": "New Task",
"url": "/new-task",
"icons": [{ "src": "/icons/new.png", "sizes": "96x96" }]
}
]
}
<!-- HTML'de manifest -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0066cc">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">
2. Service Worker
service-worker.js:
const CACHE_NAME = 'my-pwa-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
];
// Install Event
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
// Force new service worker to activate immediately
self.skipWaiting();
});
// Activate Event
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
// Take control immediately
return self.clients.claim();
});
// Fetch Event - Caching Strategy
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache hit - return response
if (response) {
return response;
}
// Clone request
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then((response) => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
Kayıt (main.js):
// Service Worker Registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
// Check for updates
registration.update();
// Listen for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available
showUpdateNotification();
}
});
});
})
.catch((error) => {
console.log('SW registration failed:', error);
});
});
}
function showUpdateNotification() {
if (confirm('New version available! Reload to update?')) {
window.location.reload();
}
}
Caching Strategies
1. Cache First (Offline First)
// Best for: Static assets
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
);
});
2. Network First
// Best for: API calls, dynamic content
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clone);
});
return response;
})
.catch(() => caches.match(event.request))
);
});
3. Stale While Revalidate
// Best for: Frequently updated content
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
const fetched = fetch(event.request).then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, clone);
});
return response;
});
return cached || fetched;
})
);
});
4. Network Only
// Best for: Real-time data, analytics
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/realtime')) {
event.respondWith(fetch(event.request));
}
});
Push Notifications
1. Request Permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notification permission granted');
await subscribeUser();
}
}
async function subscribeUser() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
// Send subscription to server
await fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
}
2. Handle Push Event (Service Worker)
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: {
url: data.url
},
actions: [
{
action: 'open',
title: 'Open'
},
{
action: 'close',
title: 'Close'
}
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'open') {
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
}
});
3. Server-Side (Node.js)
const webpush = require('web-push');
// Configure VAPID keys
webpush.setVapidDetails(
'mailto:your@email.com',
PUBLIC_VAPID_KEY,
PRIVATE_VAPID_KEY
);
// Send notification
async function sendNotification(subscription, payload) {
try {
await webpush.sendNotification(subscription, JSON.stringify(payload));
console.log('Notification sent');
} catch (error) {
console.error('Error sending notification:', error);
}
}
// Usage
const payload = {
title: 'New Message!',
body: 'You have a new message',
url: '/messages'
};
sendNotification(userSubscription, payload);
Offline Functionality
1. Offline Page
// service-worker.js
const OFFLINE_PAGE = '/offline.html';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.add(OFFLINE_PAGE))
);
});
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(OFFLINE_PAGE))
);
}
});
2. Background Sync
// Register sync
async function registerBackgroundSync() {
const registration = await navigator.serviceWorker.ready;
try {
await registration.sync.register('sync-messages');
console.log('Sync registered');
} catch (error) {
console.log('Sync registration failed:', error);
}
}
// Service Worker: Handle sync
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-messages') {
event.waitUntil(syncMessages());
}
});
async function syncMessages() {
const messages = await getQueuedMessages();
for (const message of messages) {
try {
await fetch('/api/messages', {
method: 'POST',
body: JSON.stringify(message)
});
await removeFromQueue(message.id);
} catch (error) {
console.error('Sync failed:', error);
}
}
}
Installation Prompt
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent default mini-infobar
e.preventDefault();
// Save event for later
deferredPrompt = e;
// Show custom install button
showInstallButton();
});
function showInstallButton() {
const installButton = document.getElementById('install-button');
installButton.style.display = 'block';
installButton.addEventListener('click', async () => {
// Show install prompt
deferredPrompt.prompt();
// Wait for user response
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response: ${outcome}`);
// Clear deferredPrompt
deferredPrompt = null;
// Hide install button
installButton.style.display = 'none';
});
}
// Listen for app installed
window.addEventListener('appinstalled', () => {
console.log('PWA installed');
// Track installation
gtag('event', 'pwa_install');
});
Advanced Features
1. Web Share API
async function shareContent() {
if (navigator.share) {
try {
await navigator.share({
title: 'Check this out!',
text: 'Amazing PWA',
url: window.location.href
});
console.log('Shared successfully');
} catch (error) {
console.log('Share failed:', error);
}
}
}
2. File System Access
async function saveFile() {
try {
const handle = await window.showSaveFilePicker({
suggestedName: 'document.txt',
types: [{
description: 'Text Files',
accept: { 'text/plain': ['.txt'] }
}]
});
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
console.log('File saved');
} catch (error) {
console.log('Save cancelled:', error);
}
}
3. Badge API
// Set badge count
navigator.setAppBadge(5);
// Clear badge
navigator.clearAppBadge();
Performance Best Practices
1. Precaching
// Workbox example
import { precacheAndRoute } from 'workbox-precaching';
precacheAndRoute(self.__WB_MANIFEST);
2. Route Caching
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst } from 'workbox-strategies';
// Images: Cache First
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days
})
]
})
);
// API: Network First
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api',
networkTimeoutSeconds: 3
})
);
Testing & Debugging
Chrome DevTools
// Application tab
// - Manifest
// - Service Workers
// - Cache Storage
// - Local Storage
// Lighthouse PWA Audit
// - Installable
// - PWA Optimized
// - Offline capable
PWA Checklist
✅ Manifest:
- [ ] manifest.json configured
- [ ] Icons (192x192, 512x512)
- [ ] start_url defined
- [ ] display: standalone
- [ ] theme_color set
✅ Service Worker:
- [ ] Registered correctly
- [ ] Caching strategy
- [ ] Offline fallback
- [ ] Updates handled
✅ HTTPS:
- [ ] Served over HTTPS
- [ ] Valid SSL certificate
✅ Performance:
- [ ] Fast loading (< 3s)
- [ ] Responsive design
- [ ] Core Web Vitals
✅ Features:
- [ ] Push notifications
- [ ] Background sync
- [ ] Install prompt
Sonuç
PWA'ler 2025'te web'in geleceği:
- 🚀 Native performans
- 💰 Düşük maliyet
- 📱 Her platformda çalışır
- 🔌 Offline support
- 🔔 Engagement artışı
PWA Başarı İstatistikleri:
- Twitter: %65 sayfa görüntülme artışı
- Starbucks: %2x günlük aktif kullanıcı
- Pinterest: %60 engagement artışı
- Forbes: %43 daha fazla oturum
Modern PWA geliştirme hizmeti için bize ulaşın! 📱
📧 iletisim@cesayazilim.com
📞 +90 850 225 53 34