Zurück zum Blog
8 Min. LesezeitTechnology

Progressive Web Apps in 2025: Indistinguishable from Native

PWAs have evolved significantly with new APIs and capabilities that blur the line between web and native apps. Discover what's possible today.

Progressive Web Apps in 2025: Indistinguishable from Native

Progressive Web Apps (PWAs) continue to evolve, gaining capabilities that once required native apps. With improved browser support and powerful new APIs, PWAs are becoming a compelling alternative to traditional native development.

What Makes a PWA in 2024?

Modern PWAs combine:

  • Installability: Add to home screen
  • Offline functionality: Service Workers
  • Native capabilities: Advanced APIs
  • App-like experience: Full-screen, smooth transitions
  • Progressive enhancement: Works everywhere, enhanced where supported

Core Technologies

Service Workers

The foundation of PWAs:

// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => response || fetch(event.request))
  );
});

Web App Manifest

Define your app's appearance:

{
  "name": "My Progressive Web App",
  "short_name": "MyPWA",
  "description": "A modern PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#007bff",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/home.png",
      "sizes": "540x720",
      "type": "image/png"
    }
  ],
  "categories": ["productivity", "utilities"],
  "shortcuts": [
    {
      "name": "New Document",
      "url": "/new",
      "icons": [{ "src": "/icons/new.png", "sizes": "96x96" }]
    }
  ]
}

Modern PWA Capabilities

1. File System Access

Read and write files like a native app:

// Open file picker
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();

// Save file
const saveHandle = await window.showSaveFilePicker({
  suggestedName: 'document.txt',
  types: [{
    description: 'Text Files',
    accept: { 'text/plain': ['.txt'] },
  }],
});

const writable = await saveHandle.createWritable();
await writable.write(contents);
await writable.close();

2. Web Share API

Native sharing:

if (navigator.share) {
  await navigator.share({
    title: 'Check out this article',
    text: 'Amazing PWA features',
    url: 'https://example.com/article',
  });
}

// Share files
await navigator.share({
  files: [file],
  title: 'My Photo',
});

3. Web Push Notifications

Engage users like native apps:

// Request permission
const permission = await Notification.requestPermission();

if (permission === 'granted') {
  // Subscribe to push
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: vapidPublicKey,
  });

  // Send subscription to server
  await fetch('/api/subscribe', {
    method: 'POST',
    body: JSON.stringify(subscription),
    headers: { 'Content-Type': 'application/json' },
  });
}
// In service worker
self.addEventListener('push', (event) => {
  const data = event.data.json();

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192x192.png',
      badge: '/icons/badge-72x72.png',
      actions: [
        { action: 'open', title: 'Open App' },
        { action: 'close', title: 'Dismiss' },
      ],
    })
  );
});

4. Background Sync

Retry failed requests:

// Register sync
await registration.sync.register('send-message');

// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'send-message') {
    event.waitUntil(sendPendingMessages());
  }
});

async function sendPendingMessages() {
  const messages = await getMessagesFromIndexedDB();

  for (const message of messages) {
    try {
      await fetch('/api/messages', {
        method: 'POST',
        body: JSON.stringify(message),
      });
      await deleteMessageFromIndexedDB(message.id);
    } catch (error) {
      // Will retry on next sync
    }
  }
}

5. Periodic Background Sync

Update content in the background:

// Request permission
const status = await navigator.permissions.query({
  name: 'periodic-background-sync',
});

if (status.state === 'granted') {
  // Register periodic sync (minimum 12 hours)
  await registration.periodicSync.register('update-content', {
    minInterval: 24 * 60 * 60 * 1000, // 24 hours
  });
}

// In service worker
self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'update-content') {
    event.waitUntil(updateContent());
  }
});

6. Badge API

Update app icon badge:

// Set badge number
navigator.setAppBadge(5);

// Clear badge
navigator.clearAppBadge();

7. Install Prompts

Control when to prompt installation:

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent default prompt
  e.preventDefault();
  deferredPrompt = e;

  // Show custom install button
  showInstallButton();
});

async function installApp() {
  if (deferredPrompt) {
    deferredPrompt.prompt();

    const { outcome } = await deferredPrompt.userChoice;

    if (outcome === 'accepted') {
      console.log('App installed');
    }

    deferredPrompt = null;
  }
}

8. Web Bluetooth

Connect to Bluetooth devices:

const device = await navigator.bluetooth.requestDevice({
  filters: [{ services: ['heart_rate'] }],
});

const server = await device.gatt.connect();
const service = await server.getPrimaryService('heart_rate');
const characteristic = await service.getCharacteristic('heart_rate_measurement');

characteristic.addEventListener('characteristicvaluechanged', (event) => {
  const heartRate = event.target.value.getUint8(1);
  console.log('Heart rate:', heartRate);
});

await characteristic.startNotifications();

9. Geolocation

Access user location:

if ('geolocation' in navigator) {
  // One-time position
  navigator.geolocation.getCurrentPosition(
    (position) => {
      const { latitude, longitude } = position.coords;
      console.log(latitude, longitude);
    }
  );

  // Watch position
  const watchId = navigator.geolocation.watchPosition(
    (position) => {
      updateMap(position.coords);
    }
  );
}

