Son güncelleme: Nisan 2026 · AnomixLabs Teknik Ekibi
PWA, native uygulama geliştirmek yerine web'in evrensel erişilebilirliğinden ödün vermeden uygulama deneyimi sunmanın en pragmatik yolu — özellikle Django arka planı olanlar için.
PWA Nedir?
Progressive Web App (PWA), modern web teknolojileriyle geliştirilen ve native uygulama benzeri deneyim sunan web uygulamalarıdır. Üç temel bileşen: Web App Manifest (kurulum bilgileri), Service Worker (arka plan işleme ve offline cache), HTTPS (güvenlik zorunluluğu).
PWA avantajları: App Store bağımsızlığı, %30 komisyon yok, anında güncelleme, tek kod tabanı tüm platformlar, arama motorlarında indekslenebilirlik.
iOS 17.4 PWA Değişiklikleri: Önemli Güncelleme
Apple, iOS 17.4 ile Avrupa'da home screen PWA desteğini kaldırdığını duyurdu (Şubat 2024), ardından DMA (Digital Markets Act) baskısıyla bu karardan geri döndü (Mart 2024). Gelinen nokta:
- iOS'ta PWA home screen kurulumu tüm bölgelerde hâlâ destekleniyor
- iOS 17.4+ service worker desteği iyileştirildi
- Push notification: iOS 16.4+'dan itibaren Web Push API desteği mevcut — ama kullanıcı izni gerektiriyor ve bazı kısıtlamalar var
- Tam ekran modu (standalone) iOS'ta çalışıyor ama bazı native API'ler hâlâ eksik (Face ID, NFC vb.)
Django URL Yapılandırması
PWA'nın üç dosyası (manifest.json, sw.js, offline/) Django'nun URL yönlendiricisinden geçmelidir — statik dosya olarak değil, çünkü tarayıcı bu dosyaları root scope'dan bekler:
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
# manifest.json — tarayıcı bu URL'yi arar
path('manifest.json', TemplateView.as_view(
template_name='manifest.json',
content_type='application/manifest+json'
), name='manifest'),
# Service Worker — root scope'da olmalı (/sw.js)
path('sw.js', TemplateView.as_view(
template_name='sw.js',
content_type='application/javascript'
), name='service-worker'),
# Offline sayfası — cache'te tutulur
path('offline/', TemplateView.as_view(
template_name='offline.html'
), name='offline'),
]
Bu URL'leri i18n_patterns() dışında, dil öneki almadan tanımlayın. Aksi takdirde tarayıcı /sw.js yerine /tr/sw.js arar ve service worker kaydı başarısız olur.
Web App Manifest
Manifest dosyası, tarayıcıya uygulamanızın nasıl kurulacağını söyler:
{
"name": "AnomixLabs App",
"short_name": "Anomix",
"description": "Django ile güçlendirilmiş web uygulaması",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1a1a2e",
"orientation": "portrait-primary",
"icons": [
{
"src": "/static/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/static/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Manifest Alan Referansı
En sık kullanılan manifest.json alanlarının açıklaması:

| Alan | Açıklama |
|---|---|
| name | Uygulamanın tam adı. Splash screen ve yükleme ekranında görünür. Belirtilmezse short_name kullanılır. |
| short_name | Ana ekranda ikon altında görünen kısa ad. 12 karakteri geçmemesi önerilir. |
| start_url | Uygulama ikonuna tıklandığında açılacak URL. "/" ana sayfayı işaret eder. |
| display | standalone: adres çubuğu gizli — en yaygın kullanım. fullscreen: tam ekran. minimal-ui: küçük geri butonu. browser: normal tarayıcı görünümü. |
| background_color | Splash screen (açılış) arka plan rengi. CSS yüklenmeden önce görünür. |
| theme_color | Android Chrome'da tarayıcı araç çubuğu rengi ve görev çubuğu rengi. HTML'de <meta name="theme-color"> ile eşleşmeli. |
| icons | Farklı boyutlar için ikon listesi. Chrome minimum 192×192 ve 512×512 gerektirir. purpose: "maskable" Android'de yuvarlak ikon için gerekli. Tüm boyutlar: 72, 96, 128, 144, 152, 192, 384, 512 piksel. |
| shortcuts | İkon uzun basıldığında (long press) açılan hızlı erişim menüsü. Her kısayol için name, url ve icons tanımlanır. |
| orientation | Desteklenen ekran yönü. "any" hem dikey hem yatay çalışır. |
| prefer_related_applications | true yapılırsa tarayıcı PWA yerine Play Store/App Store uygulamasını önerir. Genellikle false bırakılır. |
| lang / dir | İçeriğin dili ("tr-TR", "en-US") ve yazı yönü ("ltr" veya "rtl"). |
Django'da bu dosyayı staticfiles ile servledin ve HTML head'e bağlantı ekleyin:
<!-- base.html <head> bölümüne ekle -->
<link rel="manifest" href="{% url 'manifest' %}">
<meta name="theme-color" content="#1a1a2e">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="AnomixLabs">
<link rel="apple-touch-icon" href="{% static 'icons/icon-192.png' %}">
Service Worker: Temel Yapı
Service Worker, arka planda çalışan JavaScript dosyasıdır. Ana thread'dan bağımsız çalışır ve network isteklerini intercept eder:
// sw.js — Service Worker
const CACHE_NAME = 'anomix-v1';
const STATIC_ASSETS = [
'/',
'/static/css/main.css',
'/static/js/app.js',
'/offline/',
];
// Install: statik dosyaları cache'e al
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
// Activate: eski cache'leri temizle
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
);
self.clients.claim();
});
// Fetch: önce cache, sonra network (Cache-First strateji)
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) return cached;
return fetch(event.request).catch(() => caches.match('/offline/'));
})
);
});
Django'da Service Worker'ı kaydetmek:
// main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(reg => console.log('SW registered:', reg.scope))
.catch(err => console.log('SW failed:', err));
});
}
Önemli: Service Worker dosyasının Django URL'lerinden önce root path'de (/sw.js) servis edilmesi için URL yapılandırması gerekir:
# urls.py
from django.views.generic import TemplateView
urlpatterns = [
path('sw.js', TemplateView.as_view(
template_name='sw.js',
content_type='application/javascript'
), name='service-worker'),
]
Workbox: Service Worker Yönetimini Kolaylaştıran Kütüphane
Google'ın Workbox kütüphanesi, service worker stratejilerini hazır API ile sunar:
// sw.js with Workbox
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Statik dosyaları precache et
precacheAndRoute(self.__WB_MANIFEST);
// Görseller: Cache-First (30 gün)
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 3600 })]
})
);
// API istekleri: Network-First
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({ cacheName: 'api-cache', networkTimeoutSeconds: 3 })
);
// Sayfalar: Stale-While-Revalidate
registerRoute(
({ request }) => request.mode === 'navigate',
new StaleWhileRevalidate({ cacheName: 'pages' })
);
Cache Stratejileri
- Cache-First: Önce cache bak, yoksa ağdan al. Logo, font, statik CSS/JS için ideal.
- Network-First: Önce ağdan al, başarısız olursa cache'e bak. API ve dinamik sayfa için.
- Stale-While-Revalidate: Cache'ten hemen dön, arka planda güncelle. Haber sayfaları için ideal.
- Cache-Only: Sadece cache — önceden yüklenen offline içerik için.
- Network-Only: Cache kullanma — ödeme sayfaları gibi kritik işlemler için.
Web Push API: Django ile Push Bildirim
Web Push API, kullanıcının tarayıcıyı açmadan bile bildirim almasını sağlar. VAPID (Voluntary Application Server Identification) anahtarı gerektirir:
# pip install pywebpush
from pywebpush import webpush, WebPushException
def send_push_notification(subscription_info, message):
try:
webpush(
subscription_info=subscription_info,
data=json.dumps({'title': 'Anomix', 'body': message}),
vapid_private_key=settings.VAPID_PRIVATE_KEY,
vapid_claims={'sub': 'mailto:info@anomixlabs.com'}
)
except WebPushException as ex:
logger.error('Push notification failed: %s', ex)
iOS kısıtlaması: iOS'ta push bildirim yalnızca home screen'e eklenmiş PWA'larda çalışır — tarayıcıda açık sayfada çalışmaz (iOS 16.4+). Android'de browser'dan da çalışır.
App Badging API
App Badging API, uygulama ikonuna okunmamış sayısı ekler — email veya mesajlaşma uygulamaları için:
// Bildirim sayısını badge'e yaz
if ('setAppBadge' in navigator) {
navigator.setAppBadge(unreadCount);
}
// Badge'i temizle
if ('clearAppBadge' in navigator) {
navigator.clearAppBadge();
}
Offline Deneyimi: /offline/ Sayfası
Kullanıcı offline olduğunda ve istenen sayfa cache'de yokken gösterilecek fallback sayfası:

{% extends 'base.html' %}
{% block content %}
<div class="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 text-on-surface/20 mb-6"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M3 15a4 4 0 004 4h10a5 5 0 002.45-9.34A7 7 0 106.33 15H3z" />
</svg>
<h1 class="text-2xl font-bold mb-2">İnternet bağlantısı yok</h1>
<p class="text-on-surface/60 mb-8">Bu sayfa çevrimdışı mevcut değil.</p>
<button onclick="window.location.reload()"
class="px-6 py-3 bg-primary text-white rounded-xl font-medium
hover:opacity-90 transition-opacity">
Tekrar dene
</button>
</div>
{% endblock %}
Yükleme Bildirimi: beforeinstallprompt
Kullanıcılara PWA'yı ana ekrana eklemelerini teklif etmek için beforeinstallprompt olayını yakalayın. Tarayıcı bu olayı PWA kriterleri karşılandığında otomatik tetikler — sizin yapmanız gereken varsayılan davranışı engelleyip kendi UI'nızı göstermek:

<!-- base.html — </body> öncesine ekle -->
<div id="pwa-banner" class="hidden fixed bottom-5 left-5 z-50
flex items-center gap-3 px-4 py-3 rounded-2xl
bg-surface-container border border-on-surface/10 shadow-lg">
<span class="text-sm text-on-surface">Uygulamayı ana ekrana ekle</span>
<button id="pwa-install"
class="px-4 py-1.5 bg-primary text-white text-sm rounded-xl font-medium">
Yükle
</button>
<button id="pwa-dismiss" class="text-on-surface/40 text-lg leading-none">
×
</button>
</div>
let deferredPrompt;
const banner = document.getElementById('pwa-banner');
const installBtn = document.getElementById('pwa-install');
const dismissBtn = document.getElementById('pwa-dismiss');
// Tarayıcı kurulum promtunu hazırladığında
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault(); // varsayılan banner'ı engelle
deferredPrompt = e;
banner.classList.remove('hidden');
});
// "Yükle" butonuna tıklandığında
installBtn?.addEventListener('click', async () => {
banner.classList.add('hidden');
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log('PWA install:', outcome); // 'accepted' | 'dismissed'
deferredPrompt = null;
});
// Kapat butonunu dinle
dismissBtn?.addEventListener('click', () => banner.classList.add('hidden'));
// Uygulama başarıyla yüklendiğinde
window.addEventListener('appinstalled', () => {
banner.classList.add('hidden');
console.log('PWA yüklendi');
});
// PWA mı yoksa normal browser'da mı açıldığını tespit et
const isPwa = navigator.standalone
|| window.matchMedia('(display-mode: standalone)').matches;
console.log('Başlatma modu:', isPwa ? 'PWA' : 'browser');
Tarayıcı desteği: beforeinstallprompt Chrome/Edge'de çalışır (Android ve masaüstü). Safari bu olayı tetiklemez — iOS kullanıcılarına "Paylaş → Ana Ekrana Ekle" yönlendirmesini manuel olarak gösterin (örneğin navigator.standalone === false kontrolüyle).
Lighthouse PWA Audit ile 100 Puan
Lighthouse PWA audit kriterleri:
- HTTPS zorunlu
- Service Worker kayıtlı ve aktif
- Web App Manifest tüm zorunlu alanlarla dolu
- 192x192 ve 512x512 maskable icon
- Yönlendirme yoksa start_url cache'de mevcut
- HTTP → HTTPS yönlendirmesi
- theme-color meta tag
Özet
Django projesini PWA'ya dönüştürmek 3 ana adım: manifest.json yaz, service worker ekle (/sw.js), HTTPS'i doğrula. Workbox production'da service worker yönetimini kolaylaştırır. Push bildirim için pywebpush kütüphanesi ve VAPID anahtarı gerekli. iOS 17.4 sonrasında PWA desteği istikrarlı — ancak iOS'taki bazı native API kısıtlamaları devam ediyor.
Sıkça Sorulan Sorular
iOS'ta PWA push bildirimi çalışıyor mu? expand_more
PWA App Store'a yüklenebilir mi? expand_more
Service Worker'ı nasıl güncelleme stratejisi belirlemeliyim? expand_more
Offline-first mi, cache-first mi tercih edilmeli? expand_more
PWA'nın dezavantajları neler? expand_more
django-pwa paketi kullanmalı mıyım? expand_more
Service Worker CSRF token sorununu nasıl çözerim? expand_more
Ali Kasımoğlu
Full-stack Geliştirici & AnomixLabs Kurucusu
Python ve Django ekosisteminde uzmanlaşmış bir yazılım geliştirici. Modern web mimarileri, yapay zeka entegrasyonları ve minimalist kullanıcı deneyimleri üzerine odaklanıyor. AnomixLabs çatısı altında, karmaşık problemleri yalın ve etkili dijital çözümlere dönüştürmeyi hedefliyor.