PWA (progressive web app), web teknolojileriyle yapılan ama kullanıcıya platforma özgü bir uygulama gibi deneyim sunan web uygulamasıdır. Cihaza kurulabilir, kendi simgesiyle ayrı bir pencerede açılabilir ve service worker ile çevrimdışı çalışabilir. Bir sitenin kurulabilir olması için en azından bir web app manifest dosyası ve HTTPS gerekir; service worker kurulum için şart değildir ama çoğu PWA onu kullanır.
Bu yazıda PWA'nın ne olduğunu, manifest dosyasını ve önemli üyelerini (name, icons, start_url, scope, id, display), kurulabilirlik koşullarını, kendi kurulum düğmenizi ve MDN'in en iyi uygulama önerilerini anlatıyoruz. URL çözümleme ve manifest denetimi örneklerini Node.js 24 ile çalıştırdık. Çevrimdışı kısım için service worker ve önbellek stratejileri yazılarına, tüm konular için Web API öğrenme rehberine bakabilirsiniz.
PWA nedir? Web sitesi ile platform uygulamasının birleşimi
MDN, PWA'yı iki dünyayı karşılaştırarak anlatır. Platforma özgü uygulamalar belirli bir işletim sistemi için geliştirilir ve genellikle uygulama mağazasından dağıtılır. Avantajları bellidir: cihazda kendi simgeleri vardır, kullanıcı onlarla etkileşimde değilken ve cihaz çevrimdışıyken çalışabilirler, kendi arayüzlerini kurabilirler, işletim sistemiyle bütünleşip kamera, konum gibi cihaz özelliklerine erişebilirler.
Geleneksel web siteleri ise "kullanıcının sahip olduğu bir şey" olmaktan çok "ziyaret ettiği bir yer"dir: kullanıcı açmadığında cihazda bir varlığı yoktur ve ağ bağlantısına sıkı sıkıya bağlıdır. Buna karşın tek bir kod tabanıyla farklı işletim sistemlerinde çalışır, arama motorlarında dizinlenir ve yalnızca bir URL ile paylaşılabilir; herhangi bir mağazaya kaydolmak gerekmez.
PWA bu iki tarafın iyi yanlarını birleştirir. Standart web teknolojileriyle yazılır ve doğrudan web'den erişilir; ama cihaza kurulabilir, simge edinir, tarayıcı arayüzü olmadan bağımsız bir uygulama gibi açılabilir, çevrimdışı ve arka planda çalışabilir, işletim sisteminin bildirim sistemini kullanabilir ve uygulama mağazalarında da dağıtılabilir. Teknik olarak yine bir web sitesidir; onu çalıştıran ve yöneten bir tarayıcı motorudur.
MDN'in bir başka vurgusu progressive enhancement (aşamalı iyileştirme) ilkesidir. Kullanıcı PWA'nızı kuramayan bir tarayıcıyla açtığında site normal bir web sitesi gibi çalışmalıdır. Gelişmiş API'ler özellik tespitiyle kontrol edilmeli ve desteklenmeyen ortamda kabul edilebilir bir yedek deneyim sunulmalıdır. PWA'ların tek sayfalık uygulama (SPA) olması gerekmez; SPA'lar da PWA olmak zorunda değildir. Tek sayfalık uygulamalarda adres yönetimi için History API yazısına bakabilirsiniz.
Bir PWA'nın teknik parçaları

