Progressive Web App (PWA) Geliştirme: Offline-First Yaklaşım
Progressive Web App Nedir ve Neden Önemlidir?
Progressive Web App (PWA), web teknolojileri kullanılarak geliştirilen ancak native uygulamalara benzer bir deneyim sunan modern web uygulamalarıdır. PWA'lar; offline çalışabilme, push bildirim gönderebilme, ana ekrana eklenebilme ve hızlı yüklenme gibi özellikleriyle öne çıkar. Google, Microsoft ve Apple gibi büyük teknoloji şirketlerinin PWA desteğini artırmasıyla birlikte, bu yaklaşım giderek daha fazla benimsenmektedir.
Offline-first yaklaşımı ise PWA geliştirmenin temel taşlarından biridir. Bu strateji, uygulamanın öncelikle çevrimdışı senaryolar göz önünde bulundurularak tasarlanmasını ve internet bağlantısının bir "bonus" olarak değerlendirilmesini öngörür. Özellikle mobil kullanıcıların sıklıkla kararsız bağlantı koşullarıyla karşılaştığı düşünüldüğünde, offline-first yaklaşımı kullanıcı deneyimini dramatik şekilde iyileştirir.
PWA'nın Temel Bileşenleri
1. Service Worker
Service Worker, PWA mimarisinin kalbidir. Tarayıcı ile ağ arasında bir proxy görevi gören bu JavaScript dosyası, arka planda çalışarak ağ isteklerini yakalar, önbellek yönetimi yapar ve çevrimdışı işlevselliği mümkün kılar.
Service Worker'ı kaydetmek için ana JavaScript dosyanızda şu kodu kullanabilirsiniz:
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker kayıt başarılı:', registration.scope);
} catch (error) {
console.error('Service Worker kayıt hatası:', error);
}
});
}
2. Web App Manifest
Manifest dosyası, uygulamanızın meta verilerini tanımlayan bir JSON dosyasıdır. Uygulama adı, ikonlar, tema rengi ve görüntüleme modu gibi bilgileri içerir:
{
"name": "Benim PWA Uygulamam",
"short_name": "PWA App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
3. HTTPS Zorunluluğu
Service Worker'lar yalnızca güvenli bağlantılar üzerinden çalışır. Bu nedenle PWA'nızın HTTPS üzerinden sunulması zorunludur. Geliştirme ortamında localhost bu kuraldan muaftır, ancak canlı ortamda mutlaka bir SSL sertifikası kullanmalısınız.
Offline-First Strateji ve Önbellekleme Yöntemleri
Offline-first yaklaşımda birden fazla önbellekleme stratejisi kullanılabilir. Uygulamanızın farklı kaynakları için farklı stratejiler tercih etmek en iyi sonucu verir.
Cache-First (Önbellek Öncelikli)
Bu strateji, önce önbellekte arama yapar; bulamazsa ağa başvurur. Statik varlıklar (CSS, JS, görseller) için idealdir:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request).then((networkResponse) => {
return caches.open('dinamik-cache-v1').then((cache) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
});
})
);
});
Network-First (Ağ Öncelikli)
Önce ağdan veri almayı dener, başarısız olursa önbelleğe düşer. Sık güncellenen API verileri için uygundur:
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((networkResponse) => {
const responseClone = networkResponse.clone();
caches.open('api-cache-v1').then((cache) => {
cache.put(event.request, responseClone);
});
return networkResponse;
})
.catch(() => {
return caches.match(event.request);
})
);
});
Stale-While-Revalidate
Önbellekteki veriyi anında döndürür, arka planda ağdan güncel veriyi çekerek önbelleği günceller. Hem hız hem de güncellik açısından dengeli bir yaklaşımdır:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('swr-cache-v1').then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
});
})
);
});
Precaching: Kritik Kaynakları Önceden Yükleme
Service Worker'ın install aşamasında kritik kaynakları önbelleğe almak, uygulamanın çevrimdışı ortamda sorunsuz çalışmasını sağlar:
const CACHE_NAME = 'uygulama-cache-v1';
const CRITICAL_ASSETS = [
'/',
'/index.html',
'/css/main.css',
'/js/app.js',
'/offline.html',
'/icons/icon-192x192.png'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('Kritik kaynaklar önbelleğe alınıyor');
return cache.addAll(CRITICAL_ASSETS);
})
);
});
Eski önbellekleri temizlemek için activate olayını kullanabilirsiniz:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});
IndexedDB ile Çevrimdışı Veri Yönetimi
Basit önbellekleme yeterli olmadığında, IndexedDB yapılandırılmış verileri çevrimdışı depolamak için güçlü bir çözüm sunar. Kullanıcıların çevrimdışıyken oluşturduğu verileri saklamak ve bağlantı döndüğünde sunucuyla senkronize etmek için idealdir:
function veritabaniAc() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('PWA_Veritabani', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('bekleyen_islemler')) {
db.createObjectStore('bekleyen_islemler', {
keyPath: 'id',
autoIncrement: true
});
}
};
request.onsuccess = (event) => resolve(event.target.result);
request.onerror = (event) => reject(event.target.error);
});
}
async function offlineVeriKaydet(veri) {
const db = await veritabaniAc();
const tx = db.transaction('bekleyen_islemler', 'readwrite');
tx.objectStore('bekleyen_islemler').add({
...veri,
zaman: Date.now(),
senkronize: false
});
}
Background Sync ile Veri Senkronizasyonu
Background Sync API, çevrimdışıyken yapılan işlemleri bağlantı geri geldiğinde otomatik olarak sunucuya göndermek için kullanılır. Bu sayede kullanıcı veri kaybı yaşamaz:
// Ana uygulama tarafında sync kaydı
async function formGonder(formVerisi) {
try {
await fetch('/api/veri', {
method: 'POST',
body: JSON.stringify(formVerisi)
});
} catch (error) {
await offlineVeriKaydet(formVerisi);
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('veri-senkronizasyonu');
}
}
// Service Worker tarafında sync işlemi
self.addEventListener('sync', (event) => {
if (event.tag === 'veri-senkronizasyonu') {
event.waitUntil(bekleyenVerileriGonder());
}
});
async function bekleyenVerileriGonder() {
const db = await veritabaniAc();
const tx = db.transaction('bekleyen_islemler', 'readonly');
const store = tx.objectStore('bekleyen_islemler');
const tumVeriler = await store.getAll();
for (const veri of tumVeriler) {
await fetch('/api/veri', {
method: 'POST',
body: JSON.stringify(veri)
});
}
}
Workbox ile PWA Geliştirmeyi Kolaylaştırma
Google'ın geliştirdiği Workbox kütüphanesi, Service Worker yönetimini ve önbellekleme stratejilerini büyük ölçüde basitleştirir. Manuel Service Worker yazmak yerine Workbox kullanarak daha az kodla daha sağlam bir yapı oluşturabilirsiniz:
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Build sırasında oluşturulan manifest ile precaching
precacheAndRoute(self.__WB_MANIFEST);
// Görseller için Cache-First
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'gorseller',
plugins: [
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 gün
})
]
})
);
// API istekleri için Network-First
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-verileri',
networkTimeoutSeconds: 3
})
);
// CSS ve JS için Stale-While-Revalidate
registerRoute(
({ request }) =>
request.destination === 'style' || request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'statik-kaynaklar'
})
);
PWA Performans Optimizasyonu İpuçları
- App Shell Modeli: Uygulamanın temel iskeletini (header, sidebar, footer) önbelleğe alın ve dinamik içeriği ayrı yükleyin. Bu sayede uygulama anında yüklenir.
- Lazy Loading: Sayfa dışı görselleri ve kritik olmayan kaynakları ertelenmiş yükleme ile optimize edin.
- Önbellek Boyutunu Yönetin: ExpirationPlugin gibi araçlarla önbellek boyutunu kontrol altında tutun. Sınırsız önbellek cihaz depolama alanını tüketebilir.
- Güncellemeleri Yönetin: Service Worker güncellendiğinde kullanıcıya bildirim gösterin ve yeni sürümü etkinleştirmesi için yönlendirin.
- Çevrimdışı Fallback Sayfası: Önbelleğe alınmamış sayfalara çevrimdışı erişildiğinde anlamlı bir fallback sayfası gösterin.
PWA'nızı Test Etme
Chrome DevTools'un Application sekmesi, PWA geliştirme sürecinde en önemli aracınızdır. Bu sekme üzerinden Service Worker durumunu izleyebilir, önbellek içeriğini inceleyebilir ve çevrimdışı modu simüle edebilirsiniz. Ayrıca Lighthouse aracını kullanarak PWA uyumluluk puanınızı kontrol edebilirsiniz:
# Lighthouse CLI ile PWA denetimi
npx lighthouse https://sizin-uygulamaniz.com --only-categories=pwa --output=html
Sonuç
Offline-first yaklaşımla geliştirilen PWA'lar, kullanıcılara her koşulda güvenilir ve hızlı bir deneyim sunar. Service Worker, Cache API, IndexedDB ve Background Sync gibi modern web API'lerinin birlikte kullanımı, native uygulamalara rakip olabilecek güçlü web uygulamaları oluşturmayı mümkün kılar. Workbox gibi araçlar ise bu süreci büyük ölçüde basitleştirerek geliştirici deneyimini iyileştirir. PWA teknolojisi olgunlaştıkça, offline-first yaklaşım artık bir tercih değil, modern web geliştirmenin standart bir parçası haline gelmektedir.