Cesa Yazılım
TR EN DE

AMP • TR

Progressive Web Apps (PWA) 2025: Native Deneyimli Web Uygulamaları

2025'te Progressive Web Apps (PWA) geliştirme rehberi. Offline çalışan, kurulabilir ve native benzeri web uygulamaları oluşturma.

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:

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:

Service Worker:

HTTPS:

Performance:

Features:

Sonuç

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