Her PWA en az bir HTML sayfasına ve normal bir sitenin sahip olduğu her şeye sahiptir. Bunun üstüne iki parça eklenir. Birincisi zorunlu olan web app manifest dosyasıdır: tarayıcının uygulamayı kurmak için gereken bilgileri (ad, simge vb.) ve görünüm ile davranış ayrıntılarını verir. İkincisi isteğe bağlı service worker'dır: MDN'e göre service worker, sayfaların arayüzü, service worker'ın ise çevrimdışı ve arka plan işlerini destekleyen bir arka uç gibi davrandığı bir mimariyi teşvik eder.
| Parça | Zorunlu mu | Ne sağlar |
|---|---|---|
| Web app manifest | Evet, kurulabilirlik için | Ad, simge, başlangıç adresi, görüntüleme modu, renkler |
| HTTPS veya localhost | Evet, kurulabilirlik için | Güvenli bağlantı; service worker için de gerekir |
| Service worker | Hayır | Çevrimdışı çalışma, arka plan işleri, push |
| Duyarlı tasarım ve özellik tespiti | Önerilen | Her cihazda ve tarayıcıda kullanılabilirlik |
Web app manifest dosyası
Manifest, tek bir JSON nesnesi içeren bir metin dosyasıdır ve en üst düzeydeki anahtarlarına üye (member) denir. Belirtimde tüm üyeler isteğe bağlıdır, ama kurulabilirlik için bazıları gerekir. MDN'in örnek manifestine benzer bir dosya şöyle görünür:
{
"short_name": "Sipariş",
"name": "Sipariş Takip",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
],
"start_url": ".",
"display": "standalone",
"theme_color": "black",
"background_color": "white"
}Dosya, sayfanın head bölümündeki bir link öğesiyle bağlanır. PWA birden çok sayfadan oluşuyorsa her sayfa manifesti aynı biçimde göstermelidir. Belirtimde tanımlı uzantı .webmanifest'tir ve yanıtın Content-Type: application/manifest+json olması beklenir; tarayıcılar genellikle .json uzantısını (application/json) da kabul eder. Manifestin getirilmesi kimlik bilgisi gerektiriyorsa, dosya aynı origin'de olsa bile crossorigin="use-credentials" özniteliği eklenmelidir. HTML tarafındaki link öğesinin ayrıntıları için HTML öğrenme rehberine bakabilirsiniz.
<head>
<link rel="manifest" href="app.webmanifest">
<!-- Kimlik bilgisi gerekiyorsa: -->
<!-- <link rel="manifest" href="app.webmanifest" crossorigin="use-credentials"> -->
</head>Kurulabilirlik koşulları
MDN'e göre Chromium tabanlı tarayıcılar (Chrome, Samsung Internet, Edge) bir manifestte şu üyeleri arar: name ya da short_name; 192 piksel ve 512 piksel simge içeren icons; start_url; display ve/veya display_override; ayrıca prefer_related_applications ya hiç olmamalı ya da false olmalıdır. Diğer tarayıcıların kuralları ve kurulumu destekleyip desteklemediği platforma göre değişir; güncel durum için MDN uyumluluk tablosuna bakın.
İkinci koşul bağlantıdır: kurulabilir olmak için uygulama https üzerinden ya da yerel geliştirmede localhost veya 127.0.0.1 (port numarasıyla ya da portsuz) üzerinden sunulmalıdır. MDN bunun güvenli bağlam kuralından daha katı olduğunu belirtir, çünkü güvenli bağlam file:// adreslerini de güvenli sayar; kurulabilirlik saymaz. Yayındaki sitede HTTPS kurulumu için HTTPS rehberine, HTTP üzerinden yüklenen kaynakların yarattığı sorun için karma içerik yazısına bakabilirsiniz.
Bu koşulları elle kontrol etmek kolay değildir; aşağıdaki küçük fonksiyon Chromium listesindeki üyeleri bir manifest metninde arar. Tarayıcının gerçek kararını vermez, yalnızca eksik üyeleri yakalamak için yayından önce kullanılabilecek bir denetimdir. Node.js 24 ile çalıştırdık; JSON ayrıştırma için JSON.parse yazısına bakabilirsiniz.
function kurulabilirlikKontrol(metin) {
const m = JSON.parse(metin);
const eksik = [];
if (!m.name && !m.short_name) eksik.push("name veya short_name");
const boyutlar = (m.icons ?? []).flatMap((i) => (i.sizes ?? "").split(" "));
if (!boyutlar.includes("192x192")) eksik.push("192px ikon");
if (!boyutlar.includes("512x512")) eksik.push("512px ikon");
if (!m.start_url) eksik.push("start_url");
if (!m.display && !m.display_override) eksik.push("display");
if (m.prefer_related_applications === true) eksik.push("prefer_related_applications false olmalı");
return eksik;
}
const manifest = `{
"name": "Sipariş Takip",
"short_name": "Sipariş",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
],
"start_url": "/",
"display": "standalone"
}`;
console.log(JSON.stringify(kurulabilirlikKontrol(manifest))); // []
console.log(JSON.stringify(kurulabilirlikKontrol('{"name":"Deneme","icons":[]}')));
// ["192px ikon","512px ikon","start_url","display"]Önemli manifest üyeleri

