Web-Performance ist nicht "soll gut sein", sondern eine Zahl, die Entscheidungen beeinflusst. 2026 führt INP – die Nachfolgemetrik von FID – dazu, dass mobile Conversions um 15–20 % sinken, wenn sie 200 ms überschreitet (Google Chrome UX Report 2025 Kohorte). Um diesen Level zu halten, brauchst du keine Vermutungen, sondern automatisierte Kontrolle in der CI-Pipeline. Lighthouse CI, RUM und ein Regressions-Alarmsystem – welche Schwellwerte bindest du wo ein, und wo steht jede Metrik im Entscheidungsprozess? Dieser Artikel zeigt die Architektur, Performance-Budgets vom Test zur Geschäftsentscheidung zu verbinden – mit konkreten Zahlen.
Performance Budget: Definition und Sprint-Planung
Ein Performance Budget setzt die Obergrenze für Seitenladezeit, Bundle-Größe und Runtime-Metriken. Total Bundle ≤ 250 KB, FCP ≤ 1,2 s, INP ≤ 200 ms – das sind Budget-Werte. Sie werden vor dem Sprint definiert und zum Merge-Kriterium. Überschreitet ein PR diese Grenzen, entweder: Code refaktorieren, Feature verschieben oder Budget erhöhen und den Conversions-Verlust akzeptieren.
Budget-Definition nutzt drei Quellen: (1) Googles Core Web Vitals Schwellwerte (LCP <2,5 s, INP <200 ms, CLS <0,1), (2) p75 Benchmark aus RUM-Daten (75 % deiner Nutzer bleiben unter diesem Level), (3) Conversions-Korrelationsbericht (jede 100 ms LCP-Anstieg senkt Conversions um −2 %). Budget ist keine einzelne Zahl, sondern metrik-basiert aufgeschlüsselt:
| Metrik | Schwellwert | Quelle |
|---|---|---|
| LCP | <2,5 s | CWV offiziell |
| INP | <200 ms | CWV 2024+ |
| CLS | <0,1 | CWV offiziell |
| Total JS | <300 KB gzip | HTTP Archive p75 |
| FCP | <1,8 s | Internal RUM |
Diese Tabelle schreibst du in performance.config.json, Lighthouse CI liest die Datei und blockiert PR-Merges bei Überschreitung.
Lighthouse CI: Performance als Merge-Kriterium
Lighthouse CI führt bei jedem PR einen Lighthouse-Audit aus und vergleicht die Ergebnisse mit deinem Performance-Budget (Open-Source-Tool von Google). Es integriert sich mit GitHub Actions, GitLab CI und CircleCI. Grundablauf: (1) PR öffnet sich, (2) CI baut Artefakte, (3) lhci autorun startet ein Lighthouse-Audit in der Test-Umgebung, (4) Ergebnisse werden gegen performance.config.json validiert, (5) Budget-Verletzung = PR blockiert, Merge unmöglich.
Beispiel-Konfiguration (.lighthouserc.json):
{
"ci": {
"collect": {
"url": ["http://localhost:3000/", "http://localhost:3000/product/sample"],
"numberOfRuns": 3
},
"assert": {
"preset": "lighthouse:no-pwa",
"assertions": {
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
"interactive": ["error", {"maxNumericValue": 3500}],
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
"total-byte-weight": ["warn", {"maxNumericValue": 307200}]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
LCP > 2,5 s blockiert den PR (error), Total Bytes > 300 KB gibt nur eine Warnung (warn). 3 Durchläufe, um Netzwerk-Variance auszugleichen – eine einzelne Messung hat zu viel Noise. Der Tradeoff: Lighthouse CI läuft auf einem lokalen Dev-Server, kann keine Production-CDN simulieren. Die Ergebnisse sind konservativ, Production ist normalerweise besser – aber Budget-Grenzen sollten trotzdem nicht überschritten werden.
Lighthouse CI + Vercel Preview: Real-Staging-Test
Auf Vercel/Netlify wird automatisch eine Preview-URL für jeden PR erstellt. Wenn du Lighthouse CI auf diese Preview-URL zeigst, testest du in einer Production-ähnlichen Umgebung. GitHub Actions Beispiel:
- name: Run Lighthouse CI
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_TOKEN }}
run: |
npm install -g @lhci/cli
lhci autorun --collect.url=${{ steps.vercel.outputs.preview-url }}
steps.vercel.outputs.preview-url kommt von der Vercel-Aktion. So testest du CDN-Caching, Edge-SSR und Image-Optimierung. Bei Budget-Überschreitung kommentiert CI automatisch auf dem PR, optional Slack-Benachrichtigung.
RUM: Performance-Budget aus echten Nutzerdaten kalibrieren
Lighthouse CI ist synthetisches Testen – kontrollierte Bedingungen, immer gleiche Netzwerk-Simulation. RUM (Real User Monitoring) sammelt Metriken von echten Besuchern. Der Unterschied ist kritisch: Lighthouse simuliert 3G, RUM sieht 4G/5G/Fiber-Mix; Lighthouse testet kalten Cache, RUM erfasst Repeat-Visitor-Caching. Budget nur nach Lighthouse zu setzen bedeutet, echte Nutzererfahrung zu verpassen.
Als RUM-Collector nutzt man Googles Web Vitals Library. Sie misst CWV-Metriken auf jeder Seite und sendet sie an einen Beacon-Endpoint. Implementierungsbeispiel:
import {onCLS, onINP, onLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating
});
navigator.sendBeacon('/analytics', body);
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
Dein Backend schreibt diese Daten in BigQuery (oder dein Data Warehouse). Dann berechnest du p75:
SELECT
APPROX_QUANTILES(value, 100)[OFFSET(75)] AS p75_lcp
FROM metrics
WHERE name = 'LCP' AND date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY);
Wenn p75_lcp = 2,8 s und dein Budget 2,5 s ist, liegt das Budget unter 75 % deines echten Traffics – entweder Budget auf 2,8 s anheben (mit Conversions-Impakt) oder Code optimieren. p75 ist Standard, weil Google CWV auch nach p75 bewertet.
RUM + Segmentierung: Device- und Region-basierte Budgets
Nicht ganzer Traffic mit einem Budget. Mobile-LCP ist ~40 % höher als Desktop (Chrome UX Report 2025), Indien-Traffic ist ~60 % langsamer als USA. RUM-Daten segmentieren, Budget differenzieren:
| Segment | LCP Budget | INP Budget |
|---|---|---|
| Desktop | 2,2 s | 180 ms |
| Mobile | 3,0 s | 220 ms |
| Indien | 3,5 s | 250 ms |
Dafür erweiterst du den RUM-Beacon um deviceType und country (GeoIP Backend-seitig), analysierst in BigQuery mit GROUP BY device. Lighthouse CI Multi-Config nicht nativ, aber separate Workflows (lhci-mobile.json + lhci-desktop.json).
Regressions-Alarm: Wenn Performance sinkt, Slack sofort
Budget definiert, CI kontrolliert, RUM läuft – aber Post-Deploy sinkt die Performance in Production? Dann musst du das in 5 Minuten wissen, nicht in 3 Stunden. Ein Job analysiert RUM-Daten alle 5 Minuten (Cloudflare Workers Cron, AWS Lambda EventBridge, GCP Cloud Scheduler) und triggert Alarme.
Beispiel-Logik (Pseudo-Code):
// Alle 5min ausgeführt
async function checkRegression() {
const current = await query('SELECT AVG(value) FROM metrics WHERE name="LCP" AND timestamp > NOW() - INTERVAL 5 MINUTE');
const baseline = await query('SELECT AVG(value) FROM metrics WHERE name="LCP" AND timestamp BETWEEN NOW() - INTERVAL 1 DAY AND NOW() - INTERVAL 1 HOUR');
if (current > baseline * 1.15) { // 15% Anstieg
await sendSlack({
text: `🚨 LCP Regression: ${current}ms (Baseline ${baseline}ms)`,
channel: '#performance-alerts'
});
}
}
Baseline 1 Stunde zurück, weil der Deploy gerade sein könnte. 15 % Schwellwert ist kalibrierbar – 10 % zu sensitiv (False Positives), 25 % zu spät. Optional: PagerDuty, Opsgenie für On-Call-Integration. Team entscheidet Rollback oder Hotfix.
Root Cause mit Lighthouse Diff
Alarm kommt rein, LCP ist gestiegen – warum? Lighthouse CI zeigt nur Schwellwert-Verletzung, nicht Root Cause. Mit lhci compare vergleichst du zwei Audit-Läufe:
lhci compare --base=build-1234 --head=build-1235 --preset=lighthouse:all
Output: "unused-javascript increased by 45 KB", "server-response-time +120 ms". Das grenzt Root Cause ein. Bundle-Analyzer (webpack-bundle-analyzer, Next.js analyze) zeigt, woher 45 KB stammen; Server-Logs zeigen, welche Operation 120 ms länger dauert.
Performance an Conversions binden: Attribution
Budget ist Technik, aber um es ins Entscheidungs-System zu binden, braucht es Business-Metrik. "LCP von 2,5 s auf 3,0 s erhöhen senkt Conversions um 4 %" – dieser Report muss aus A/B-Test oder Kohorten-Analyse kommen. A/B-Test: 50 % Traffic bekommt absichtlich langsamere Version (500 ms künstliche Verzögerung), Conversions vergleichen. Kohorten-Analyse: RUM-Daten splitten – Conversions-Rate für LCP <2 s vs. LCP >3 s.
Google Analytics 4 + BigQuery Export zur Korrelation:
SELECT
CASE
WHEN lcp < 2000 THEN 'fast'
WHEN lcp BETWEEN 2000 AND 4000 THEN 'medium'
ELSE 'slow'
END AS lcp_bucket,
COUNT(DISTINCT user_pseudo_id) AS users,
COUNTIF(event_name = 'purchase') / COUNT(DISTINCT session_id) AS conversion_rate
FROM analytics_events
LEFT JOIN rum_metrics ON analytics_events.session_id = rum_metrics.session_id
GROUP BY lcp_bucket;
Ergebnis-Tabelle:
| LCP Bucket | Conversions-Rate |
|---|---|
| fast | 4,2 % |
| medium | 3,6 % |
| slow | 2,9 % |
LCP von 3,0 s auf 2,5 s senken = +16,7 % Conversions-Lift. Bei 100K monatliche Besucher = +1670 Conversions; mit AOV $50 = +$83K Revenue. Dieser Report geht nicht an den CTO, sondern an den CFO – hier wird Sprint-Priorität gesetzt.
Budget-Überschreitung: Tradeoff-Entscheidung
Sprint-Feature landet, Bundle wächst um 50 KB, Budget bricht. Drei Wege: (1) Code refaktorieren (Code-Splitting, Lazy Loading), (2) Budget erhöhen + Conversions-Verlust akzeptieren, (3) Feature verschieben. Entscheidung zahlgesteuert: 50 KB extra = +200 ms LCP (Lighthouse Trace), +200 ms LCP = −2 % Conversions (RUM-Korrelation). Wenn Feature selbst +5 % Lift bringt, netto +3 % – weitermachen. Wenn nur +1 %, netto −1 % – verschieben.
Dafür bauen wir intern einen "performance cost estimator" (Tabelle/Tool). Input: Bundle-Size-Delta; Output: geschätztes LCP-Delta + Conversions-Impact. Modell aus deinen RUM-Daten: jede 10 KB Bundle ≈ +30 ms LCP, jede 100 ms LCP ≈ −0,8 % Conversions. Zeige das PM, die setzen Feature-Priorität.
Headless Commerce: Budget für API-Latenz
E-Commerce = Performance = Revenue. Headless Commerce (Shopify Hydrogen, Remix, Next.js) gibt dir Frontend-Bundle-Kontrolle, aber Backend-API-Latenz zählt auch zum Budget. Shopify Storefront API durchschnittlich 150 ms, das muss ins Budget: LCP = TTFB (150 ms) + FCP (800 ms) + LCP-Delta (600 ms) = 1550 ms. Budget 2500 ms = 950 ms Puffer.
Regressions-Ursachen in Headless meist: (1) API-Query-Komplexität steigt (GraphQL-Depth +2 Level = +50 ms), (2) SSR-Component-Count (20 Components = +100 ms Hydration), (3) Third-Party-Scripts (Analytics-Tag = +200 ms). Lighthouse CI unterscheidet das nicht – RUM-Trace nötig. Next.js Middleware mit Server-Timing Header:
export function middleware(req) {
const start = Date.now();
const res = NextResponse.next();
res.headers.set('Server-Timing', `api;dur=${Date.now() - start}`);
return res;
}
Im Chrome Dev