Tarayıcıda Hangi Storage? localStorage, IndexedDB, Cache API, OPFS, cookieStore ve Kota Yönetimi
Küçük bir tercihi localStorage'a, sorgulanacak kayıtları IndexedDB'ye, HTTP response'larını Cache API'ye, dosyaları OPFS'e, sunucunun da okuyacağı değeri cookieStore'a yazarsın; hepsi origin kotasını paylaşır ve persist() yoksa silinebilir. Chromium 158 ve WebKit 27.2'de localStorage'ın 5 MiB sınırını, Cache API'nin max-age'i yok saymasını, OPFS'in ephemeral oturumda düşmesini, estimate()/persist() sonuçlarını ve Safari'nin 7 günlük silme kuralını gösteriyoruz.
Sürüm notu: Bu yazıdaki örnekler Angular 22.2 (TypeScript 6.0), Node.js 24 ve Playwright 1.65 ile denenmiştir; tarayıcı çıktıları Playwright'ın Chromium 158 ve WebKit 27.2 build'lerinden alınmıştır. Destek bilgileri MDN'in (Mozilla'nın web platformu dokümantasyonu) browser-compat-data (BCD) 8.1.9 ve web-features 3.46'dan (Kasım 2026) okunmuştur; Firefox bu ortamda çalıştırılamadı, Firefox satırları bu verilerden gelir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Angular istemcisi, kullanıcının okuma listesini JSON olarak localStorage'a yazıyor. Liste büyüdükçe bir gün sınır aşılacak. Aşağıdaki fonksiyon tek bir değere sığan en uzun string'i ikili arama ile buluyor:
// Tek bir localStorage değerine kaç karakter sığar?
export function localStorageLimit() {
let low = 0;
let high = 20 * 1024 * 1024;
let lastError = null;
while (low < high) {
const size = Math.ceil((low + high) / 2);
try {
localStorage.setItem("probe", "x".repeat(size));
low = size;
} catch (err) {
lastError = err;
high = size - 1;
}
}
localStorage.removeItem("probe");
console.log(`largest value: ${low} chars; error: ${lastError?.name}`);
}
Chromium ve WebKit'te kaç karakter sığar: 5 milyon civarı mı, 10 milyon mu, yoksa tarayıcı diskte yer oldukça izin mi verir? Sınır aşılınca ne olur: değer kesilip kaydedilir mi, sessizce yok sayılır mı, hata mı fırlar?
Web Storage ve cookie'nin temelini JavaScript Orta serisinin "JavaScript Web Storage ve Cookie: localStorage, sessionStorage ve document.cookie" yazısında görmüştük; token'ın nerede saklanacağı ise Frontend Güvenliği serisinin "Token Nerede Saklanmalı? localStorage, Memory, Cookie, Service Worker ve BFF'in Tehdit Modeli" yazısının konusu. Bu yazı seçim üzerine: hangi veri localStorage'a, hangisi IndexedDB'ye, Cache API'ye, OPFS'e (Origin Private File System; tarayıcının her siteye ayırdığı özel dosya sistemi) ya da cookieStore'a gider, kota nasıl ölçülür ve tarayıcı veriyi ne zaman siler.
Seçenekler Tek Tabloda
Cevap: iki motorda da aynı sayı ve aynı hata.
Chromium: largest value: 5242875 chars; error: QuotaExceededError
WebKit: largest value: 5242875 chars; error: QuotaExceededError
Chromium: 2148891 chars: write 2.4 ms, read 2.0 ms, 20000 items
WebKit: 2148891 chars: write 1.0 ms, read 3.0 ms, 20000 items
İlk iki satırdaki 5.242.875, 5 MiB'ın (5.242.880) "probe" key'inin beş karakteri düşülmüş hâli: sınır key ve value'ların toplam karakter sayısına uygulanıyor ve aşılınca QuotaExceededError fırlıyor. Değer kesilmiyor, eski değer de yerinde kalıyor. Son iki satıra yazının ilerisinde döneceğiz.
Tarayıcının sunduğu storage seçeneklerini aynı sayfada çalıştırıp hangisinin var olduğunu kontrol ettim:
Chromium: {"localStorage":true,"indexedDB":true,"caches":true,"opfs":true,"createWritable":true,"cookieStore":true,"storageBuckets":true}
WebKit: {"localStorage":true,"indexedDB":true,"caches":true,"opfs":true,"createWritable":true,"cookieStore":true,"storageBuckets":false}
| API | Veri | Erişim | Worker / service worker | Destek (BCD 8.1.9) |
|---|---|---|---|---|
localStorage | string | senkron | yok | tüm tarayıcılar |
| IndexedDB | object, Blob, ArrayBuffer | asenkron, transaction'lı | var | Baseline (2021'den beri) |
| Cache API | Request → Response çiftleri | asenkron | var | Chrome 43, Firefox 41, Safari 11.1 |
| OPFS | dosya ve klasör (byte) | asenkron; dedicated worker'da senkron | var | Baseline (Mart 2023'ten beri) |
cookieStore | cookie | asenkron, change event'li | var (Safari'de service worker'daki change event'i yok) | Chrome 87, Firefox 140, Safari 18.4 |
| Storage Buckets | yukarıdakilerin ayrı gruplara bölünmesi | asenkron | var | yalnız Chromium 122+, experimental |
Seçimi iki soru belirliyor. Birincisi verinin tipi: tek bir tercih mi, sorgulanacak bir kayıt listesi mi, bir HTTP response'u mu, yoksa bir dosya mı? İkincisi onu kimin okuyacağı: yalnız sayfa mı, bir worker ya da service worker mı, sunucu mu? Bookstore için kararlar şöyle:

IndexedDB'yi bu yazıda yalnız yerini göstermek için anıyorum; schema versiyonlama, transaction'lar ve Angular'da kullanımı bir sonraki yazının konusu.
localStorage: Küçük, Senkron ve 5 MiB
localStorage'ın asıl kısıtları senkron olması ve service worker ile worker'lardan erişilememesi. Senkron API'nin maliyetini 20.000 kayıtlık bir okuma listesini yazıp okuyarak ölçtüm:
// 20.000 kayıtlık okuma listesi main thread'de localStorage'a yazılıp okunuyor
export function readingListInLocalStorage() {
const list = Array.from({ length: 20_000 }, (_, i) => ({
isbn: String(9780000000000 + i),
title: `Book ${i}`,
addedAt: new Date(Date.UTC(2026, 10, 1, 0, 0, i)).toISOString(),
note: "Read after Dune",
}));
let started = performance.now();
const json = JSON.stringify(list);
localStorage.setItem("readingList", json);
const writeMs = performance.now() - started;
started = performance.now();
const back = JSON.parse(localStorage.getItem("readingList"));
const readMs = performance.now() - started;
console.log(`${json.length} chars: write ${writeMs.toFixed(1)} ms, read ${readMs.toFixed(1)} ms, ${back.length} items`);
localStorage.removeItem("readingList");
}
Önce Tahmin Et bölümündeki son iki satır bu fonksiyonun çıktısı: 2,1 milyon karakterlik JSON'un yazılması 1–2,5 ms, okunup parse edilmesi 2–3 ms sürdü. Bu sayılar beklediğimden küçük; modern motorlar localStorage'ı bellekte de tutuyor. Yani "localStorage main thread'i dakikalarca kilitler" iddiası bu boyutta doğru değil. Sorun başka yerlerde. 5 MiB'ın üstüne hiç çıkamıyorsun. Bir input'un her değişikliğinde koca listeyi stringify edip yazan kod, bu birkaç milisaniyeyi her tuş vuruşunda ödüyor. Değerler yalnız string; tarih ve Blob gibi tipler dönüşümde kayboluyor. Service worker localStorage'ı göremiyor; offline senaryoda kullanacağın veri başka yerde olmalı. Tek bir değer, tek bir tercih için ise localStorage hâlâ en basit seçim: tema bunun tipik örneği.
Cache API: Response Saklayan Storage
Cache API, HTTP cache'i değil; adı benzese de ayrı bir storage. Request'i key, Response'u value olarak saklıyor, header'larıyla birlikte:
// Cache API, Request/Response çiftlerini saklar; HTTP cache'i değildir
export async function cacheCatalog() {
const cache = await caches.open("catalog-v1");
await cache.add("/api/books"); // response'u indirip saklar; 2xx olmayan status'ta hata verir
const cached = await cache.match("/api/books");
const books = await cached.json();
console.log(`cached: ${books.length} books, ETag ${cached.headers.get("ETag")}, Cache-Control ${cached.headers.get("Cache-Control")}`);
console.log("keys:", (await cache.keys()).map((request) => new URL(request.url).pathname).join(", "));
console.log("caches:", (await caches.keys()).join(", "));
// yok sayılmasını istemedikçe query string farklı bir key demek
console.log("match with ?page=1:", Boolean(await cache.match("/api/books?page=1")));
console.log("ignoreSearch:", Boolean(await cache.match("/api/books?page=1", { ignoreSearch: true })));
}
cached: 6 books, ETag "5c066e3af808", Cache-Control max-age=60
keys: /api/books
caches: catalog-v1
match with ?page=1: false
ignoreSearch: true
Response Cache-Control: max-age=60 ile geldi ama Cache API bunu yorumlamıyor: kayıt sen silene kadar orada, 60 saniye sonra da match aynı response'u döndürür. Bayatlama kararı tamamen senin kodunda; Service Worker yazısında stale-while-revalidate stratejisini bunun üzerine kuracağız. Key URL'nin tamamı: query string'i farklı bir istek eşleşmiyor, ignoreSearch bunu değiştiriyor. cache.add 2xx olmayan bir response'u saklamak yerine reject ediyor; cache.put ise verdiğin her şeyi saklıyor, bu yüzden 500 dönen bir hata sayfasını cache'e yazmamak için status'u kendin kontrol etmelisin. Cache API'yi sayfadan da kullanabilirsin ama asıl kullanıcısı service worker; offline katalog yanıtları bu yüzden burada.
OPFS: Tarayıcının Dosya Sistemi
OPFS, her origin'e ayrılmış ve kullanıcının diskinde görünmeyen bir dosya sistemi. Büyük binary veriler için IndexedDB'den daha doğal: dosyayı parça parça yazabilir, belirli bir offset'ten okuyabilirsin. Bookstore'da kullanıcının indirdiği örnek bölüm PDF'i buraya yazılıyor:
// Origin Private File System: yalnız bu origin'in gördüğü dosyalar, kullanıcının diski değil
export async function saveSampleChapter() {
const pdf = new Uint8Array(2 * 1024 * 1024).fill(37); // Dune'un örnek bölümü yerine 2 MB'lık veri
const root = await navigator.storage.getDirectory();
const samples = await root.getDirectoryHandle("samples", { create: true });
const handle = await samples.getFileHandle("dune-chapter-1.pdf", { create: true });
const started = performance.now();
const writable = await handle.createWritable();
await writable.write(pdf);
await writable.close(); // dosya ancak stream kapanınca değişir
const file = await handle.getFile();
console.log(`main thread: wrote ${file.size} bytes in ${(performance.now() - started).toFixed(1)} ms`);
const names = [];
for await (const name of samples.keys()) names.push(name);
console.log("samples/:", names.join(", "));
}
// createSyncAccessHandle yalnız dedicated worker'da var: senkron ve hızlı okuma/yazma
self.onmessage = async ({ data: path }) => {
const [dir, name] = path.split("/");
const root = await navigator.storage.getDirectory();
const handle = await (await root.getDirectoryHandle(dir)).getFileHandle(name);
const access = await handle.createSyncAccessHandle(); // dosyayı close()'a kadar kilitler
try {
const size = access.getSize();
const buffer = new Uint8Array(size);
const started = performance.now();
const read = access.read(buffer, { at: 0 });
self.postMessage(`worker: read ${read} of ${size} bytes in ${(performance.now() - started).toFixed(1)} ms, first byte ${buffer[0]}`);
} finally {
access.close();
}
};
Chromium:
main thread: wrote 2097152 bytes in 3.6 ms
samples/: dune-chapter-1.pdf
worker: read 2097152 of 2097152 bytes in 0.7 ms, first byte 37
WebKit, default (ephemeral) context:
UnknownError: The operation failed for an unknown transient reason (e.g. out of memory).
WebKit, persistent profile:
main thread: wrote 2097152 bytes in 7.0 ms
samples/: dune-chapter-1.pdf
worker: read 2097152 of 2097152 bytes in 0.0 ms, first byte 37
Main thread'de createWritable bir stream veriyor; dosya ancak close() çağrılınca değişiyor, yarıda kalan bir yazma eski içeriği bozmuyor. Dedicated worker'da ise createSyncAccessHandle senkron read/write sunuyor ve dosyayı close()'a kadar kilitliyor; SQLite'ın WebAssembly sürümü gibi kütüphanelerin tarayıcıda veritabanı dosyasını tuttuğu yer burası. Worker temeli için JavaScript İleri serisinin "JavaScript Web Workers: postMessage, Transferable Objects ve Module Worker" yazısına bakabilirsin.
WebKit satırları önemli bir ayrıntı gösteriyor. Playwright'ın varsayılan WebKit context'i (diske bir şey yazmayan, ephemeral bir oturum) OPFS'in ilk çağrısında UnknownError verdi; aynı kod kalıcı bir profille sorunsuz çalıştı. createWritable Safari'ye 26 ile geldi, OPFS'in kendisi ise 15.2'den beri var. Gerçek kullanıcıda gizli pencere, kısıtlanmış bir WebView ya da dolu bir disk aynı sonucu verebilir: OPFS çağrısını her zaman try/catch içine al ve dosyayı saklayamadığında indirmeye ya da ağdan okumaya geri dön.
cookieStore: Asenkron Cookie API'si
document.cookie tek bir string; okumak için parse etmek, yazmak için bütün attribute'ları elle birleştirmek gerekiyor ve bir cookie'nin değiştiğini öğrenmenin yolu yok. cookieStore bunları çözüyor:
// cookieStore: change event'leri olan asenkron cookie API'si (service worker'da da var)
export async function languageCookie() {
cookieStore.addEventListener("change", (event) => {
for (const cookie of event.changed) console.log(`change: ${cookie.name}=${cookie.value}`);
for (const cookie of event.deleted) console.log(`deleted: ${cookie.name}`);
});
await cookieStore.set({ name: "lang", value: "tr", expires: Date.now() + 365 * 24 * 60 * 60 * 1000, sameSite: "lax" });
const lang = await cookieStore.get("lang");
console.log(`get: ${lang.name}=${lang.value}, sameSite ${lang.sameSite}, path ${lang.path}`);
console.log(`document.cookie: ${document.cookie}`);
await cookieStore.delete("lang");
await new Promise((resolve) => setTimeout(resolve, 100)); // change event'leri asenkron gelir
}
Chromium:
change: lang=tr
get: lang=tr, sameSite lax, path /
document.cookie: lang=tr
deleted: lang
WebKit:
change: lang=tr
get: lang=tr, sameSite undefined, path undefined
document.cookie: lang=tr
deleted: lang
Bookstore'da dil tercihi bir cookie, çünkü sunucu da ona göre e-posta ve fatura dilini seçiyor; sadece istemcinin okuduğu bir tercih için cookie her istekte gereksiz yere sunucuya gider. cookieStore Firefox 140 ve Safari 18.4 ile üç motorda da var, ama web-features onu Baseline saymıyor: service worker'da cookie değişikliklerine abone olmayı sağlayan CookieStoreManager Safari'de yok. WebKit'in get sonucunda yalnız name ve value geldi; sameSite ve path Chromium'da dolu, WebKit'te undefined. Bu attribute'lara göre karar veren kod yazma. cookieStore yalnız güvenli bağlamda (HTTPS ya da localhost) çalışıyor ve HttpOnly cookie'leri document.cookie gibi o da göremiyor.
Kota, Eviction ve persist()
Bütün bu storage'lar (localStorage hariç) origin başına tek bir kotayı paylaşıyor. Ne kadarının kullanıldığını navigator.storage.estimate() söylüyor:
const mb = (bytes) => (bytes / 1024 / 1024).toFixed(2);
export async function quota() {
const { usage, quota, usageDetails } = await navigator.storage.estimate();
const persisted = await navigator.storage.persisted();
console.log(`usage ${mb(usage)} MB of ${(quota / 1024 ** 3).toFixed(1)} GB, persisted: ${persisted}`);
if (usageDetails) console.log("usageDetails:", JSON.stringify(usageDetails));
}
// Yalnız verinin önemli olduğunu gösteren bir kullanıcı eyleminden sonra iste (ör. "offline kütüphanemi sakla")
export async function requestPersistence() {
const granted = await navigator.storage.persist();
console.log(`persist() -> ${granted}`);
return granted;
}
Chromium: usage 0.00 MB of 10.0 GB, persisted: false
Chromium: usageDetails: {}
Chromium: usage 2.00 MB of 10.0 GB, persisted: false
Chromium: usageDetails: {"caches":919,"fileSystem":2097494}
Chromium: persist() -> false
WebKit: usage 0.00 MB of 1.0 GB, persisted: false
WebKit: persist() -> false
Chromium'un ikinci ölçümü OPFS dosyası ve Cache API kaydı yazıldıktan sonra: usageDetails kullanımı storage türüne göre ayırıyor ama yalnız Chromium'da var. Kota değerlerine fazla anlam yükleme: Playwright'ın context'leri gizli pencereye benzer oturumlar ve 10 GB ile 1 GB bu oturumların sınırları. MDN'in kota sayfasına göre normal bir profilde Chromium origin başına diskin %60'ına kadar izin veriyor; Safari 17'den beri tarayıcı uygulamasında origin başına yaklaşık %60, uygulamaya gömülü WebView'larda yaklaşık %15; Firefox best-effort veride diskin %10'u ile 10 GiB'tan küçük olanı. Değerler parmak izi (fingerprinting) çıkarılmasın diye bilerek yuvarlanıyor ve kullanım tahmini doldurulabiliyor; estimate() bir garanti değil, yazma her zaman QuotaExceededError ile düşebilir.
Veri varsayılan olarak "best-effort" modunda: disk dolduğunda tarayıcı LRU'ya (Least Recently Used; en uzun süredir kullanılmayanı önce silme) göre bir origin'in verisini, IndexedDB, Cache API ve OPFS dahil hepsini birden siliyor. navigator.storage.persist() origin'i "persistent" yapmayı istiyor; persistent veri yalnız kullanıcı tarayıcı ayarlarından silerse gidiyor. Firefox bunun için kullanıcıya izin soruyor, Chromium ve Safari ise sormadan, sitenin kullanıcıyla etkileşim geçmişine (kurulu olması, ana ekrana eklenmiş olması gibi) bakarak karar veriyor. Taze bir oturumda iki tarayıcı da false döndü, ki bu beklenen sonuç. persist()'i sayfa açılır açılmaz değil, kullanıcı offline veriyi önemsediğini gösterdiğinde çağır.
Safari'nin bir kuralı daha var. WebKit'in 2020 tarihli Intelligent Tracking Prevention duyurusuna göre, kullanıcı Safari'yi kullandığı yedi gün boyunca siteyle hiç etkileşime girmezse sitenin script ile yazdığı bütün storage'ı (localStorage, IndexedDB, service worker kaydı ve Cache API) siliniyor; sunucunun Set-Cookie ile yazdığı cookie'ler bunun dışında. Ana ekrana eklenmiş web uygulamaları Safari'nin parçası sayılmıyor ve kendi kullanım günlerini sayıyor. Pratik sonucu: Safari'de offline veri, kullanıcı siteye bir hafta uğramazsa yok olabilir. Uygulaman bu durumda veriyi sunucudan tekrar indirebilmeli; kullanıcının yerelde oluşturduğu ve henüz sunucuya gitmemiş veri (bir sonraki yazının outbox'ı gibi) bu yüzden mümkün olan ilk fırsatta gönderilmeli. Chromium'a özgü Storage Buckets ise veriyi gruplara ayırıp her grubun silinme önceliğini ayrı belirlemene izin veriyor; diğer motorlarda olmadığı için bugün yalnız ek bir iyileştirme olarak düşün.
Angular'da: Tema Tercihi ve Offline Veri Durumu
bookstore-web'e bir ayarlar sayfası ekledim. Tema localStorage'da; erişim hata fırlatabileceği için (dolu kota, kapatılmış storage, bazı gömülü tarayıcılar) okuma ve yazma try/catch içinde, uygulama storage olmadan da çalışıyor:
import { Service, signal } from '@angular/core';
export type Theme = 'light' | 'dark';
// Küçük ve senkron okunan bir tercih: yeri localStorage
@Service()
export class ThemeService {
readonly theme = signal<Theme>(this.read());
set(theme: Theme) {
this.theme.set(theme);
try {
localStorage.setItem('theme', theme);
} catch (err) {
// storage dolu, kapalı ya da engellenmiş olabilir; uygulama onsuz da çalışmalı
console.warn('theme not saved', err);
}
}
private read(): Theme {
try {
return localStorage.getItem('theme') === 'dark' ? 'dark' : 'light';
} catch {
return 'light';
}
}
}
Kota ve kalıcılık durumu ayrı bir service'te; persist() yalnız butona basılınca çağrılıyor:
@Service()
export class StorageStatusService {
async status(): Promise<StorageStatus> {
const { usage = 0, quota = 0 } = await navigator.storage.estimate();
return {
usageMb: Math.round(usage / 1024 / 1024),
quotaMb: Math.round(quota / 1024 / 1024),
persisted: await navigator.storage.persisted(),
};
}
// kullanıcı offline veriyi saklamak istediğinde, bir click handler'dan çağır
requestPersistence(): Promise<boolean> {
return navigator.storage.persist();
}
}
export class SettingsPage {
private readonly statuses = inject(StorageStatusService);
protected readonly themes = inject(ThemeService);
protected readonly result = signal('');
protected readonly storage = resource({ loader: () => this.statuses.status() });
async keep() {
const granted = await this.statuses.requestPersistence();
this.result.set(
granted
? 'Your offline library will be kept.'
: 'The browser did not grant persistent storage.',
);
this.storage.reload();
}
}
Playwright'ta sayfayı açıp temayı "dark" yaptım, butona bastım ve sayfayı yeniledim:
[chromium] Offline data: 0 MB of 10240 MB, may be deleted by the browser
[chromium] The browser did not grant persistent storage.
[chromium] after reload: theme=dark, localStorage.theme=dark
[webkit] Offline data: 0 MB of 1000 MB, may be deleted by the browser
[webkit] The browser did not grant persistent storage.
[webkit] after reload: theme=dark, localStorage.theme=dark
Tema yenilemeden sonra da yerinde; kalıcılık isteği iki tarayıcıda da reddedildi ve arayüz bunu kullanıcıya açıkça söylüyor. Reddedilmesi bir hata değil: veri yine saklanıyor, yalnız disk baskısında silinebilir.
Üretimde Dikkat
Storage'ın dolması ve silinmesi sahada sessiz olur. Uygulama açılırken estimate() ve persisted() sonucunu analytics'e gönder; kullanıcıların yüzde kaçının persistent olduğunu ve ortalama kullanımı bilmek, offline özelliklerin gerçekte nasıl davrandığını gösterir. Her QuotaExceededError'u da say.
Storage'daki verinin şeması olduğunu unutma. localStorage'daki bir JSON'un ya da Cache API'deki bir response'un biçimi uygulamanın yeni sürümünde değişebilir; key'e sürüm koy (catalog-v1) ve eski sürümleri temizle. Bir şeyi teşhis ederken Browser DevTools serisinin "Application Paneli: Storage, Cookie Flag'leri, Cache Storage ve Angular Service Worker" yazısındaki adımlar, origin'in bütün storage'ını tek ekranda görmeni sağlar.
Hassas veriyi bu storage'lara düz yazma. Aynı origin'de çalışan her script, XSS (sayfaya dışarıdan zararlı script enjekte edilmesi) ile giren dahil, hepsini okuyabilir; OPFS'in "private" olması kullanıcının dosya sisteminden gizli olması demek, başka script'lerden değil.
Sık Düşülen Tuzaklar
localStorage.setItem'ıtry/catcholmadan çağırmak. Kota doluncaQuotaExceededErrorfırlar ve bunu yakalamayan kod, örneğin bir kaydetme butonunun işini yarıda bırakır.- Büyük ve sık değişen veriyi localStorage'a JSON olarak yazmak. 5 MiB'ı aşamazsın ve her değişiklikte bütün listeyi yeniden serialize edersin; liste IndexedDB'ye ait.
- Cache API'nin
max-age'e uyduğunu sanmak. Kayıt sen silene kadar kalır; süreyi kendin yönet. cache.putile hata response'larını saklamak. Status'u kontrol etmeden yazılan bir 500 sayfası, offline'da kullanıcıya gösterilir.- OPFS ve
persist()sonucuna güvenmek. Ephemeral oturumda OPFS hiç çalışmayabilir,persist()çoğu zamanfalsedöner; ikisi için de bir yedek yolun olsun. - Safari'de offline veriyi kalıcı sanmak. Yedi gün etkileşim yoksa script ile yazılan her şey silinebilir; yerelde oluşan veriyi bekletmeden sunucuya gönder.
Küçük Bir Deneme
★☆☆ ThemeService'e bir effect ekleyip seçilen temayı document.documentElement'in data-theme attribute'una yaz. Sonra DevTools konsolunda localStorage.setItem'ı hata fırlatan bir fonksiyonla değiştirip uygulamanın çalışmaya devam ettiğini kontrol et.
★★☆ cacheCatalog'u, response'u cache.put ile yazan ve yalnız response.ok ise saklayan, yazarken de x-cached-at adında bir header ekleyen bir fonksiyona çevir. Okurken bu header'a bakıp 10 dakikadan eski kaydı bayat say.
★★★ Ayarlar sayfasına "Offline kütüphane" bölümü ekle: Dune'un örnek bölümünü OPFS'e indiren, boyutunu ve estimate() ile ölçülen toplam kullanımı gösteren, "sil" butonuyla dosyayı kaldıran bir arayüz. OPFS çalışmadığında (Playwright'ın varsayılan WebKit context'inde dene) arayüz indirme yerine "bu tarayıcıda offline saklama kullanılamıyor" demeli.
Okuma listesini bu yazıda localStorage'dan çıkarıp IndexedDB'ye taşımaya karar verdik ama nasıl yapılacağını göstermedik. Bir sonraki yazıda IndexedDB'yi Angular'da kullanacağız: onupgradeneeded ile schema versiyonlama, transaction'ların ne zaman kendiliğinden kapandığı, başka bir sekmenin upgrade'i nasıl blokladığı ve offline'da eklenen yorumları bağlantı gelince çift kayıt oluşturmadan gönderen bir outbox.