| Üye | Ne işe yarar | Belirtilmezse |
|---|---|---|
| name | Uygulamanın tam adı; kurulum isteminde ve açılış ekranında görünür | Kurulabilirlik için name veya short_name gerekir |
| short_name | Ana ekran gibi dar alanlarda kullanılan kısa ad | Tarayıcı name değerini kullanır |
| icons | Farklı bağlamlar için simge listesi (src, sizes, type, purpose) | Kurulabilirlik için 192 ve 512 piksel gerekir |
| start_url | Simgeye dokunulduğunda açılacak adres | Manifesti bağlayan sayfanın adresi |
| scope | Uygulamaya ait sayfaların üst adres yolu | start_url adresinden dosya adı, sorgu ve parça atılarak bulunur |
| id | Uygulamanın benzersiz kimliği | start_url değeri kullanılır |
| display | Görüntüleme modu: fullscreen, standalone, minimal-ui, browser | browser |
| display_override | display öncesinde sırayla denenecek modlar dizisi | Yalnızca display uygulanır |
| theme_color | Araç çubuğu ve durum çubuğu gibi arayüz öğelerinin rengi | Tarayıcı varsayılanı |
| background_color | Stil dosyaları yüklenmeden önceki pencere arka planı | Tarayıcı varsayılanı |
| shortcuts | Simgede açılan menüdeki kısayollar (name ve url zorunlu) | Kısayol menüsü olmaz |
Göreli adresler manifest dosyasının adresine göre çözülür; sayfanın adresine göre değil. MDN'in icons sayfasındaki örnekte https://example.com/manifest.json için images/icon-192x192.png değeri https://example.com/images/icon-192x192.png olur. Manifest bir alt dizindeyse sonuç değişir. scope belirtilmediğinde nasıl türetildiğini de aynı mantıkla gösterebiliriz:
const manifestURL = "https://example.com/app/manifest.json";
console.log(new URL("images/icon-192x192.png", manifestURL).href);
// https://example.com/app/images/icon-192x192.png
console.log(new URL("./", manifestURL).href); // https://example.com/app/
console.log(new URL("/", manifestURL).href); // https://example.com/
// scope yoksa: start_url'den dosya adı, sorgu ve parça atılır
const start = new URL("panel/index.html?kaynak=pwa#ust", manifestURL);
const kapsam = new URL("./", start).href;
console.log(kapsam); // https://example.com/app/panel/start_url geçersizse (dize değilse, geçerli bir URL değilse ya da manifesti bağlayan sayfayla aynı origin'de değilse) manifesti bağlayan sayfanın adresi kullanılır. MDN, start_url değerinin tarayıcılar için bir ipucu olduğunu ve her zaman kullanılmayabileceğini, bazı tarayıcılarda ise kurulabilirlik için açıkça belirtilmesi gerektiğini not eder; herkes için varsayılan davranışı istiyorsanız "start_url": "./" yazabilirsiniz.
scope, kurulu uygulamanın hangi sayfalarda uygulama arayüzüyle görüneceğini belirler. Kapsam dışına çıkan bir gezinmede de uygulama arayüzü korunur ama tarayıcı bağlam değişikliğini göstermek için adres çubuğu gibi öğeleri gösterir. start_url kapsamın içinde değilse scope geçersiz sayılır. Bu manifest kapsamı, service worker kayıt kapsamından ayrı bir kavramdır; ikincisini service worker yazısında anlattık.
id üyesi, tarayıcının uygulamaları birbirinden ayırmasını sağlar. Kurulu bir uygulamayla eşleşmeyen bir id gören tarayıcı, aynı adresten sunulsa bile onu ayrı bir uygulama sayar. Kurulu uygulamanın kimliğiyle eşleşen bir id ise farklı bir adresten gelse bile mevcut manifestin yerine geçer. id, start_url ile aynı origin'de olmalıdır ve içindeki parça (# sonrası) her zaman yok sayılır. Başlangıç adresini ileride değiştirme ihtimaliniz varsa id değerini baştan sabitlemek, kurulu uygulamanın kimliğini korumanın yoludur.
Simgelerde purpose anahtar sözcüğü simgenin hangi bağlamda kullanılacağını söyler: any (varsayılan, her bağlam), maskable (güvenli bölge dikkate alınarak tasarlanmış, kenarları maskelenebilir simge) ve monochrome (renk bilgisi atılıp yalnızca alfa kanalının maske olarak kullanıldığı tek renk simge). Değerler boşlukla birleştirilebilir; yalnızca tanınmayan değerler verilen bir simge yok sayılır. sizes değeri 48x48 96x96 gibi genişlik x yükseklik biçimindedir, SVG için any kullanılabilir.
Görüntüleme modları: display ve display_override
display üyesi uygulama açıldığında tarayıcı arayüzünün ne kadarının görüneceğini belirler. fullscreen tüm ekranı kullanır ve tarayıcı öğelerini gizler; MDN bunu tam ekran etkileşimin önemli olduğu oyunlar için örnekler. standalone uygulamayı kendi penceresi ve simgesiyle, adres çubuğu olmadan açar ama durum çubuğu gibi öğeler kalabilir. minimal-ui geri, ileri ve yenile gibi asgari gezinme denetimlerini bırakır. browser ise normal bir sekme ya da pencere açar ve hiçbir değer verilmezse varsayılandır.
Tarayıcı istenen modu desteklemiyorsa önceden tanımlı bir sırayla bir sonrakine düşer: fullscreen, sonra standalone, sonra minimal-ui, en son browser. Bu sıra yetmediğinde display_override devreye girer: bir mod dizisi alır, diziyi sırayla dener ve desteklenen ilk modu uygular; hiçbiri desteklenmezse display değerine bakılır. Gerçekte kullanılan mod display-mode medya özelliğiyle CSS'te sorgulanabilir; bu değer manifestte istenenden farklı olabilir. display değerindeki fullscreen, Fullscreen API'den bağımsızdır: biri tüm pencereyi, diğeri tek bir öğeyi tam ekran yapar.
Kurulum: tarayıcıdan, mağazadan ve kendi düğmenizden
Kurulabilirlik koşullarını sağlayan bir uygulamayı destekleyen tarayıcı kullanıcıya kurulum için önerir; örneğin adres çubuğunda bir "Yükle" simgesi gösterebilir. Kurulum isteminde manifestteki name ve icons kullanılır. MDN, description ve screenshots üyelerinin Android'de kurulum istemine ek bilgi olarak eklendiğini belirtir. Kurulum arayüzü tarayıcıya ve platforma göre değişir. Koşulları sağlayan bir uygulama Google Play Store ve Microsoft Store gibi mağazalar için paketlenip oradan da dağıtılabilir.
Tarayıcının varsayılan arayüzü yerine kendi kurulum düğmenizi göstermek için beforeinstallprompt olayı kullanılır. MDN bu olayın standart dışı olduğunu ve şu anda yalnızca Chromium tabanlı tarayıcılarda uygulandığını açıkça belirtir; bu yüzden düğme varsayılan olarak gizli tutulur ve yalnızca olay geldiğinde gösterilir.
<button id="install" hidden>Uygulamayı yükle</button>let installPrompt = null;
const installButton = document.querySelector("#install");
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault(); // tarayıcının kendi kurulum arayüzünü engeller
installPrompt = event;
installButton.removeAttribute("hidden");
});
installButton.addEventListener("click", async () => {
if (!installPrompt) {
return;
}
const result = await installPrompt.prompt();
console.log(`Install prompt was: ${result.outcome}`);
disableInAppInstallPrompt();
});
window.addEventListener("appinstalled", () => {
disableInAppInstallPrompt();
});
function disableInAppInstallPrompt() {
installPrompt = null;
installButton.setAttribute("hidden", "");
}prompt() kurulum istemini gösterir ve sonucu bir promise ile verir: outcome değeri kullanıcı kurduysa "accepted", kapattıysa "dismissed" olur. Her BeforeInstallPromptEvent örneği için prompt() yalnızca bir kez çağrılabilir, bu yüzden çağrıdan sonra durum sıfırlanır. Kullanıcı uygulamayı tarayıcının kendi arayüzünden kurarsa da appinstalled olayı gelir ve düğme gizlenir. MDN'e göre beforeinstallprompt şu durumlarda hiç tetiklenmez: uygulama zaten kuruluysa, kurulabilirlik koşulları sağlanmıyorsa ya da uygulama o cihaza kurulamıyorsa.
MDN'e göre iyi bir PWA