10. Media Devices

Access camera and microphone:

// Camera
const stream = await navigator.mediaDevices.getUserMedia({
  video: { facingMode: 'environment' },
  audio: false,
});

videoElement.srcObject = stream;

// Screen sharing
const screenStream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
});

Advanced Caching Strategies

1. Cache First

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((cached) => cached || fetch(event.request))
  );
});

2. Network First

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .catch(() => caches.match(event.request))
  );
});

3. Stale While Revalidate

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(event.request).then((cached) => {
        const fetchPromise = fetch(event.request).then((response) => {
          cache.put(event.request, response.clone());
          return response;
        });

        return cached || fetchPromise;
      });
    })
  );
});

4. Cache Strategies with Workbox

import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

// Images: Cache first
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
      }),
    ],
  })
);

// API: Network first
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 50,
      }),
    ],
  })
);

// Pages: Stale while revalidate
registerRoute(
  ({ request }) => request.destination === 'document',
  new StaleWhileRevalidate({
    cacheName: 'pages',
  })
);

Offline-First Architecture

IndexedDB for Storage

// Open database
const db = await new Promise((resolve, reject) => {
  const request = indexedDB.open('MyApp', 1);

  request.onerror = () => reject(request.error);
  request.onsuccess = () => resolve(request.result);

  request.onupgradeneeded = (event) => {
    const db = event.target.result;
    db.createObjectStore('posts', { keyPath: 'id' });
  };
});

// Add data
const transaction = db.transaction(['posts'], 'readwrite');
const store = transaction.objectStore('posts');
store.add({ id: 1, title: 'My Post', content: '...' });

// Query data
const getTransaction = db.transaction(['posts'], 'readonly');
const getStore = getTransaction.objectStore('posts');
const post = await getStore.get(1);

Sync Queue Pattern

class SyncQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }

  async add(request) {
    this.queue.push(request);
    await this.save();

    if (!this.processing) {
      this.process();
    }
  }

  async process() {
    this.processing = true;

    while (this.queue.length > 0) {
      const request = this.queue[0];

      try {
        await fetch(request.url, request.options);
        this.queue.shift();
        await this.save();
      } catch (error) {
        // Still offline, stop processing
        break;
      }
    }

    this.processing = false;
  }

  async save() {
    localStorage.setItem('syncQueue', JSON.stringify(this.queue));
  }
}

Performance Optimization

1. Lazy Load Routes

const routes = {
  '/': () => import('./pages/home.js'),
  '/about': () => import('./pages/about.js'),
  '/products': () => import('./pages/products.js'),
};

async function navigate(path) {
  const pageModule = await routes[path]();
  pageModule.render();
}

2. Prefetch Resources

// Prefetch next likely page
const links = document.querySelectorAll('a');
links.forEach((link) => {
  link.addEventListener('mouseenter', () => {
    const href = link.getAttribute('href');
    prefetchPage(href);
  });
});

function prefetchPage(url) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = url;
  document.head.appendChild(link);
}

3. App Shell Pattern

// Cache app shell during install
const SHELL_CACHE = 'shell-v1';
const SHELL_FILES = [
  '/',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.svg',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(SHELL_CACHE)
      .then((cache) => cache.addAll(SHELL_FILES))
  );
});

Testing PWAs

1. Lighthouse

# CLI
npm install -g lighthouse
lighthouse https://example.com --view

# Programmatic
import lighthouse from 'lighthouse';
const result = await lighthouse('https://example.com');

2. PWA Builder

Test and package your PWA:

  • Visit PWABuilder.com
  • Enter your URL
  • Get PWA score and recommendations
  • Generate app packages for stores

3. Workbox Testing

import { precacheAndRoute } from 'workbox-precaching';

// Test service worker
describe('Service Worker', () => {
  it('should cache shell files', async () => {
    const cache = await caches.open('shell-v1');
    const keys = await cache.keys();

    expect(keys.length).toBeGreaterThan(0);
  });
});

Real-World Success Stories

Twitter Lite

  • 65% increase in pages per session
  • 75% increase in Tweets sent
  • 20% decrease in bounce rate

Tinder

  • 90% reduction in load time
  • Swiping in seconds instead of minutes
  • 10x less data usage

Starbucks

  • 2x daily active users
  • Works offline for browsing menu
  • Smaller than iOS/Android apps

Distribution

1. App Stores

PWAs can be listed in:

  • Microsoft Store
  • Google Play Store (via Trusted Web Activity)
  • Samsung Galaxy Store

2. Direct Installation

Users can install directly from browser:

  • Chrome: Install button in address bar
  • Safari: Share → Add to Home Screen
  • Edge: App available button

When to Choose PWA

Great for:

  • Content-heavy apps
  • E-commerce
  • News and media
  • Social networks
  • Productivity tools

Consider native when:

  • Need cutting-edge device features
  • Complex background processing
  • High-performance games
  • Apps requiring deep OS integration

Conclusion

PWAs in 2024 are more capable than ever. With powerful APIs, better browser support, and proven success stories, they're a compelling alternative to native development.

The line between web and native continues to blur. Are you building the next great PWA?

👨‍💻

Jordan Patel

Webentwickler & Technologie-Enthusiast