Progresif Web Uygulamaları: Modern Web Yeteneklerine Derinlemesine Bakış
Service worker'lar, çevrimdışı-ilk mimari, push bildirimleri ve PWA'ları native uygulamalarla rekabet edecek şekilde geliştirmek için ihtiyacınız olan her şey.
Progresif Web Uygulamaları, web'i nasıl düşündüğümüz konusunda temel bir değişimi temsil eder. Bir web sitesinin erişim ve ulaşılabilirlik avantajlarını alır ve bunları bir zamanlar yalnızca native uygulamalara özgü olan yeteneklerle katmanlandırır. Çevrimdışı destek, push bildirimleri, arka plan senkronizasyonu ve ana ekrana kurulum artık platforma özel özellikler değildir — bunlar her modern tarayıcının desteklediği web standartlarıdır.
PWA tek bir teknoloji değildir. Bir araya geldiğinde birçok senaryoda native bir uygulamadan ayırt edilemeyecek bir deneyim üreten API'ler ve tasarım desenleri için bir şemsiye terimdir. Temel gereksinimler — HTTPS, bir service worker ve bir Web Uygulama Manifestosı — uygulanması basit olsa da, önbelleğe alma, çevrimdışı davranış ve depolama mimarisi etrafındaki tasarım kararları dikkatli bir değerlendirme gerektirir.
Bu rehber, üretim kalitesinde bir PWA'nın her katmanını kapsar: service worker yaşam döngüsü, önbelleğe alma stratejileri, manifest ve kurulabilirlik, push bildirimleri, arka plan senkronizasyonu, IndexedDB ile istemci tarafı depolama, service worker yönetimini basitleştirmek için Workbox kullanımı, PWA ve native karşılaştırması, bilinen iOS sınırlamaları ve PWA'nızı Lighthouse ile denetleme. Her bölüm pratik örnekler ve somut öneriler içerir.
Bir PWA'yı Ne Oluşturur ve Neden Önemlidir
Bir Progresif Web Uygulaması, Web Uygulama Manifestosı spesifikasyonu ve service worker standardı tarafından tanımlanan üç temel kriteri karşılamalıdır. İlk olarak, aracıların uygulama koduna müdahale edememesi için HTTPS üzerinden sunulmalıdır. İkinci olarak, çevrimdışı işlevsellik ve arka plan işleme sağlayan bir service worker kaydetmelidir. Üçüncü olarak, tarayıcıya uygulamanın kullanıcının cihazına kurulduğunda nasıl davranması gerektiğini söyleyen bir Web Uygulama Manifestosı JSON dosyası içermelidir.
Bu teknik gereksinimlerin ötesinde, PWA'lar bir tasarım felsefesini somutlaştırır: aşamalı iyileştirme. Hızlı bağlantıya sahip modern bir tarayıcıdaki kullanıcı tam deneyimi alır — çevrimdışı destek, push bildirimleri, akıcı animasyonlar. Eski bir tarayıcı veya yavaş bir ağdaki kullanıcı yine de işlevsel, erişilebilir bir web sitesi alır. Uygulama, temel içerik ve yönlendirme JavaScript olmadan çalıştığı için zarif bir şekilde geriler ve gelişmiş özellikler yalnızca tarayıcı bunları desteklediğinde üste eklenir.
PWA'ların iş gerekçesi iyi belgelenmiştir. PWA teknolojisine yatırım yapan şirketler, kullanıcı etkileşiminde, dönüşüm oranlarında ve performansta önemli iyileştirmeler bildirmektedir. Pinterest, mobil web deneyimini bir PWA olarak yeniden inşa etti ve temel etkileşimlerde %60 artış gördü. Twitter Lite, veri tüketimini %70 azalttı ve oturum başına sayfa sayısını artırdı. Forbes, kullanıcı başına oturum sayısında %43 artış gördü. Bu sonuçlar, anında yükleme (önbelleğe alınmış kabuk), push bildirimleriyle yeniden etkileşim ve bir uygulama mağazası indirmesine kıyasla web kurulumunun daha düşük sürtünmesinin birleşiminden gelir.
Service Worker'lar: Yaşam Döngüsü ve Yetenekler
Service worker, herhangi bir PWA'nın motorudur. Ana tarayıcı iş parçacığından ayrı olarak arka planda çalışan ve programlanabilir bir ağ proxy'si görevi gören bir JavaScript dosyasıdır. Ağ isteklerini engelleyebilir, önbelleğe alınmış yanıtları sunabilir, arka planda veri senkronize edebilir ve push olaylarını alabilir — tüm bunları web sayfası açık değilken yapabilir.
Service worker yaşam döngüsü üç farklı aşamaya sahiptir: kaydetme, yükleme ve etkinleştirme. Kaydetme, web sayfanız navigator.serviceWorker.register çağırdığında gerçekleşir. Tarayıcı service worker dosyasını indirir, ayrıştırır ve yaşam döngüsü boyunca ilerletir. Bu yaşam döngüsünü anlamak kritiktir çünkü herhangi bir aşamadaki hatalar çevrimdışı deneyiminizi sessizce bozabilir.
// Yaşam Döngüsü İzleme ile Service Worker Kaydı
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => {
console.log('Service worker kaydedildi, kapsam:', reg.scope);
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
console.log('Worker durumu:', newWorker.state);
// Durumlar: 'installing' | 'installed' | 'activating' | 'activated' | 'redundant'
});
});
})
.catch(err => console.error('Kayıt başarısız:', err));
}
// Service Worker Yükleme Olayı
self.addEventListener('install', (event) => {
console.log('Service worker yükleniyor');
// Bekleyen service worker'ın hemen aktif olmasını sağla
self.skipWaiting();
// Kritik kaynakları önceden önbelleğe al
event.waitUntil(
caches.open('static-v1').then(cache =>
cache.addAll([
'/',
'/styles/main.css',
'/scripts/app.js',
'/offline.html',
])
)
);
});
// Service Worker Etkinleştirme Olayı
self.addEventListener('activate', (event) => {
console.log('Service worker etkinleştiriliyor');
// Tüm istemcileri hemen kontrol altına al
event.waitUntil(clients.claim());
// Eski önbellek sürümlerini temizle
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => key !== 'static-v1')
.map(key => caches.delete(key))
)
)
);
});Yükleme olayı, önbelleği statik varlıklarla doldurduğunuz yerdir — uygulama kabuğunuz, CSS, JavaScript, yazı tipleri ve dağıtımlar arasında değişmeyen resimler. Etkinleştirme olayı, eski önbellekleri temizlediğiniz ve clients.claim kullanarak açık sayfaların kontrolünü aldığınız yerdir. Yaygın bir hata, etkinleştirme sırasında önbellek temizliğini atlamaktır, bu da kullanıcının cihazında her varlığın birden çok sürümünün süresiz olarak depolanmasına neden olur.
Service Worker Kapsamı ve Sürümleme
Bir service worker, kapsamı dahilindeki sayfaları kontrol eder. Service worker'ınız /sw.js adresindeyse, / altındaki tüm sayfaları kontrol eder. /app/sw.js konumuna yerleştirirseniz, yalnızca /app/ altındaki sayfaları kontrol eder. Kapsam, service worker'ın dizininin ötesine genişletilemez — bu, isteklerin keyfi olarak ele geçirilmesini önlemek için bir güvenlik önlemidir. Her dağıtım, mantık aynı olsa bile service worker dosyasını değiştirmelidir, çünkü tarayıcı güncellemeleri tetiklemek için bayt düzeyinde farklılıkları kontrol eder.
Önbelleklerinizi sürümlemek, temiz yükseltmeler için çok önemlidir. static-v1, static-v2 ve dynamic-v1 gibi bir adlandırma kuralı kullanın, böylece etkinleştirme işleyicisi eski sürümleri silebilir. Asla bir önbelleğin üzerine yeni içerik yazmayın — her zaman yeni bir önbellek oluşturun ve eskisini silin. Bu, uygulamanızın eski bir sürümünü çalıştıran bir sayfanın, anlamadığı daha yeni bir önbellekten asla varlık almamasını sağlar.
Derleme sırasında başarısız olan bir service worker sessizce atlanır. Service worker'ınızı dağıtmadan önce her zaman yerel olarak test edin ve yükleme ile etkinleştirme işleyicilerinize her zaman hata yönetimi ekleyin.
Önbelleğe Alma Stratejileri Açıklaması
Seçtiğiniz önbelleğe alma stratejisi, ağ kullanılamadığında, yavaş olduğunda veya güvenilmez olduğunda uygulamanızın davranışını belirler. Farklı içerik türleri farklı stratejiler gerektirir. Uygulama kabuğunuz, API yanıtlarınızdan farklı şekilde önbelleğe alınmalı ve resimleriniz, kullanıcıya özel verilerinizden farklı şekilde önbelleğe alınmalıdır. Çoğu PWA mimarisinin temelini oluşturan dört ana strateji vardır.
Önce Önbellek (Ağ Yedekli Önbellek)
Önce Önbellek stratejisi, bir yanıt için önbelleğe bakar ve bulursa döndürür. Kaynak önbellekte değilse, service worker onu ağdan getirir, gelecekteki istekler için yanıtı önbelleğe alır ve döndürür. Bu strateji, yanıt doğrudan cihazın deposundan ağ gecikmesi olmadan geldiği için mümkün olan en hızlı yükleme sürelerini üretir. Statik varlıklar, uygulama kabuğu kaynakları ve sık değişmeyen içerikler için idealdir.
Önce Ağ (Önbellek Yedekli Ağ)
Önce Ağ stratejisi, kaynağı önce ağdan getirmeyi dener. Ağ isteği başarılı olursa, yanıt önbelleğe alınır ve döndürülür. Ağ başarısız olursa — kullanıcı çevrimdışıysa, sunucu kapalıysa veya istek zaman aşımına uğrarsa — service worker önbelleğe geri döner. Bu strateji, hız yerine güncelliğe öncelik verir. API yanıtları, kullanıcıya özel veriler ve sık değişen içerikler için doğru seçimdir.
Eski-iken-Yeniden-Doğrula
Eski-iken-Yeniden-Doğrula stratejisi, önbelleğe alınmış sürümü hemen sunarken eşzamanlı olarak ağdan yeni bir sürüm getirir. Yeni yanıt, bir sonraki istek için önbelleğin yerini alır. Bu, size önce-önbellek davranışının hızını, önce-ağ davranışının güncelliğiyle birleştirir. Ara sıra güncellenen ancak tamamen güncel olması gerekmeyen kaynaklar için ideal stratejidir — haber akışları, sosyal medya zaman tünelleri ve ürün listeleri bu yaklaşımdan faydalanır.
// Önbelleğe Alma Stratejisi Uygulamaları
const CACHE_NAMES = {
static: 'static-v1',
dynamic: 'dynamic-v1',
};
// Önce Önbellek
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response.clone());
return response;
}
// Önce Ağ
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAMES.dynamic);
cache.put(request, response.clone());
return response;
} catch {
const cached = await caches.match(request);
if (cached) return cached;
return new Response('Çevrimdışı', { status: 503 });
}
}
// Eski-iken-Yeniden-Doğrula
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAMES.dynamic);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
// İstekleri stratejilere yönlendiren fetch olay işleyicisi
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (url.origin !== location.origin) {
// Farklı kaynak istekleri (CDN, API)
if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(request));
} else {
event.respondWith(staleWhileRevalidate(request));
}
} else if (url.pathname.startsWith('/static/')) {
event.respondWith(cacheFirst(request));
} else {
event.respondWith(networkFirst(request));
}
});Çoğu üretim PWA'sı bu stratejilerin bir kombinasyonunu kullanır. Fetch olay işleyicisi, gelen isteği inceler ve URL desenine, istek yöntemine veya içerik türüne göre uygun stratejiye yönlendirir. Bu yönlendirme, service worker'ınızdaki en önemli mimari karardır — doğru yapmak, uygulamanızın tekrar ziyaretlerde önbellekten anında yüklenmesi ve dinamik içerik için güncel kalması anlamına gelir.
Çevrimdışı-İlk Mimari Desenleri
Çevrimdışı-ilk, uygulamanızı çevrimdışının bir hata durumu değil, varsayılan mod olduğu şekilde tasarlamak anlamına gelir. Ağ, yerel-ilk deneyimin üzerinde bir iyileştirme haline gelir. Bu, ağın kullanılabilir olduğu varsayılan ve çevrimdışının özel işlem gerektiren bir hata durumu olduğu geleneksel web geliştirmeden temel bir zihniyet değişimidir.
En yaygın çevrimdışı-ilk desen, uygulama kabuğu mimarisidir. Uygulama kabuğu — kullanıcı arayüzünü oluşturmak için gereken minimum HTML, CSS ve JavaScript — service worker yükleme olayı sırasında önbelleğe alınır. Sonraki her gezinme, önbellekten anında yüklenir ve dinamik içerik ağdan veya yerel bir veri deposundan doldurulur. Bu, kullanıcıya hızlı bir bağlantıda, yavaş bir bağlantıda veya tamamen çevrimdışıyken aynı anında yükleme deneyimini sağlar.
- Uygulama kabuğu ön önbelleğe alma: HTML iskeletini, CSS çerçevesini ve JavaScript paketini service worker yükleme olayı sırasında önbelleğe alın. Kabuk, ağ durumundan bağımsız olarak sonraki her ziyarette anında yüklenir.
- İçerik sulandırma: Kabuk yüklendikten sonra, dinamik içeriği ağdan getirin. Ağ kullanılamıyorsa, IndexedDB'ye geri dönün veya eski veriyi belirten bir banner ile önbelleğe alınmış sürümü gösterin.
- Arka plan senkronizasyon kuyruğu: Kullanıcı çevrimdışıyken bir yazma işlemi gerçekleştirdiğinde — form gönderme, yazı oluşturma, profil güncelleme — isteği IndexedDB'ye serileştirin ve Senkronizasyon API'sini kullanarak ağ kullanılabilir olduğunda tekrar oynatın.
- İyimser UI güncellemeleri: Kullanıcı bir eylem gerçekleştirdiğinde, ağ değişikliği onaylamadan önce kullanıcı arayüzünü hemen güncelleyin. Ağ isteği başarısız olursa, UI'yi geri alın ve bir hata bildirimi gösterin.
Çevrimdışı-ilk, yalnızca-çevrimdışı ile aynı şey değildir. İyi tasarlanmış bir çevrimdışı-ilk uygulama, ağ kullanılabilir olduğunda her zaman yeni veriler getirir ve kullanıcıya gördükleri verilerin güncelliği hakkında görünürlük sağlar. Son güncelleme: 3 dakika önce gibi basit bir gösterge, güven oluşturmak için yeterlidir. Amaç, yükleme döndürücüsünü varsayılan deneyim olarak ortadan kaldırmak ve onu arka planda sessizce güncellenen anlık yerel verilerle değiştirmektir.
Çevrimdışı-ilk, hiç çevrimiçi olmayan kullanıcıları desteklemekle ilgili değildir. Güvenilmez ağlarda mobil web deneyimlerini etkileyen boş yükleme durumunu ortadan kaldırmakla ilgilidir. Ağ mükemmel çalışırken bile her kullanıcı anında yüklemeden faydalanır.
Web Uygulama Manifestosı ve Kurulabilirlik
Web Uygulama Manifestosı, tarayıcıya uygulamanızın kullanıcının cihazına kurulduğunda nasıl davranması gerektiğini söyleyen bir JSON dosyasıdır. Uygulama adını, simgeleri, başlangıç URL'sini, görüntüleme modunu, yönlendirmeyi, tema rengini ve arka plan rengini kontrol eder. Bir manifest olmadan, tarayıcı sitenizi kurulum adayı olarak görmez ve birçok PWA özelliği devre dışı kalır.
Manifest, uygulamanızın her sayfasından HTML başlığındaki <link rel=manifest href=/manifest.json> etiketi aracılığıyla bağlanır. Tarayıcılar periyodik olarak manifest güncellemelerini kontrol eder, ancak kullanıcının değişiklikleri alması için uygulamayı yeniden kurması gerekir. Manifest dosyasını bir sorgu parametresi veya benzersiz bir dosya adıyla sürümlemek, manifest meta verilerini güncellemeyi planladığınızda iyi bir uygulamadır.
- display: standalone, tarayıcı arayüzünü kaldırır ve uygulamanın kendi penceresinde açılmasını sağlar; bu, en native benzeri deneyimdir.
- display: minimal-ui, adres çubuğu gibi temel gezinme kontrollerini korurken kurulu bir deneyim sunar.
- display: fullscreen, her şeyi gizler ve oyunlar ile medya uygulamaları için uygundur.
- display: browser, varsayılandır ve kurulum avantajı sağlamaz — PWA'lar için bundan kaçının.
Kurulabilirlik kriterleri tarayıcıya göre değişir, ancak hepsi kısa bir ad, en az 192x192 piksel simgeler, kayıtlı ve aktif bir service worker ve HTTPS içeren geçerli bir manifest gerektirir. Chrome ayrıca, uygulamanın ziyaretler arasında en az beş dakika olacak şekilde en az iki kez ziyaret edilmiş olmasını gerektirir. Bu kriterler, yalnızca kullanıcının düzenli olarak etkileşimde bulunduğu uygulamaların kurulum istemini tetiklemesini sağlayarak erken kurulum taleplerinin gürültüsünü azaltır.
Push Bildirimleri
Push bildirimleri, PWA'lar için birincil yeniden etkileşim kanalıdır. APNs veya FCM gibi platforma özel hizmetler aracılığıyla çalışan native push bildirimlerinin aksine, web push bildirimleri Push API ve Notification API'yi birlikte kullanır. Push API, mesajı sunucunuzdan tarayıcı kapalıyken bile service worker'a iletir. Notification API daha sonra mesajı service worker bağlamından kullanıcıya görüntüler.
Push akışı birkaç adım içerir. İlk olarak, uygulamanız kullanıcıdan bildirim izni ister. İzin verilirse, bir uç nokta URL'si ve şifreleme anahtarları içeren bir abonelik nesnesi oluşturan PushManager API'sini kullanarak push mesajlarına abone olur. Uygulamanız bu aboneliği sunucunuza gönderir ve sunucunuz onu saklar. Bir bildirim göndermek istediğinizde, sunucunuz yükü abonelik uç noktasına gönderir ve tarayıcı, push olayını işlemek için service worker'ınızı uyandırır.
// VAPID ile istemci tarafı push aboneliği
async function subscribeToPush(registration) {
const publicKey = 'VAPID_AÇIK_ANAHTARINIZ';
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey),
});
// Aboneliği sunucunuza gönderin
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
return subscription;
}
// Service Worker Push Olay İşleyicisi
self.addEventListener('push', (event) => {
const data = event.data?.json() ?? {
title: 'Varsayılan Başlık',
body: 'Varsayılan içerik',
icon: '/icons/icon-192.png',
badge: '/icons/badge-96.png',
data: { url: '/' },
};
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
badge: data.badge,
vibrate: [200, 100, 200],
actions: data.actions ?? [],
data: data.data,
})
);
});
// Bildirim Tıklama İşleyicisi
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data?.url ?? '/';
event.waitUntil(
clients.matchAll({ type: 'window' }).then(clientList => {
for (const client of clientList) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
return clients.openWindow(url);
})
);
});VAPID (Gönüllü Uygulama Sunucusu Tanımlaması), web push için gereklidir. Sunucunuzu gönderdiği push mesajlarıyla ilişkilendirir ve yetkisiz varlıkların kullanıcılarınıza bildirim göndermesini önler. VAPID anahtarları bir kez oluşturulur ve sunucunuzda saklanır. Açık anahtar, abonelik çağrısına dahil edilir ve özel anahtar, sunucunuzun gönderdiği her push isteğini imzalar. VAPID olmadan, büyük tarayıcılar push mesajlarını reddeder.
Push bildirimi izni, bir düğme tıklaması gibi bir kullanıcı hareketine yanıt olarak istenmelidir. Sayfa yüklenirken izin istemek, tarayıcı engellemesini tetikler ve kabul oranlarını %60'tan fazla düşürür. Doğru anda sorun, ilk anda değil.
Arka Plan Senkronizasyonu ve Periyodik Senkronizasyon
Arka Plan Senkronizasyon API'si, bir service worker'ın kullanıcı kararlı bir ağ bağlantısına sahip olana kadar eylemleri ertelemesine olanak tanır. Bu, kullanıcının çevrimdışıyken bir form gönderebileceği veya içerik oluşturabileceği çevrimdışı-ilk uygulamalar için çok önemlidir. Bu verileri kaybetmek veya kullanıcıyı beklemeye zorlamak yerine, service worker isteği serileştirir, bir senkronizasyon olayı kaydeder ve ağ kullanılabilir olduğunda tekrar oynatır.
Periyodik Senkronizasyon, ilgili ancak farklı bir API'dir ve service worker'ın uygulama açık olmasa bile düzenli aralıklarla yeni içerik getirmesine olanak tanır. Bu, native bir uygulamanın arka plan yenilemesinin web karşılığıdır. Haber uygulamaları, sosyal akışlar ve e-posta istemcileri, önbelleğe alınmış içeriği taze tutmak için periyodik senkronizasyon kullanır, böylece kullanıcı uygulamayı açtığında ağ isteklerini beklemeden en son verileri görür.
// Çevrimdışı bir eylemden sonra arka plan senkronizasyonu kaydetme
async function submitOfflineForm(data) {
// Form verilerini IndexedDB'ye kaydet
await saveToSyncQueue(data);
// Benzersiz bir etiketle senkronizasyon olayı kaydet
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-pending-forms');
}
// Service Worker Senkronizasyon Olay İşleyicisi
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-pending-forms') {
event.waitUntil(processSyncQueue());
}
});
async function processSyncQueue() {
const pendingItems = await getSyncQueue();
for (const item of pendingItems) {
try {
const response = await fetch(item.url, {
method: item.method,
headers: item.headers,
body: item.body,
});
if (response.ok) {
await removeFromSyncQueue(item.id);
}
} catch (error) {
console.error('Senkronizasyon başarısız:', item.id, error);
// Tarayıcı tekrar deneyecek; üstel geri bildirim yerleşiktir
break;
}
}
}
// Periyodik Senkronizasyon Kaydı
async function registerPeriodicSync() {
const registration = await navigator.serviceWorker.ready;
// İzin kontrolü (çoğu tarayıcı tarafından gereklidir)
const status = await navigator.permissions.query({
name: 'periodic-background-sync',
});
if (status.state === 'granted') {
await registration.periodicSync.register('refresh-feed', {
minInterval: 24 * 60 * 60 * 1000, // Günde bir kez
});
}
}Arka plan senkronizasyonu, tarayıcılar arasında iyi desteklenir ve güvenilirdir. Periyodik senkronizasyon daha sınırlı desteğe sahiptir — Chrome Android'de destekler, ancak iOS Safari bunu hiç uygulamaz. Periyodik senkronizasyon kullanılamadığında, kullanıcı uygulamayı açtığında yeni içerik getirmeye geri dönün. Senkronizasyon olay işleyicisi her zaman idempotent olmalıdır: aynı veri için bir senkronizasyon işlemi iki kez çalışırsa, ikinci çalışma hiçbir şey yapmamalıdır.
IndexedDB ve İstemci Tarafı Depolama
Service worker'lar ağ yanıtlarını önbelleğe alabilir, ancak birçok uygulama için yalnızca önbelleğe alma yeterli değildir. Ayrıca yapılandırılmış istemci tarafı depolamaya — tarayıcıda yaşayan ve sayfa yenilemelerinde verileri koruyan bir veritabanına — ihtiyacınız vardır. IndexedDB bunun için standart çözümdür. Her modern tarayıcıya yerleşik, işlemsel, nesne yönelimli bir veritabanıdır ve çevrimdışı-ilk uygulamaların bağlı olduğu depolama katmanını sağlar.
Senkron olan, yalnızca string anahtar-değer çiftleriyle sınırlı ve yaklaşık 5 MB ile sınırlı olan localStorage'ın aksine, IndexedDB asenkrondur, ikili bloblar dahil yapılandırılmış verileri destekler ve tarayıcının kullanılabilir disk alanı tarafından belirlenen depolama limitleri sunar. Asenkron API, service worker bağlamları için kritiktir çünkü senkron işlemler olay döngüsünü bloke eder ve tarayıcının service worker'ı sonlandırmasına neden olabilir.
// Çevrimdışı Veri Deposu için IndexedDB Sarmalayıcısı
const DB_NAME = 'PWAStore';
const DB_VERSION = 2;
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('posts')) {
db.createObjectStore('posts', { keyPath: 'id' });
}
if (!db.objectStoreNames.contains('sync_queue')) {
const store = db.createObjectStore('sync_queue', {
keyPath: 'id',
autoIncrement: true,
});
store.createIndex('status', 'status', { unique: false });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function savePosts(posts) {
const db = await openDB();
const tx = db.transaction('posts', 'readwrite');
const store = tx.objectStore('posts');
for (const post of posts) {
store.put(post); // Ekle veya güncelle
}
await new Promise((resolve, reject) => {
tx.oncomplete = resolve;
tx.onerror = reject;
});
}
async function getPost(id) {
const db = await openDB();
const tx = db.transaction('posts', 'readonly');
const store = tx.objectStore('posts');
return store.get(id);
}
async function getAllPosts() {
const db = await openDB();
const tx = db.transaction('posts', 'readonly');
const store = tx.objectStore('posts');
return store.getAll();
}IndexedDB sorguları, tüm istekler tamamlandığında ve işlem kapsam dışına çıktığında otomatik olarak tamamlanan işlemler içinde gerçekleştirilir. Bu, dikkatli kullanım olmadan işlemler arasında asenkron işlemleri karıştıramayacağınız anlamına gelir. Yaygın bir hata, bir işlem açmak, fetch gibi bir asenkron işlem başlatmak ve işlemin asenkron işlem çözümlendiğinde aktif kalmasını beklemektir. Kalmayacaktır — işlem, kontrol olay döngüsüne döndüğünde otomatik olarak tamamlanır.
- IndexedDB, yapılandırılmış veriler, kullanıcı tarafından oluşturulan içerik, senkronizasyon kuyrukları ve anahtarlar veya indekslerle sorgulanması gereken veriler için doğru seçimdir.
- CacheStorage (service worker'lar tarafından kullanılan önbellek API'si), ağ yanıtları, statik varlıklar ve CDN'lerden gelen opak yanıtlar için doğru seçimdir.
- localStorage, basit tercihler, bayraklar ve işlemsel bütünlük gerektirmeyen küçük kritik olmayan durumlar için uygundur.
- SessionStorage, yalnızca sekme kapanışından sonra kalması gerekmeyen geçici durumlar için kullanılmalıdır, örneğin sihirbaz form ilerlemesi.
IndexedDB, service worker kapsamı ve ana iş parçacığı kapsamı içinde kullanılabilir, ancak veritabanı örneği paylaşılır. Veritabanı şemanızı her zaman sürümleyin ve onupgradeneeded olayını düzgün bir şekilde yönetin, çünkü uygulamanızın farklı sürümlerini çalıştıran iki sekme aynı veritabanına aynı anda erişebilir.
Üretim Service Worker'ları için Workbox
Service worker kodunu elle yazmak eğiticidir ancak üretim uygulamaları için nadiren pratiktir. Service worker, önbellek sürümleme, istek yönlendirme, strateji seçimi, arka plan senkronizasyonu ve push olaylarını yönetmelidir. Elle yazılan uygulamalar kaçınılmaz olarak uç durumları kaçırır — başarısız olan bir farklı kaynak isteği, kullanıcının diskini dolduran bir önbellek veya yükleme ve etkinleştirme olayları arasında bir yarış koşulu. Google Chrome ekibi tarafından geliştirilen Workbox, bu sorunları iyi test edilmiş modüllerden oluşan bir kütüphane ile çözer.
Workbox en iyi derleme zamanı aracı olarak entegre edilir. workbox-webpack-plugin, workbox-vite-plugin veya workbox-build CLI, derleme işleminiz sırasında service worker dosyasını oluşturur ve önceden önbelleğe alınmış varlıkların listesini ve revizyon hash'lerini enjekte eder. Bu, service worker geliştirmenin en hataya açık kısmını ortadan kaldırır: ön önbellek manifestosını gerçek derleme çıktısıyla senkronize tutmak. Bir dosya eklediğinizde veya değiştirdiğinizde, derleme eklentisi otomatik olarak service worker manifestosunu günceller.
// Workbox Webpack Eklentisi Yapılandırması
const { InjectManifest } = require('workbox-webpack-plugin');
module.exports = {
// ... diğer webpack yapılandırması
plugins: [
new InjectManifest({
swSrc: './src/sw.js', // Özel service worker şablonunuz
swDest: 'sw.js', // Derleme dizininde çıktı yolu
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5 MB
}),
],
};
// src/sw.js — Workbox service worker şablonu
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import {
NetworkFirst,
CacheFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Bu import, derleme zamanında gerçek ön önbellek manifestosu ile değiştirilir
precacheAndRoute(self.__WB_MANIFEST);
// Google Fonts'u CacheFirst ve süre sınırı ile önbelleğe al
registerRoute(
/^https:\/\/fonts\.googleapis\.com/,
new CacheFirst({
cacheName: 'google-fonts',
plugins: [
new ExpirationPlugin({
maxEntries: 30,
maxAgeSeconds: 60 * 60 * 24 * 365, // 1 yıl
}),
],
})
);
// API yanıtlarını NetworkFirst ile önbelleğe al
registerRoute(
/\/api\//,
new NetworkFirst({
cacheName: 'api-responses',
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 60 * 60, // 1 saat
}),
],
})
);
// Resimleri StaleWhileRevalidate ile önbelleğe al
registerRoute(
/\.(?:png|jpg|jpeg|svg|gif|webp)$/,
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 gün
}),
],
})
);Workbox modülleri tree-shake edilebilir, bu nedenle üretim service worker'ınız yalnızca gerçekten kullandığınız stratejileri ve eklentileri içerir. ExpirationPlugin, giriş sayısını ve önbelleğe alınmış yanıtların maksimum yaşını sınırlayarak önbellek şişmesini önler. CacheableResponsePlugin, yalnızca başarılı yanıtları önbelleğe almanızı sağlar — bir 404 veya 500 yanıtı asla geçerli bir önbelleğe alınmış sürümün yerini almamalıdır. BroadcastUpdatePlugin, önbelleğe alınmış içerik arka planda güncellendiğinde web uygulamanızı bilgilendirerek kullanıcıya bir bildirim göstermenize olanak tanır.
- workbox-precaching, ön önbellek manifestosu, revizyon takibi ve derleme varlıklarının önce-önbellek sunumunu yönetir.
- workbox-routing, aksi halde manuel uygulama gerektirecek URL eşleme ve strateji dağıtımını sağlar.
- workbox-strategies, CacheFirst, NetworkFirst, StaleWhileRevalidate, NetworkOnly ve CacheOnly stratejilerini içerir.
- workbox-expiration, eski önbellek girişlerinin sayı, yaş veya her ikisine göre otomatik temizliğini ekler.
- workbox-background-sync, başarısız ağ isteklerini kuyruğa almak ve daha sonra tekrar göndermek için hazır bir çözüm sunar.
- workbox-window, ana iş parçacığından service worker güncellemeleri ve kurulum istemleri dahil yaygın PWA etkileşimlerini yönetir.
PWA ve Native: Hangisi Ne Zaman Mantıklı
Bir PWA veya native bir uygulama oluşturma kararı, özel gereksinimlerinize, kitlenize ve kaynaklarınıza bağlıdır. Hiçbir yaklaşım evrensel olarak üstün değildir. Doğru seçim, kullanıcılarınıza ve iş hedeflerinize en iyi hizmet edendir. Ödünleşimleri anlamak, abartı veya geleneği takip etmek yerine bilinçli bir karar vermek için çok önemlidir.
PWA'lar, erişim, keşfedilebilirlik ve düşük sürtünmenin en önemli olduğu senaryolarda mükemmeldir. Bir kullanıcı bir URL'yi ziyaret ederek bir PWA kullanmaya başlayabilir — uygulama mağazası yok, indirme beklemesi yok, onay gerektiren kurulum diyaloğu yok. Güncelleme mekanizması sorunsuzdur çünkü en son sürüm her gezinmede sunulur. PWA'lar, herhangi bir modern tarayıcıda herhangi bir işletim sisteminde çalıştıkları için doğal olarak çapraz platformdur. İçerik odaklı uygulamalar, haber siteleri, e-ticaret mağazaları ve yardımcı araçlar için PWA'lar genellikle native uygulamalardan daha üstün bir kullanıcı deneyimi sağlar.
- Erişim ve anında erişimin platforma özel özelliklerden daha önemli olduğu durumlarda PWA'yı seçin. İçerik siteleri, e-ticaret, seyahat rezervasyonu ve üretkenlik araçları mükemmel PWA adaylarıdır.
- Web API yüzeyinin kapsamadığı derin donanım erişimi, arka plan ses, Bluetooth veya NFC bağlantısı veya gelişmiş kamera kontrolleri gerektiğinde native'i seçin.
- Web'in geniş erişimini uygulama mağazalarının dağıtım ve keşif avantajlarıyla birleştirmek istediğinizde hibrit (PWA artı native sarmalayıcı) seçin.
Native uygulamalar birkaç alanda avantajlarını korur. Tam cihaz API yüzeyine erişimleri vardır — Bluetooth, NFC, USB, gelişmiş kamera kontrolleri, arka plan ses oynatma ve Köken Özel Dosya Sistemi'nin ötesinde dosya sistemi erişimi. Tarayıcının yaşam süresi sınırlamalarına tabi olmayan gerçek arka plan hizmetleri çalıştırabilirler. iOS'te App Clips, Widget'lar ve Siri Kısayolları veya Android Widget'ları gibi platforma özel özelliklerle entegre olurlar ve sistem düzeyinde paylaşım, kimlik doğrulama ve ödeme akışlarına katılabilirler.
PWA ve native yetenekleri arasındaki fark her yıl daralmaktadır. Dosya Sistemi Erişimi, Web Bluetooth, Web NFC, WebUSB, Async Clipboard ve Screen Wake Lock API, en önemli donanım etkileşimlerinin çoğunu web'e getirmiştir. Kalan boşluklar — gelişmiş arka plan yürütme ve tam çevrimdışı medya oynatma — aktif olarak standartlaştırılmaktadır.
iOS Safari Sınırlamaları ve Geçici Çözümler
iOS Safari, PWA'lar için en kısıtlı tarayıcı ortamıdır. Apple, birkaç PWA etkinleştirici teknolojiyi benimsemekte yavaş davranmıştır ve bazı sınırlamalar teknik kısıtlamalardan ziyade kasıtlı tasarım kararlarıdır. Bu sınırlamaları anlamak çok önemlidir çünkü iOS, mobil web trafiğinin önemli bir kısmını temsil eder ve iPhone'larda düzgün çalışmayan bir PWA tam bir çözüm değildir.
En etkili sınırlama, iOS Safari'nin Push API ve Notification API'yi desteklememesidir. Web push bildirimleri, kullandığınız tarayıcıdan bağımsız olarak iOS'ta tamamen kullanılamaz, çünkü Apple tüm üçüncü taraf tarayıcıların WebKit kullanmasını gerektirir ve WebKit, Push API'yi uygulamaz. Bu, PWA'lar için iOS'ta birincil yeniden etkileşim kanalını ortadan kaldırır. Tek geçici çözüm, yedek olarak e-posta veya SMS bildirimleri uygulamaktır, ancak bunlar push bildirimlerinin anlıklığından ve düşük sürtünmesinden yoksundur.
- Push bildirimleri: iOS'ta kullanılamaz. E-posta veya uygulama içi bildirim rozetlerine geri dönün.
- Periyodik arka plan senkronizasyonu: Kullanılamaz. Arka plan yenilemeye güvenmek yerine uygulama açıldığında yeni içerik getirin.
- Önbellek kalıcılığı: Safari, service worker önbelleklerini 7-14 gün kullanılmama sonrasında silebilir. Kritik varlıkları periyodik olarak yeniden önbelleğe alan bir önbellek ısıtma stratejisi uygulayın.
- Kurulum istemi: Safari, native kurulum banner'ını göstermez. Kullanıcılar manuel olarak Paylaş'a ve ardından Ana Ekrana Ekle'ye dokunmalıdır.
- Blob depolama: Safari, blob URL ömürlerini sınırlar, bu da önbelleğe alınmış yanıtlardan video ve resim oynatmayı bozabilir. Blob URL'leri yerine doğrudan yanıtları kullanmayı düşünün.
Bu sınırlamalara rağmen, iOS'taki PWA'lar hala anlamlı faydalar sağlar. Uygulama kabuğu önbellekten anında yüklenir ve algılanan yükleme süresini önemli ölçüde azaltır. Tek başına görüntüleme modu, tarayıcı arayüzünü kaldırır ve native benzeri bir gezinme deneyimi sağlar. Simge rozetleri, push odaklı olmasa da, kullanıcı uygulamayı açtığında güncellenebilir. Önbelleğe alma sayesinde performans iyileştirmeleri ve önceden ziyaret edilmiş sayfalar için çevrimdışı deneyim, tam iOS özellik paritesi olmasa bile bir PWA oluşturmayı haklı çıkarmak için yeterlidir.
PWA'ları Lighthouse ile Denetleme
Lighthouse, Google'ın web uygulamaları için otomatik denetleme aracıdır. Performans, erişilebilirlik, en iyi uygulamalar, SEO ve PWA uyumluluğunu kapsayan ayrıntılı bir rapor oluşturur. PWA denetimi, kategorilere ayrılmış yaklaşık 14 kriteri kontrol eder: kurulabilirlik, service worker, çevrimdışı davranış ve manifest yapılandırması. Bu denetimlerden herhangi birinde başarısız olmak, uygulamanızın PWA'nın temel tanımını karşılamadığı anlamına gelir.
Bir Lighthouse denetimi çalıştırmak basittir. Chrome DevTools'u açın, Lighthouse sekmesine gidin, denetlemek istediğiniz kategorileri seçin ve Rapor Oluştur'a tıklayın. PWA denetimleri için özellikle, mobil cihazda veya mobil cihaz emülasyonu etkinken denetim yapmalısınız çünkü görüntü alanı yapılandırması ve dokunma hedefi boyutlandırması gibi bazı kriterler mobil'e özgüdür. Lighthouse ayrıca Lighthouse CI aracılığıyla CI entegrasyonunu destekler ve dağıtım hattınızda PWA standartlarını zorunlu kılmanıza olanak tanır.
// PWA Denetimi için Lighthouse CI Yapılandırması
// .lighthouseci.js
module.exports = {
ci: {
collect: {
startServerCommand: 'npm run start',
url: ['http://localhost:3000'],
numberOfRuns: 3,
settings: {
preset: 'desktop',
},
},
assert: {
assertions: {
// PWA kategorisi 100 puan almalı
'categories:pwa': ['error', { minScore: 1 }],
// Bireysel PWA denetimleri
'service-worker': 'error',
'installable-manifest': 'error',
'splash-screen': 'error',
'themed-omnibox': 'error',
'content-width': 'error',
'viewport': 'error',
'apple-touch-icon': 'error',
// Performans bütçeleri
'categories:performance': ['warn', { minScore: 0.9 }],
'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};En sık başarısız olan PWA denetimleri, iOS Safari'nin bir başlangıç ekranı görüntülemesi için gereken apple-touch-icon bağlantısının eksik olması ve yanlış theme-color yapılandırmasıdır. apple-touch-icon, modern iOS cihazları için en az 180x180 piksel olmalıdır ve theme-color meta etiketi, başlangıç ekranı geçişi sırasında yanlış renk parlamasını önlemek için manifest'inizdeki theme_color özelliğiyle eşleşmelidir.
- Bir service worker kaydedin ve DevTools'un Uygulama > Service Workers panelinde yanıt verdiğini doğrulayın.
- En az 192x192 ve 512x512 piksel simgeler, kısa bir ad ve bir başlangıç URL'si içeren bir Web Uygulama Manifestosı ekleyin.
- iOS başlangıç ekranı desteği için 180x180 boyutunda bir resme işaret eden bir apple-touch-icon bağlantı etiketi ekleyin.
- Başlangıç sırasında tutarlı markalama için theme-color meta etiketini manifest theme_color ile eşleşecek şekilde ayarlayın.
- DevTools'da Çevrimdışı onay kutusunu işaretleyip sayfayı yenileyerek çevrimdışı davranışı doğrulayın.
- DevTools'daki Service Worker panelinin Push düğmesini kullanarak push bildirimlerini manuel olarak test edin.
Lighthouse denetimleri bir başlangıç noktasıdır, bir varış noktası değil. 14 PWA denetiminin tamamını geçmek, uygulamanızın teknik olarak kurulabilir olduğu ve minimum çevrimdışı desteğe sahip olduğu anlamına gelir. Çevrimdışı deneyiminizin iyi tasarlandığı, önbelleğe alma stratejinizin optimal olduğu veya push bildirimi akışınızın kullanıcı dostu olduğu anlamına gelmez. Lighthouse'u kalite sürecinizdeki ilk kontrol olarak kullanın, ardından otomatik denetimlerin ölçemediği kullanıcı deneyimi yönlerine yatırım yapın.
Hepsini Bir Araya Getirmek
Üretim kalitesinde bir PWA oluşturmak, birçok hareketli parçanın koordine edilmesini gerektirir. Service worker, kullanıcı deneyimini bozmadan kaydedilmeli, sürümlenmeli ve güncellenmelidir. Önbelleğe alma stratejileri, kaynak türüne göre seçilmeli ve gerçek ağ koşulları altında test edilmelidir. Manifest, çapraz platform uyumluluğu için yapılandırılmalıdır. Push bildirimleri, izin kontrollü ve kullanıcıya saygılı olmalıdır. Arka plan senkronizasyonu, idempotent ve dayanıklı olmalıdır. IndexedDB şemaları sürümlenmeli ve taşınmalıdır.
Basit başlayın. Statik varlıklarınız için tek bir Önce Önbellek stratejisiyle uygulama kabuğunu uygulayın. Kabuk çevrimdışı güvenilir bir şekilde çalıştıktan sonra API rotalarınız için Önce Ağ ekleyin. Medya ve üçüncü taraf kaynakları için Eski-iken-Yeniden-Doğrula'yı tanıtın. Kullanıcıların uygulamanıza en az üç kez geri döndüğünü gösteren analizleriniz olduktan sonra push bildirimleri ekleyin — bu, push'un rahatsızlıktan ziyade değer katacağı minimum davranışsal sinyaldir. Workbox'u ilk günden itibaren kullanın çünkü mevcut elle yazılmış bir service worker'a sonradan eklemek, onunla başlamaktan daha fazla iştir.
PWA'ları mümkün kılan web platformu yetenekleri hala gelişmektedir. Dosya Sistemi Erişimi, WebUSB, Web Bluetooth ve Async Clipboard API, native platformlarla olan farkı kapatmaya devam etmektedir. iOS tarafından PWA özelliklerinin benimsenmesi, yavaş olsa da ilerlemektedir — Safari, son beta sürümlerinde Uzaktan Bildirimler API'si aracılığıyla push bildirimi benzeri özellikler için destek eklemiştir. Yön nettir: web, birinci sınıf bir uygulama platformu haline gelmektedir ve bugün PWA'lar oluşturmak için gereken beceriler, platform olgunlaştıkça daha da değerli hale gelecektir.