- Her tarayıcıya uyum: uygulamayı farklı tarayıcı ve işletim sistemlerinde test edin, özellik tespitiyle aşamalı iyileştirme yapın. Örneğin bir formu önce düz HTML ile çalışır kurup sonra JavaScript ile zenginleştirin.
- Her cihaza uyum: duyarlı tasarım kullanın; klavye, fare, dokunma ve kalem girişlerini destekleyin; kendi düğmenizi yazmak yerine anlamsal HTML öğelerini tercih edin.
- Çevrimdışı deneyim: en azından tarayıcının genel hata sayfası yerine kendi çevrimdışı sayfanızı gösterin; mümkünse işlevlerin bir kısmını ağ yokken de çalışır tutun.
- Derin bağlantılar: uygulamanın her bölümüne benzersiz bir URL verin; kullanıcı yer imi ekleyebilsin, paylaşabilsin, arama motorları dizinleyebilsin.
- Hız: kurulu uygulamaların her zaman hızlı açılması beklenir; ölçüm için Performance API yazısına bakabilirsiniz.
- Erişilebilirlik: uygulamayı herkesin kullanabileceği biçimde tasarlayın.
- Uygulama hissi: standalone görüntüleme modu, tanımlı uygulama simgesi, tema ve arka plan renkleri,
prefers-color-schemeile tema uyumu ve gereksiz büyük başlık ve altbilgilerden arındırılmış bir arayüz. - İşletim sistemiyle bütünleşme: Notifications API ile bildirim,
file_handlersile dosya ilişkilendirme, simgede rozet ve uygulamalar arası paylaşım.
Sık yapılan hatalar
- Manifesti yalnızca ana sayfaya bağlamak. Çok sayfalı bir PWA'da her sayfa manifesti göstermelidir.
- Simge yollarını sayfaya göre düşünmek. Göreli
src,start_urlvescopedeğerleri manifest dosyasının adresine göre çözülür. - Yalnızca tek boyutta simge vermek. Kurulabilirlik listesi 192 ve 512 piksel simge ister.
start_urldeğerini kapsamın dışına koymak. Bu durumdascopegeçersiz sayılır.- Yayındaki siteyi HTTP üzerinden sunmak. Kurulabilirlik HTTPS ister;
file://da yetmez. - Özel kurulum düğmesini her tarayıcıda göstermek.
beforeinstallpromptstandart dışıdır; düğmeyi yalnızca olay geldiğinde görünür yapın. prompt()yöntemini aynı olay nesnesiyle ikinci kez çağırmak. Her olay için bir kez çağrılabilir.- Başlangıç adresini değiştirirken
idvermemiş olmak.idyoksastart_urlkimlik olarak kullanılır; adres değişince tarayıcı bunu farklı bir uygulama olarak görebilir.
Sık Sorulan Sorular
PWA olmak için service worker gerekli mi?
MDN'e göre kurulum için service worker gerekmez; manifest ve HTTPS yeterlidir. Ancak kurulu bir uygulamanın çevrimdışı da açılması beklendiği için çoğu PWA en azından bir çevrimdışı sayfa sunan service worker kullanır.
Manifest dosyasının uzantısı .json olabilir mi?
Evet. Belirtimde tanımlı uzantı .webmanifest ve tür application/manifest+json'dur, ama tarayıcılar genellikle application/json türüyle sunulan .json dosyalarını da kabul eder.
localhost üzerinde PWA kurulumunu deneyebilir miyim?
Evet. Kurulabilirlik için localhost ve 127.0.0.1 adresleri, port numarasıyla ya da portsuz, kabul edilir. Dosyayı doğrudan file:// ile açmak ise yetmez.
Uygulama kurulu olarak mı açıldı, nasıl anlarım?
Gerçekte kullanılan görüntüleme modu display-mode medya özelliğiyle sorgulanabilir; örneğin standalone modunda farklı stil uygulayabilirsiniz. Bu değer, tarayıcı istenen modu desteklemediğinde manifesttekinden farklı olabilir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · Service worker kaydı · Çevrimdışı önbellek stratejileri · Notifications API · Depolama kotası ve kalıcı depolama · HTTPS ve TLS rehberi