UK
HomeProjectsBlogAboutContact
Uğur Kaval

AI/ML Engineer & Full Stack Developer building innovative solutions with modern technologies.

Quick Links

  • Home
  • Projects
  • Blog
  • About
  • Contact

Connect

GitHubLinkedInTwitterEmail
Download CV →RSS Feed

© 2026 Uğur Kaval. All rights reserved.

Built with Next.js 16, TypeScript, Tailwind CSS & Prisma

  1. Home
  2. Blog
  3. Progressive Web Apps in 2026: What Works and What to Skip
Web Development

Progressive Web Apps in 2026: What Works and What to Skip

Stop chasing native app parity for the sake of it. In 2026, PWAs have matured into a high-performance niche. Here is what I learned building production-grade web apps in the current ecosystem.

June 15, 2026
5 min read
By Uğur Kaval
PWAWeb DevelopmentPerformanceSQLiteService Workers
Progressive Web Apps in 2026: What Works and What to Skip

The State of Play: Why the Web Won the Middle Tier

In 2024, we were still debating if a PWA could truly replace a native app. By 2026, the question has shifted: why would you ever build a native app for anything that doesn't require low-level kernel access or proprietary AR hardware? Last quarter, I led a migration for a logistics platform where we moved from a Flutter-based mobile app back to a PWA built on Vite 7.0 and TypeScript 6.2. We didn't just do it for the sake of 'web standards'—we did it because our deployment velocity was being strangled by the App Store review cycles.

In 2026, the 'Web Install API' has finally replaced the clunky beforeinstallprompt event across all major engines, including Safari 20. This means the friction between 'visiting a site' and 'installing an app' has dropped to nearly zero. If you are building an internal tool, a B2B dashboard, or a content-heavy platform, the PWA is no longer the 'budget option'; it is the superior architectural choice.

Modern Service Workers: Beyond the Fetch Proxy

The biggest shift this year is the stabilization of Service Worker Modules. We no longer have to deal with the importScripts mess or the lack of top-level await. Modern service workers are now first-class citizens that handle complex background logic, not just caching.

One thing I learned the hard way: stop manually managing your cache keys. With the new Cache-Control: immutable and better browser-level heuristics, your service worker should focus on Background Sync and Periodic Sync rather than trying to outsmart the browser's internal cache.

// sw.js - Using ES Modules in 2026
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';

// Top-level await is now standard in SW modules
const configuration = await fetch('/api/v1/config').then(r => r.json());

precacheAndRoute(self.__WB_MANIFEST);

// Periodic Background Sync for 2026
self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'update-fleet-data') {
    event.waitUntil(updateFleetCache());
  }
});

async function updateFleetCache() {
  const response = await fetch(configuration.apiUrl + '/sync');
  const cache = await caches.open('fleet-data-v1');
  await cache.put('/api/data', response);
  
  // Notify the UI using the new BroadcastChannel API
  const channel = new BroadcastChannel('sync-status');
  channel.postMessage({ type: 'SYNC_COMPLETE', timestamp: Date.now() });
}


### Storage: OPFS is the New Standard

If you are still using IndexedDB for anything other than basic metadata, your app is effectively legacy. In 2026, the Origin Private File System (OPFS) has reached 99% support across the board. It allows us to run SQLite directly in the browser with performance that rivals native filesystem I/O. 

In our logistics app, we switched from a messy IndexedDB schema to an OPFS-backed SQLite database. We saw a 12x improvement in write speeds and a significant reduction in UI jank during heavy data synchronization. The ability to run complex SQL queries over 500MB of local data without blocking the main thread is a game changer. 

```typescript
// storage.ts - Implementing OPFS with SQLite Wasm
import initSqlJs from '@sqlite.org/sqlite-wasm';

export async function initDatabase() {
  const sqlite3 = await initSqlJs();
  
  // Access the Origin Private File System
  const opfsRoot = await navigator.storage.getDirectory();
  const fileHandle = await opfsRoot.getFileHandle('app_db.sqlite', { create: true });
  
  // Use the SyncAccessHandle for near-native performance
  const accessHandle = await fileHandle.createSyncAccessHandle();
  
  const db = new sqlite3.oo1.DB('/app_db.sqlite', 'c');
  
  db.exec(`
    CREATE TABLE IF NOT EXISTS shipments (
      id TEXT PRIMARY KEY,
      status TEXT,
      last_updated INTEGER
    );
  `);
  
  return db;
}


### What to Skip in 2026

Not everything that 'can' be done should be done. Here are the features I've stopped implementing because they cause more support tickets than they solve:

1. **Web Bluetooth for Consumer Apps**: Unless you are building a specific industrial tool, the pairing UX is still too fragmented. Users expect a 'system' pairing dialog that PWAs still struggle to bridge smoothly across iOS and Android.
2. **Aggressive Push Notifications**: In 2026, browser vendors have implemented strict 'Engagement Scores.' If users don't interact with your notifications, your PWA's background privileges get revoked. Only use push for critical, actionable events.
3. **Manual App Shell Architectures**: Modern frameworks like Next.js 16 or Remix 3 handle the hydration and serialization so well that building a custom 'App Shell' manually is a waste of time. Use the framework's built-in PWA plugins.
4. **Background Geolocation**: Even with the latest APIs, battery drain on mobile devices is a major complaint. Unless you are building a fitness tracker or a delivery app, use coarse location or request it only when the app is in the foreground.

### The Gotchas: What the Docs Don't Tell You

**The 14-Day Eviction Rule**: On iOS 19.x and 20.x, if a user doesn't open your PWA for 14 days, the system marks your storage for eviction. Even if you've requested `navigator.storage.persist()`, Apple treats this as a 'suggestion' rather than a command. Always build your app with the assumption that local data is ephemeral and must be synced to a cloud-based source of truth.

**Memory Leaks in Background Sync**: I spent three weeks tracking down a memory leak that only occurred during `periodicsync` events. It turns out that if you don't explicitly close your SQLite database handles inside the service worker event listener, the worker process stays alive longer than it should, eventually causing the OS to kill the entire PWA process, clearing your volatile state.

### Takeaway

Stop treating your PWA as a website and start treating it as a local-first application. **The single most impactful thing you can do today is migrate your data layer to OPFS and SQLite.** This move alone will provide the 'native feel' that users crave more than any fancy animation or splash screen could. The web is no longer a document viewer; it's an application runtime. Build like it.

Enjoyed this article?

Share it with your network

Uğur Kaval

Uğur Kaval

AI/ML Engineer & Full Stack Developer specializing in building innovative solutions with modern technologies. Passionate about automation, machine learning, and web development.

Related Articles

Mastering RSC: Architecture Patterns for Data-Heavy Dashboard Systems
Web Development

Mastering RSC: Architecture Patterns for Data-Heavy Dashboard Systems

May 26, 2026

Next.js App Router: Server Components, Streaming, and Caching Strategies
Web Development

Next.js App Router: Server Components, Streaming, and Caching Strategies

April 28, 2026

Scaling Data Grids with React Server Components: Lessons from Production
Web Development

Scaling Data Grids with React Server Components: Lessons from Production

April 20, 2026