AMP • TR
2025'te Progressive Web Apps (PWA) geliştirme rehberi. Offline çalışan, kurulabilir ve native benzeri web uygulamaları oluşturma.
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!
Progressive Web App özellikleri:
| Ö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 | ❌ |
{
"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">
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();
}
}
// Best for: Static assets
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
);
});
// 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))
);
});
// 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;
})
);
});
// Best for: Real-time data, analytics
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/realtime')) {
event.respondWith(fetch(event.request));
}
});
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'
}
});
}
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)
);
}
});
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);
// 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))
);
}
});
// 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);
}
}
}
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');
});
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);
}
}
}
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);
}
}
// Set badge count
navigator.setAppBadge(5);
// Clear badge
navigator.clearAppBadge();
// Workbox example
import { precacheAndRoute } from 'workbox-precaching';
precacheAndRoute(self.__WB_MANIFEST);
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
})
);
// Application tab
// - Manifest
// - Service Workers
// - Cache Storage
// - Local Storage
// Lighthouse PWA Audit
// - Installable
// - PWA Optimized
// - Offline capable
✅ Manifest:
✅ Service Worker:
✅ HTTPS:
✅ Performance:
✅ Features:
PWA'ler 2025'te web'in geleceği:
PWA Başarı İstatistikleri:
Modern PWA geliştirme hizmeti için bize ulaşın! 📱
📧 iletisim@cesayazilim.com
📞 +90 850 225 53 34