Erstelle ein benutzerdefiniertes Analytics-Dashboard
Diese Anleitung zeigt dir, wie du ein benutzerdefiniertes Analytics-Dashboard erstellst, das deine Zenovay-Daten anzeigt. Du lernst die empfohlene Architektur, wie du API-Aufrufe sicher proxy-st, und wie du Diagramme, Karten und Echtzeit-Besucherzähler erstellst.
Voraussetzungen
Du benötigst einen Zenovay API-Schlüssel, bevor du API-Aufrufe tätigen kannst. Gehe zu Settings → Security → API keys, um einen zu erstellen. Der API-Zugriff erfordert einen bezahlten Plan.

Architektur-Übersicht
Das empfohlene Muster verwendet einen Server-seitigen Proxy, um deinen API-Schlüssel sicher zu halten:
Browser --> Your Backend --> Zenovay API
(proxy) api.zenovay.com
Gebe deinen Zenovay API-Schlüssel niemals in Client-seitigem JavaScript preis. Proxy-e Anfragen immer über deinen eigenen Server.
Einrichtung eines Server-seitigen Proxy
import { NextResponse } from 'next/server';
const ZENOVAY_API_KEY = process.env.ZENOVAY_API_KEY!;
const ZENOVAY_BASE_URL = 'https://api.zenovay.com/api/external/v1';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const websiteId = searchParams.get('websiteId');
const endpoint = searchParams.get('endpoint') || '';
const range = searchParams.get('range') || '7d';
if (!websiteId) {
return NextResponse.json({ error: 'websiteId required' }, { status: 400 });
}
const url = `${ZENOVAY_BASE_URL}/analytics/${websiteId}${endpoint ? '/' + endpoint : ''}?range=${range}`;
const response = await fetch(url, {
headers: { 'X-API-Key': ZENOVAY_API_KEY },
});
const data = await response.json();
return NextResponse.json(data);
}Abrufen von Analytics-Übersichtsdaten
Der Endpunkt /analytics/:websiteId gibt Zusammenfassungsstatistiken und tägliche Aufschlüsselungen zurück:
const API_BASE = '/api/analytics'; // Your proxy endpoint
export interface AnalyticsOverview {
website: { id: string; domain: string; name: string };
time_range: string;
summary: {
total_visitors: number;
total_page_views: number;
unique_visitors: number;
};
daily_stats: Array<{
date: string;
total_visitors: number;
page_views: number;
unique_visitors: number;
}>;
}
export async function fetchOverview(
websiteId: string,
range = '7d'
): Promise<AnalyticsOverview> {
const res = await fetch(
`${API_BASE}?websiteId=${websiteId}&range=${range}`
);
const json = await res.json();
if (!json.success) throw new Error(json.error?.message || 'API error');
return json.data;
}
export async function fetchCountries(websiteId: string, range = '7d') {
const res = await fetch(
`${API_BASE}?websiteId=${websiteId}&endpoint=countries&range=${range}`
);
const json = await res.json();
if (!json.success) throw new Error(json.error?.message || 'API error');
return json.data.countries;
}
export async function fetchTechnology(websiteId: string, range = '7d') {
const res = await fetch(
`${API_BASE}?websiteId=${websiteId}&endpoint=technology&range=${range}`
);
const json = await res.json();
if (!json.success) throw new Error(json.error?.message || 'API error');
return json.data;
}Erstelle Besucher-Diagramme
Verwende das Array daily_stats aus der Analytics-Übersicht, um Zeitreihendiagramme zu erstellen. Hier ist ein Beispiel mit einem einfachen HTML-Canvas, aber du kannst jede Diagrammbibliothek verwenden (Chart.js, Recharts, usw.):
'use client';
import { useEffect, useState } from 'react';
import { fetchOverview, type AnalyticsOverview } from '@/lib/zenovay';
interface Props {
websiteId: string;
range?: string;
}
export function VisitorChart({ websiteId, range = '7d' }: Props) {
const [data, setData] = useState<AnalyticsOverview | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchOverview(websiteId, range)
.then(setData)
.finally(() => setLoading(false));
}, [websiteId, range]);
if (loading) return <div>Loading chart...</div>;
if (!data) return <div>No data available</div>;
const { daily_stats, summary } = data;
const maxViews = Math.max(...daily_stats.map(d => d.page_views));
return (
<div>
<div style={{ display: 'flex', gap: '2rem', marginBottom: '1rem' }}>
<div>
<strong>{summary.total_visitors.toLocaleString()}</strong>
<div>Total Visitors</div>
</div>
<div>
<strong>{summary.total_page_views.toLocaleString()}</strong>
<div>Page Views</div>
</div>
<div>
<strong>{summary.unique_visitors.toLocaleString()}</strong>
<div>Unique Visitors</div>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: '2px', height: '200px' }}>
{daily_stats.map((day) => (
<div
key={day.date}
title={`${day.date}: ${day.page_views} views`}
style={{
flex: 1,
height: `${(day.page_views / maxViews) * 100}%`,
backgroundColor: '#3b82f6',
borderRadius: '2px 2px 0 0',
minHeight: '2px',
}}
/>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.75rem' }}>
<span>{daily_stats[0]?.date}</span>
<span>{daily_stats[daily_stats.length - 1]?.date}</span>
</div>
</div>
);
}Geografische Karte
Verwende den Endpunkt /analytics/:websiteId/countries, um Besucherstandorte anzuzeigen. Die Antwort enthält Ländercode, die mit jeder Kartenbibliothek funktionieren:
'use client';
import { useEffect, useState } from 'react';
import { fetchCountries } from '@/lib/zenovay';
interface Country {
country_code: string;
country_name: string;
visitors: number;
avg_value_score: number;
}
export function CountryTable({ websiteId }: { websiteId: string }) {
const [countries, setCountries] = useState<Country[]>([]);
useEffect(() => {
fetchCountries(websiteId, '30d').then(setCountries);
}, [websiteId]);
const total = countries.reduce((sum, c) => sum + c.visitors, 0);
return (
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th style={{ textAlign: 'left' }}>Country</th>
<th style={{ textAlign: 'right' }}>Visitors</th>
<th style={{ textAlign: 'right' }}>%</th>
</tr>
</thead>
<tbody>
{countries.map((country) => (
<tr key={country.country_code}>
<td>{country.country_name}</td>
<td style={{ textAlign: 'right' }}>{country.visitors.toLocaleString()}</td>
<td style={{ textAlign: 'right' }}>
{total > 0 ? ((country.visitors / total) * 100).toFixed(1) : 0}%
</td>
</tr>
))}
</tbody>
</table>
);
}Für eine interaktive Karte kannst du die Länderdaten mit einer Bibliothek wie react-simple-maps oder Mapbox GL JS kombinieren. Das Feld country_code verwendet ISO 3166-1 alpha-2 Codes, die diese Bibliotheken direkt verstehen.
Technologie-Aufschlüsselung
Zeige Geräte-, Browser- und Betriebssystemstatistiken von /analytics/:websiteId/technology an:
'use client';
import { useEffect, useState } from 'react';
import { fetchTechnology } from '@/lib/zenovay';
interface TechItem {
name: string;
count: number;
percentage: number;
}
function BarList({ items, label }: { items: TechItem[]; label: string }) {
return (
<div>
<h3>{label}</h3>
{items.slice(0, 5).map((item) => (
<div key={item.name} style={{ marginBottom: '0.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{item.name}</span>
<span>{item.percentage}%</span>
</div>
<div style={{ background: '#e5e7eb', borderRadius: '4px', height: '8px' }}>
<div
style={{
width: `${item.percentage}%`,
background: '#3b82f6',
borderRadius: '4px',
height: '100%',
}}
/>
</div>
</div>
))}
</div>
);
}
export function TechBreakdown({ websiteId }: { websiteId: string }) {
const [tech, setTech] = useState<{
devices: TechItem[];
browsers: TechItem[];
operating_systems: TechItem[];
} | null>(null);
useEffect(() => {
fetchTechnology(websiteId).then(setTech);
}, [websiteId]);
if (!tech) return <div>Loading...</div>;
return (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '2rem' }}>
<BarList items={tech.devices} label="Devices" />
<BarList items={tech.browsers} label="Browsers" />
<BarList items={tech.operating_systems} label="Operating Systems" />
</div>
);
}Echtzeit-Besucherzähler
Der Echtzeit-Besucherendpunkt ist öffentlich und erfordert keine Authentifizierung:
GET https://api.zenovay.com/e/live/YOUR_TRACKING_CODE
Dies macht es sicher, direkt vom Browser aus zu rufen. Frage alle 10 Sekunden nach einem Live-Zähler ab:
'use client';
import { useEffect, useState } from 'react';
export function LiveVisitors({ trackingCode }: { trackingCode: string }) {
const [count, setCount] = useState<number | null>(null);
useEffect(() => {
const fetchLive = async () => {
try {
const res = await fetch(
`https://api.zenovay.com/e/live/${trackingCode}`
);
const data = await res.json();
setCount(data.visitors ?? data.data?.visitors ?? 0);
} catch {
// Silently fail -- keep last known count
}
};
fetchLive();
const interval = setInterval(fetchLive, 10_000);
return () => clearInterval(interval);
}, [trackingCode]);
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<span
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: count !== null ? '#22c55e' : '#9ca3af',
display: 'inline-block',
}}
/>
<span>
{count !== null ? count.toLocaleString() : '--'} visitor{count !== 1 ? 's' : ''} online
</span>
</div>
);
}Alles zusammenbringen
Hier ist eine vollständige Dashboard-Seite, die alle Komponenten kombiniert:
import { VisitorChart } from '@/components/VisitorChart';
import { CountryTable } from '@/components/CountryTable';
import { TechBreakdown } from '@/components/TechBreakdown';
import { LiveVisitors } from '@/components/LiveVisitors';
const WEBSITE_ID = 'ws_abc123'; // Your Zenovay website ID
const TRACKING_CODE = 'ZV_XXXXXXXXXXX'; // Your tracking code
export default function DashboardPage() {
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1>Analytics Dashboard</h1>
<LiveVisitors trackingCode={TRACKING_CODE} />
</div>
<section style={{ marginTop: '2rem' }}>
<h2>Visitor Trends</h2>
<VisitorChart websiteId={WEBSITE_ID} range="30d" />
</section>
<section style={{ marginTop: '2rem' }}>
<h2>Technology</h2>
<TechBreakdown websiteId={WEBSITE_ID} />
</section>
<section style={{ marginTop: '2rem' }}>
<h2>Top Countries</h2>
<CountryTable websiteId={WEBSITE_ID} />
</section>
</div>
);
}Nächste Schritte
- Erste Schritte mit der API -- API-Schlüssel-Setup und Authentifizierung
- Server-seitige Analytics -- Node.js und Python Wrapper für Backend-Pipelines
- Webhook und Echtzeit-Daten -- Erweitertes Polling und Alerting-Muster
- Externe API-Referenz -- Vollständige Endpunkt-Dokumentation