Cloud-Native

SPA Performance-Architektur: Service Worker, Shared Worker und App Shell

Vollständige Architektur für performante SPAs: Service Worker Caching, Shared Worker State-Sharing, App Shell Modell und Bootstrap-API-Optimierung.

C
CFTools Software
Autor
30. Januar 2026
7 min Lesezeit

SPA Performance-Architektur

Moderne Single-Page-Applications erfordern eine mehrschichtige Caching- und State-Architektur für optimale Performance.

Architektur-Komponenten

KomponenteAufgabeTechnologie
App ShellInstant UI-GerüstService Worker Precache
Static AssetsJS, CSS, FontsService Worker Cache First
API CacheResponses cachenService Worker SWR
Shared StateTab-übergreifendShared Worker
WebSocketRealtime-DatenShared Worker
Bootstrap CacheUser-DatenPersonalisierter Cache

App Shell Modell

Definition

Das App Shell Modell cached das minimale UI-Gerüst (HTML, CSS, JS) für sofortiges Rendering, während dynamische Inhalte nachgeladen werden.

Struktur

app-shell/
├── index.html        # Minimales HTML
├── shell.css         # Layout, Navigation
├── shell.js          # Router, Core-Logic
└── icons/            # App-Icons

Implementierung

// sw.ts - App Shell Precaching
import { precacheAndRoute } from 'workbox-precaching';

precacheAndRoute([
  { url: '/index.html', revision: '1.0.0' },
  { url: '/shell.css', revision: '1.0.0' },
  { url: '/shell.js', revision: '1.0.0' },
]);

// Navigation Requests -> App Shell
import { NavigationRoute, registerRoute } from 'workbox-routing';

registerRoute(
  new NavigationRoute(
    async () => {
      const cache = await caches.open('app-shell');
      return cache.match('/index.html') || fetch('/index.html');
    }
  )
);

Multi-Tab-Architektur mit Shared Worker

Architektur-Diagramm

┌─────────────────────────────────────────────────────┐
│                     Browser                          │
├──────────┬──────────┬──────────┬───────────────────┤
│  Tab 1   │  Tab 2   │  Tab 3   │   Service Worker  │
│          │          │          │   (Caching)       │
└────┬─────┴────┬─────┴────┬─────┴───────────────────┘
     │          │          │
     └──────────┼──────────┘
                │
        ┌───────┴───────┐
        │ Shared Worker │
        │   (State)     │
        │ - WebSocket   │
        │ - Shared Data │
        └───────┬───────┘
                │
        ┌───────┴───────┐
        │    Backend    │
        └───────────────┘

Shared Worker State Manager

// shared-worker.ts
interface AppState {
  user: User | null;
  permissions: string[];
  config: Record<string, any>;
  notifications: Notification[];
}

class SharedStateManager {
  private state: AppState = {
    user: null,
    permissions: [],
    config: {},
    notifications: [],
  };
  
  private ports: Set<MessagePort> = new Set();
  private ws: WebSocket | null = null;

  addPort(port: MessagePort) {
    this.ports.add(port);
    port.postMessage({ type: 'INIT_STATE', payload: this.state });
    
    port.onmessage = (event) => this.handleMessage(event.data, port);
  }

  private handleMessage(message: any, sourcePort: MessagePort) {
    switch (message.type) {
      case 'UPDATE_STATE':
        this.updateState(message.payload);
        this.broadcast({ type: 'STATE_CHANGED', payload: message.payload }, sourcePort);
        break;
        
      case 'WS_SEND':
        this.ws?.send(JSON.stringify(message.payload));
        break;
    }
  }

  private updateState(partial: Partial<AppState>) {
    this.state = { ...this.state, ...partial };
  }

  private broadcast(message: any, exclude?: MessagePort) {
    this.ports.forEach(port => {
      if (port !== exclude) {
        port.postMessage(message);
      }
    });
  }
}

Bootstrap-API-Caching

Strategie für authentifizierte Daten

// sw.ts
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';

// User-spezifischer Cache-Key
const userCacheKey = async (request: Request): Promise<Request> => {
  const token = await getAuthToken();
  if (!token) return request;
  
  const userId = parseJWT(token).sub;
  const url = new URL(request.url);
  url.searchParams.set('_uid', userId);
  
  return new Request(url.toString(), request);
};

