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ı

Bir PWA'nın teknik parçaları: Web app manifest, HTTPS veya localhost, Service worker, Duyarlı tasarım ve özellik tespiti

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çaZorunlu muNe sağlar
Web app manifestEvet, kurulabilirlik içinAd, simge, başlangıç adresi, görüntüleme modu, renkler
HTTPS veya localhostEvet, kurulabilirlik içinGüvenli bağlantı; service worker için de gerekir
Service workerHayırÇevrimdışı çalışma, arka plan işleri, push
Duyarlı tasarım ve özellik tespitiÖnerilenHer 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:

json
{
  "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.

html
<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.

javascript
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

Önemli manifest üyeleri: name, short_name, icons, start_url
ÜyeNe işe yararBelirtilmezse
nameUygulamanın tam adı; kurulum isteminde ve açılış ekranında görünürKurulabilirlik için name veya short_name gerekir
short_nameAna ekran gibi dar alanlarda kullanılan kısa adTarayıcı name değerini kullanır
iconsFarklı bağlamlar için simge listesi (src, sizes, type, purpose)Kurulabilirlik için 192 ve 512 piksel gerekir
start_urlSimgeye dokunulduğunda açılacak adresManifesti bağlayan sayfanın adresi
scopeUygulamaya ait sayfaların üst adres yolustart_url adresinden dosya adı, sorgu ve parça atılarak bulunur
idUygulamanın benzersiz kimliğistart_url değeri kullanılır
displayGörüntüleme modu: fullscreen, standalone, minimal-ui, browserbrowser
display_overridedisplay öncesinde sırayla denenecek modlar dizisiYalnızca display uygulanır
theme_colorAraç çubuğu ve durum çubuğu gibi arayüz öğelerinin rengiTarayıcı varsayılanı
background_colorStil dosyaları yüklenmeden önceki pencere arka planıTarayıcı varsayılanı
shortcutsSimgede 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:

javascript
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.

html
<button id="install" hidden>Uygulamayı yükle</button>
javascript
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

MDN'e göre iyi bir PWA: Her tarayıcıya uyum, Her cihaza uyum, Çevrimdışı deneyim, Derin bağlantılar
  • 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-scheme ile 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_handlers ile 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_url ve scope değ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_url değerini kapsamın dışına koymak. Bu durumda scope geç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. beforeinstallprompt standart 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 id vermemiş olmak. id yoksa start_url kimlik 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