Add blog posts, cleanup unused files, update components

- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 11:42:11 +01:00
co-authored by Claude Opus 4.5
parent cba6c66ccc
commit 43484c5023
164 changed files with 60969 additions and 23346 deletions
@@ -0,0 +1,804 @@
# Performance Monitoring & Analytics
**Meta-Description:** Web Performance Monitoring mit Next.js. Real User Monitoring, Analytics Integration, Error Tracking und Dashboards.
**Keywords:** Performance Monitoring, RUM, Analytics, Error Tracking, Web Vitals, Sentry, Google Analytics, Vercel Analytics
---
## Einführung
**Performance Monitoring** geht über einmalige Tests hinaus. **Real User Monitoring (RUM)** erfasst echte Nutzerdaten, **Error Tracking** findet Probleme früh. Dieser Guide zeigt moderne Monitoring-Strategien für Next.js Anwendungen.
---
## Monitoring Overview
```
┌─────────────────────────────────────────────────────────────┐
│ PERFORMANCE MONITORING STACK │
├─────────────────────────────────────────────────────────────┤
│ │
│ Data Collection: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Browser │ │
│ │ ├── Web Vitals (LCP, INP, CLS) │ │
│ │ ├── Resource Timing │ │
│ │ ├── Navigation Timing │ │
│ │ ├── Long Tasks │ │
│ │ └── JS Errors │ │
│ │ │ │
│ │ Server │ │
│ │ ├── Response Times │ │
│ │ ├── Server Errors │ │
│ │ ├── Database Queries │ │
│ │ └── External API Calls │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Tools & Services: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Analytics: │ │
│ │ ├── Vercel Analytics / Speed Insights │ │
│ │ ├── Google Analytics 4 │ │
│ │ └── Plausible / Fathom (Privacy-focused) │ │
│ │ │ │
│ │ Error Tracking: │ │
│ │ ├── Sentry │ │
│ │ ├── Bugsnag │ │
│ │ └── LogRocket │ │
│ │ │ │
│ │ APM (Application Performance): │ │
│ │ ├── Datadog │ │
│ │ ├── New Relic │ │
│ │ └── Grafana Cloud │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Alerting: │
│ ├── Slack / Discord notifications │
│ ├── Email alerts │
│ ├── PagerDuty integration │
│ └── Custom webhooks │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Web Vitals Monitoring
```typescript
// lib/monitoring/web-vitals.ts
import { onLCP, onINP, onCLS, onFCP, onTTFB, Metric } from 'web-vitals';
interface VitalsData {
name: string;
value: number;
rating: 'good' | 'needs-improvement' | 'poor';
delta: number;
id: string;
navigationType: string;
url: string;
timestamp: number;
}
type ReportCallback = (data: VitalsData) => void;
const thresholds: Record<string, [number, number]> = {
LCP: [2500, 4000],
INP: [200, 500],
CLS: [0.1, 0.25],
FCP: [1800, 3000],
TTFB: [800, 1800]
};
function getRating(name: string, value: number): 'good' | 'needs-improvement' | 'poor' {
const [good, poor] = thresholds[name] || [0, 0];
if (value <= good) return 'good';
if (value <= poor) return 'needs-improvement';
return 'poor';
}
function createReporter(callback: ReportCallback) {
return (metric: Metric) => {
callback({
name: metric.name,
value: metric.value,
rating: getRating(metric.name, metric.value),
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType || 'unknown',
url: window.location.href,
timestamp: Date.now()
});
};
}
export function initWebVitals(callback: ReportCallback) {
const reporter = createReporter(callback);
onLCP(reporter);
onINP(reporter);
onCLS(reporter);
onFCP(reporter);
onTTFB(reporter);
}
// Aggregated reporting (batch)
let vitalsBuffer: VitalsData[] = [];
let flushTimeout: NodeJS.Timeout | null = null;
export function initBatchedWebVitals(endpoint: string) {
const flush = () => {
if (vitalsBuffer.length === 0) return;
const data = [...vitalsBuffer];
vitalsBuffer = [];
// Use sendBeacon for reliability
if (navigator.sendBeacon) {
navigator.sendBeacon(endpoint, JSON.stringify(data));
} else {
fetch(endpoint, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
keepalive: true
});
}
};
initWebVitals((data) => {
vitalsBuffer.push(data);
// Flush after 5 seconds or when buffer is full
if (flushTimeout) clearTimeout(flushTimeout);
if (vitalsBuffer.length >= 10) {
flush();
} else {
flushTimeout = setTimeout(flush, 5000);
}
});
// Flush on page unload
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
flush();
}
});
}
```
```typescript
// app/api/vitals/route.ts - Vitals API Endpoint
import { NextResponse } from 'next/server';
interface VitalsPayload {
name: string;
value: number;
rating: string;
url: string;
timestamp: number;
}
export async function POST(request: Request) {
try {
const data: VitalsPayload[] = await request.json();
// Validate data
const validData = data.filter(item =>
item.name && typeof item.value === 'number'
);
// Store in database or forward to analytics
await storeVitals(validData);
// Check for poor performance and alert
const poorVitals = validData.filter(v => v.rating === 'poor');
if (poorVitals.length > 0) {
await sendAlert(poorVitals);
}
return NextResponse.json({ success: true });
} catch (error) {
console.error('Vitals API error:', error);
return NextResponse.json({ error: 'Failed to process vitals' }, { status: 500 });
}
}
async function storeVitals(data: VitalsPayload[]) {
// Example: Store in InfluxDB, PostgreSQL, or analytics service
// await db.vitals.createMany({ data });
}
async function sendAlert(poorVitals: VitalsPayload[]) {
// Example: Send Slack notification
const message = poorVitals.map(v =>
`${v.name}: ${v.value.toFixed(2)} (${v.rating}) on ${v.url}`
).join('\n');
// await fetch(process.env.SLACK_WEBHOOK_URL, {
// method: 'POST',
// body: JSON.stringify({ text: `Poor Web Vitals detected:\n${message}` })
// });
}
```
---
## Vercel Analytics Integration
```typescript
// app/layout.tsx - Vercel Analytics
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="de">
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
);
}
// Custom event tracking
// components/Button.tsx
'use client';
import { track } from '@vercel/analytics';
export function CTAButton({ label, action }: { label: string; action: string }) {
const handleClick = () => {
track('cta_click', {
label,
action,
page: window.location.pathname
});
};
return (
<button onClick={handleClick} className="btn-primary">
{label}
</button>
);
}
```
---
## Sentry Error Tracking
```typescript
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Performance Monitoring
tracesSampleRate: 0.1, // 10% of transactions
// Session Replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true
}),
Sentry.browserTracingIntegration()
],
// Environment
environment: process.env.NODE_ENV,
// Release tracking
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
// Ignore specific errors
ignoreErrors: [
'ResizeObserver loop limit exceeded',
'Non-Error exception captured'
],
// Before send hook
beforeSend(event, hint) {
// Filter out sensitive data
if (event.request?.headers) {
delete event.request.headers['authorization'];
delete event.request.headers['cookie'];
}
return event;
}
});
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
environment: process.env.NODE_ENV
});
// sentry.edge.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1
});
```
```typescript
// lib/monitoring/sentry.ts - Custom Sentry Helpers
import * as Sentry from '@sentry/nextjs';
// Capture custom error with context
export function captureError(
error: Error,
context?: Record<string, unknown>
) {
Sentry.withScope(scope => {
if (context) {
Object.entries(context).forEach(([key, value]) => {
scope.setExtra(key, value);
});
}
Sentry.captureException(error);
});
}
// Set user context
export function setUser(user: { id: string; email?: string; username?: string }) {
Sentry.setUser(user);
}
// Clear user on logout
export function clearUser() {
Sentry.setUser(null);
}
// Custom breadcrumb
export function addBreadcrumb(
category: string,
message: string,
data?: Record<string, unknown>
) {
Sentry.addBreadcrumb({
category,
message,
data,
level: 'info'
});
}
// Performance transaction
export function startTransaction(name: string, op: string) {
return Sentry.startInactiveSpan({
name,
op,
forceTransaction: true
});
}
// Usage example
export async function fetchDataWithMonitoring<T>(
url: string,
options?: RequestInit
): Promise<T> {
const span = startTransaction(`fetch ${url}`, 'http.client');
try {
addBreadcrumb('http', `Fetching ${url}`);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
captureError(error as Error, { url, options });
throw error;
} finally {
span?.end();
}
}
```
---
## Google Analytics 4
```typescript
// lib/analytics/gtag.ts
declare global {
interface Window {
gtag: (...args: unknown[]) => void;
dataLayer: unknown[];
}
}
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_ID;
// Initialize GA4
export function initGA() {
if (!GA_MEASUREMENT_ID) return;
window.dataLayer = window.dataLayer || [];
window.gtag = function gtag() {
window.dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: window.location.pathname,
anonymize_ip: true
});
}
// Page view
export function pageview(url: string) {
if (!GA_MEASUREMENT_ID) return;
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url
});
}
// Custom event
export function event(
action: string,
category: string,
label?: string,
value?: number
) {
if (!GA_MEASUREMENT_ID) return;
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value
});
}
// E-commerce events
export function purchaseEvent(transaction: {
id: string;
value: number;
currency: string;
items: Array<{
id: string;
name: string;
price: number;
quantity: number;
}>;
}) {
if (!GA_MEASUREMENT_ID) return;
window.gtag('event', 'purchase', {
transaction_id: transaction.id,
value: transaction.value,
currency: transaction.currency,
items: transaction.items
});
}
// Web Vitals to GA4
export function sendWebVitalToGA(metric: {
name: string;
value: number;
id: string;
}) {
if (!GA_MEASUREMENT_ID) return;
window.gtag('event', metric.name, {
event_category: 'Web Vitals',
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true
});
}
```
```tsx
// components/GoogleAnalytics.tsx
'use client';
import Script from 'next/script';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, Suspense } from 'react';
import { GA_MEASUREMENT_ID, pageview } from '@/lib/analytics/gtag';
function GAPageTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (pathname) {
const url = pathname + (searchParams?.toString() ? `?${searchParams}` : '');
pageview(url);
}
}, [pathname, searchParams]);
return null;
}
export function GoogleAnalytics() {
if (!GA_MEASUREMENT_ID) return null;
return (
<>
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
/>
<Script
id="gtag-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_MEASUREMENT_ID}', {
page_path: window.location.pathname,
anonymize_ip: true
});
`
}}
/>
<Suspense fallback={null}>
<GAPageTracker />
</Suspense>
</>
);
}
```
---
## Custom Analytics Dashboard
```typescript
// lib/analytics/dashboard.ts
interface AnalyticsData {
pageViews: number;
uniqueVisitors: number;
avgSessionDuration: number;
bounceRate: number;
topPages: Array<{ path: string; views: number }>;
vitals: {
lcp: { avg: number; p75: number };
inp: { avg: number; p75: number };
cls: { avg: number; p75: number };
};
errors: Array<{
message: string;
count: number;
lastSeen: Date;
}>;
}
export async function getAnalyticsDashboardData(
startDate: Date,
endDate: Date
): Promise<AnalyticsData> {
// Aggregate data from various sources
const [vitals, pageViews, errors] = await Promise.all([
getVitalsData(startDate, endDate),
getPageViewsData(startDate, endDate),
getErrorsData(startDate, endDate)
]);
return {
pageViews: pageViews.total,
uniqueVisitors: pageViews.unique,
avgSessionDuration: pageViews.avgDuration,
bounceRate: pageViews.bounceRate,
topPages: pageViews.topPages,
vitals,
errors
};
}
// API Route for dashboard
// app/api/analytics/dashboard/route.ts
import { NextResponse } from 'next/server';
import { getAnalyticsDashboardData } from '@/lib/analytics/dashboard';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const startDate = new Date(searchParams.get('start') || Date.now() - 7 * 24 * 60 * 60 * 1000);
const endDate = new Date(searchParams.get('end') || Date.now());
const data = await getAnalyticsDashboardData(startDate, endDate);
return NextResponse.json(data);
}
```
```tsx
// app/admin/analytics/page.tsx - Analytics Dashboard
'use client';
import { useState, useEffect } from 'react';
import { Card } from '@/components/ui/Card';
interface DashboardData {
pageViews: number;
uniqueVisitors: number;
vitals: {
lcp: { avg: number; p75: number };
inp: { avg: number; p75: number };
cls: { avg: number; p75: number };
};
}
export default function AnalyticsDashboard() {
const [data, setData] = useState<DashboardData | null>(null);
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState('7d');
useEffect(() => {
fetchData();
}, [dateRange]);
const fetchData = async () => {
setLoading(true);
const start = getStartDate(dateRange);
const res = await fetch(`/api/analytics/dashboard?start=${start.toISOString()}`);
const data = await res.json();
setData(data);
setLoading(false);
};
if (loading) return <div>Loading...</div>;
if (!data) return <div>No data available</div>;
return (
<div className="p-6 space-y-6">
<h1 className="text-2xl font-bold">Analytics Dashboard</h1>
{/* Date Range Selector */}
<select
value={dateRange}
onChange={(e) => setDateRange(e.target.value)}
className="border rounded px-3 py-2"
>
<option value="24h">Last 24 hours</option>
<option value="7d">Last 7 days</option>
<option value="30d">Last 30 days</option>
</select>
{/* Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<MetricCard
title="Page Views"
value={data.pageViews.toLocaleString()}
trend="+12%"
/>
<MetricCard
title="Unique Visitors"
value={data.uniqueVisitors.toLocaleString()}
trend="+8%"
/>
<MetricCard
title="LCP (p75)"
value={`${data.vitals.lcp.p75}ms`}
status={getVitalStatus('LCP', data.vitals.lcp.p75)}
/>
<MetricCard
title="INP (p75)"
value={`${data.vitals.inp.p75}ms`}
status={getVitalStatus('INP', data.vitals.inp.p75)}
/>
</div>
{/* Web Vitals Chart */}
<Card title="Core Web Vitals">
<VitalsChart data={data.vitals} />
</Card>
</div>
);
}
function MetricCard({
title,
value,
trend,
status
}: {
title: string;
value: string;
trend?: string;
status?: 'good' | 'needs-improvement' | 'poor';
}) {
const statusColors = {
good: 'bg-green-100 text-green-800',
'needs-improvement': 'bg-yellow-100 text-yellow-800',
poor: 'bg-red-100 text-red-800'
};
return (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-sm text-gray-500">{title}</h3>
<p className="text-2xl font-bold mt-1">{value}</p>
{trend && (
<span className="text-sm text-green-600">{trend}</span>
)}
{status && (
<span className={`text-xs px-2 py-1 rounded ${statusColors[status]}`}>
{status}
</span>
)}
</div>
);
}
function getVitalStatus(name: string, value: number): 'good' | 'needs-improvement' | 'poor' {
const thresholds: Record<string, [number, number]> = {
LCP: [2500, 4000],
INP: [200, 500],
CLS: [0.1, 0.25]
};
const [good, poor] = thresholds[name] || [0, 0];
if (value <= good) return 'good';
if (value <= poor) return 'needs-improvement';
return 'poor';
}
```
---
## Monitoring Tools Comparison
| Tool | Type | Pricing | Best For |
|------|------|---------|----------|
| **Vercel Analytics** | RUM | Free tier | Vercel deployments |
| **Google Analytics** | Analytics | Free | General analytics |
| **Sentry** | Error Tracking | Free tier | Error monitoring |
| **Datadog** | APM | Paid | Enterprise |
| **Plausible** | Analytics | Paid | Privacy-focused |
| **Grafana Cloud** | Observability | Free tier | Custom dashboards |
---
## Fazit
Performance Monitoring umfasst:
1. **Web Vitals**: Kontinuierliche RUM-Erfassung
2. **Error Tracking**: Sentry für schnelle Problemlösung
3. **Analytics**: User Behavior verstehen
4. **Alerting**: Proaktive Benachrichtigungen
Monitoring ist kein einmaliges Setup, sondern kontinuierlicher Prozess.
---
## Bildprompts
1. "Performance monitoring dashboard, real-time charts and metrics"
2. "Error tracking interface showing stack traces and user sessions"
3. "Web Vitals visualization, LCP/INP/CLS gauges with colors"
---
## Quellen
- [Vercel Analytics](https://vercel.com/docs/analytics)
- [Sentry Next.js](https://docs.sentry.io/platforms/javascript/guides/nextjs/)
- [Google Analytics 4](https://developers.google.com/analytics/devguides/collection/ga4)
- [Web Vitals Library](https://github.com/GoogleChrome/web-vitals)