registerRoute(
  ({ url }) => url.pathname.startsWith('/api/bootstrap'),
  new StaleWhileRevalidate({
    cacheName: 'bootstrap-cache',
    plugins: [{
      cacheKeyWillBeUsed: async ({ request }) => userCacheKey(request),
    }],
  })
);

Bootstrap-Endpoints

EndpointInhaltCache-Strategie
/api/bootstrap/userUser-ProfilSWR, 1h TTL
/api/bootstrap/permissionsBerechtigungenSWR, 1h TTL
/api/bootstrap/configFeature-FlagsSWR, 15min TTL
/api/bootstrap/preferencesUser-SettingsSWR, 24h TTL

Performance-Metriken

Core Web Vitals Ziele

MetrikZielBeschreibung
LCP< 2.5sLargest Contentful Paint
FID< 100msFirst Input Delay
CLS< 0.1Cumulative Layout Shift
TTFB< 200msTime to First Byte

Messbare Verbesserungen

SzenarioOhne CachingMit Architektur
Erster Besuch3-5s2-3s
Wiederholter Besuch2-4s100-300ms
OfflineNicht möglichVollständig nutzbar
Multi-Tab (5 Tabs)5x API-Last1x API-Last

Integrations-Pattern

Vue 3 Composable

// useSharedState.ts
import { ref, onMounted, onUnmounted, Ref } from 'vue';

export function useSharedState<T>(
  key: string,
  defaultValue: T
): { state: Ref<T>; update: (value: T) => void } {
  const state = ref<T>(defaultValue) as Ref<T>;
  let worker: SharedWorker | null = null;

  onMounted(() => {
    if ('SharedWorker' in window) {
      worker = new SharedWorker('/shared-worker.js');
      worker.port.start();
      
      worker.port.onmessage = (event) => {
        if (event.data.payload?.[key] !== undefined) {
          state.value = event.data.payload[key];
        }
      };
    }
  });

  const update = (value: T) => {
    state.value = value;
    worker?.port.postMessage({
      type: 'UPDATE_STATE',
      payload: { [key]: value },
    });
  };

  onUnmounted(() => worker?.port.close());

  return { state, update };
}

React Hook

// useSharedState.ts
import { useState, useEffect, useCallback } from 'react';

export function useSharedState<T>(key: string, defaultValue: T) {
  const [state, setState] = useState<T>(defaultValue);
  const [worker, setWorker] = useState<SharedWorker | null>(null);

  useEffect(() => {
    if ('SharedWorker' in window) {
      const sw = new SharedWorker('/shared-worker.js');
      sw.port.start();
      
      sw.port.onmessage = (event) => {
        if (event.data.payload?.[key] !== undefined) {
          setState(event.data.payload[key]);
        }
      };
      
      setWorker(sw);
      return () => sw.port.close();
    }
  }, [key]);

  const update = useCallback((value: T) => {
    setState(value);
    worker?.port.postMessage({
      type: 'UPDATE_STATE',
      payload: { [key]: value },
    });
  }, [worker, key]);

  return [state, update] as const;
}

Tooling Landscape

KategorieTools
Service WorkerWorkbox, vite-plugin-pwa, @angular/pwa
State ManagementPinia, Redux, Zustand
Performance TestingLighthouse, WebPageTest, Chrome DevTools
Monitoringweb-vitals, Sentry, LogRocket

Verwandte Themen

  • Progressive Web Apps (PWA)
  • Core Web Vitals
  • HTTP/2 Push
  • Resource Hints (preload, prefetch)
  • Code Splitting

CFTools Software entwickelt performante SPA-Architekturen für Enterprise-Anwendungen.

Tags:
SPA
Web Performance
Service Worker
Shared Worker
App Shell
Architektur
C

CFTools Software

Geschäftsführer und Gründer von CFTools Software GmbH. Leidenschaftlich in der Entwicklung skalierbarer Softwarelösungen und Cloud-Native-Architekturen.

Artikel nicht verfügbar

Dieser Artikel ist für Ihren Zugangstyp nicht verfügbar.

Alle Artikel anzeigen