SPA Performance-Architektur
Moderne Single-Page-Applications erfordern eine mehrschichtige Caching- und State-Architektur für optimale Performance.
Architektur-Komponenten
| Komponente | Aufgabe | Technologie |
|---|
| App Shell | Instant UI-Gerüst | Service Worker Precache |
| Static Assets | JS, CSS, Fonts | Service Worker Cache First |
| API Cache | Responses cachen | Service Worker SWR |
| Shared State | Tab-übergreifend | Shared Worker |
| WebSocket | Realtime-Daten | Shared Worker |
| Bootstrap Cache | User-Daten | Personalisierter 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
| Endpoint | Inhalt | Cache-Strategie |
|---|
| /api/bootstrap/user | User-Profil | SWR, 1h TTL |
| /api/bootstrap/permissions | Berechtigungen | SWR, 1h TTL |
| /api/bootstrap/config | Feature-Flags | SWR, 15min TTL |
| /api/bootstrap/preferences | User-Settings | SWR, 24h TTL |
Performance-Metriken
Core Web Vitals Ziele
| Metrik | Ziel | Beschreibung |
|---|
| LCP | < 2.5s | Largest Contentful Paint |
| FID | < 100ms | First Input Delay |
| CLS | < 0.1 | Cumulative Layout Shift |
| TTFB | < 200ms | Time to First Byte |
Messbare Verbesserungen
| Szenario | Ohne Caching | Mit Architektur |
|---|
| Erster Besuch | 3-5s | 2-3s |
| Wiederholter Besuch | 2-4s | 100-300ms |
| Offline | Nicht möglich | Vollständig nutzbar |
| Multi-Tab (5 Tabs) | 5x API-Last | 1x 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
| Kategorie | Tools |
|---|
| Service Worker | Workbox, vite-plugin-pwa, @angular/pwa |
| State Management | Pinia, Redux, Zustand |
| Performance Testing | Lighthouse, WebPageTest, Chrome DevTools |
| Monitoring | web-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.