Erstellen Sie ein Echtzeit-Nachrichten-Dashboard mit React und APITube
Echtzeit-Nachrichten-Dashboards sehen einfach aus, bis Sie eines liefern. Sie beginnen mit einer 30-Sekunden-Polling-Schleife, stoßen am ersten Freitagmorgen auf ein 429, fügen einen Wiederholungsversuch hinzu, verdoppeln das Intervall, und jetzt ist das Dashboard weder Echtzeit noch zuverlässig.
Dieses Tutorial führt durch ein React Nachrichten-Dashboard, das Ratenbegrenzungen bewältigt, ohne stillschweigend zu brechen, und die sentiment- und category-Filter freilegt, die einen Nachrichtenfeed nützlich und nicht überladen machen.
Sie werden TypeScript, React 18 Hooks und die APITube News API verwenden (Offenlegung: Ich arbeite an APITube). Der Endzustand ist ein Dashboard mit einem Live-aktualisierenden Feed, Filtern und einem funktionierenden Deploy auf Vercel. Der vollständige Quellcode ist auf GitHub – Link am Ende.
Dieser Artikel richtet sich an React-Entwickler, die Hooks und TypeScript-Grundlagen kennen und eine produktionsreife Vorlage wünschen, kein Spielzeug-Demo.
Wichtige Erkenntnisse
- Verwenden Sie Server-Sent Events für Nachrichten-Feeds, die bis zu 10 Mal pro Sekunde aktualisiert werden – WebSocket ist übertrieben und benötigt Backend-Infrastruktur, die Sie nicht benötigen.
- Behandeln Sie
429 Too Many Requestsals UX-Zustand: Zeigen Sie ein Banner „Verbindung wird in Xs wiederhergestellt“ mit exponentiellem Backoff (2 → 4 → 8 → 16 → 32 → 60 Sekunden, die gleiche Kurve, dieEventSourceverwendet). - Halten Sie einen In-Memory-Puffer-Cap (100 Artikel) ein, um über lange Sitzungen hinweg keinen Speicher zu verlieren.
- Die kostenlose Stufe von APITube (100 Anfragen pro Tag + SSE Zugriff) reicht für Produktions-Experimente aus.
- Full Stack: React 18, TypeScript, Vite, Vercel – kein Node-Server, kein Pub/Sub-Broker, kein kostenpflichtiges SaaS.
1. Was Sie bauen werden
Ein Echtzeit-Nachrichten-Dashboard ist eine Single-Page-Web-App, die Artikel aus einer Nachrichten API streamt, wenn sie veröffentlicht werden, sie clientseitig filtert und ohne Seitenaktualisierung aktualisiert. Dieses Tutorial erstellt eines mit drei Panels: einen Live-Feed der neuesten Artikel, eine Kategoriefilter-Seitenleiste (Technologie, Wirtschaft, Sport…) und einen Sentiment-Chip auf jeder Karte (grün / neutral / rot). Artikel werden mit Server-Sent Events gestreamt — keine Seitenaktualisierung, keine Polling-Schleife, kein Backend-Broker.
Die Architektur ist minimal: kein Node-Server, kein WebSocket-Broker, kein Pusher-Konto. Die React-App spricht direkt mit APITube über den REST-Endpunkt für das erste Rendering und den Server-Sent Events-Endpunkt für Live-Updates.
Stack:
- React 18 + TypeScript (Vite)
- Native
fetchundEventSource— keine Client-Bibliothek - Zwei benutzerdefinierte Hooks:
useRateLimitedFetch,useNewsStream - Vercel für Deployment
2. Voraussetzungen
Sie benötigen Node 20+, fundierte Kenntnisse von React Hooks (useState, useEffect, Cleanup-Funktionen) und einen kostenlosen APITube API Schlüssel. Die Anmeldung ist nur per E-Mail möglich, keine Karte erforderlich. Die kostenlose Stufe gewährt Ihnen 100 Anfragen pro Tag plus SSE Zugriff – genug für diesen Build und für Produktions-Experimente.
Grundlegende TypeScript wird angenommen (Schnittstellen, Generika, Union-Typen). Keine Backend-Arbeit erforderlich.
3. Polling vs SSE vs WebSocket: Auswahl des Echtzeit-Transports
Bevor Sie Code schreiben, wählen Sie den richtigen Mechanismus. Die meisten React-Dashboards verwenden standardmäßig WebSocket, da diese für die "Echtzeit"-Artikel auf Google am besten ranken, aber für Nachrichten-Daten ist WebSocket fast immer übertrieben.
Das Entscheidungsgerüst:
| Szenario | Aktualisierungsfrequenz | Empfohlener Transport | Warum |
|---|---|---|---|
| Abfragen | <1 Ereignis / 30 s | setInterval + Abruf | Günstig, trivial, funktioniert über jeden Proxy |
| Server-Sent Events | 1-10 Ereignisse/Sek., nur Server→Client | EventSource | Automatische Wiederverbindung, HTTP/2-freundlich, kein Broker |
| WebSocket | >10 Ereignisse/Sek. oder bidirektional | ws + Server | Voller Roundtrip, benötigt aber Infrastruktur |
Nachrichtenereignisse treffen höchstens wenige pro Sekunde pro Filter. Das bringt sie direkt in den SSE-Bereich. WebSocket ist übertrieben – Ereignisse treffen weit unter 10 pro Sekunde, daher gewinnt SSE bei der Komplexität, ohne die Latenz zu beeinträchtigen.
Sie implementieren immer noch einen Polling-Fallback für das erste Rendern (damit der Feed nicht für 0.5 s leer ist, während SSE sich verbindet) und für Browser, die EventSource hinter aggressiven Proxys blockieren.
Was ist der beste Weg, um einen Echtzeit-Feed in React zu erstellen?
Der beste Weg, einen Echtzeit-Feed in React zu erstellen, sind Server-Sent Events für Feeds, die sich weniger als zehnmal pro Sekunde aktualisieren, kombiniert mit einem anfänglichen REST-Abruf für das erste Rendern. SSE stellt automatisch eine Verbindung wieder her, funktioniert über Proxys und benötigt keine Server-Infrastruktur, wenn Ihre Datenquelle bereits einen SSE-Endpunkt bereitstellt.
4. Projekteinrichtung
Erstellen Sie eine Vite + React + TypeScript-App:
npm create vite@latest news-dashboard -- --template react-ts
cd news-dashboard
npm install
npm install --save-dev @types/node
Erstellen Sie eine .env.local im Projektstammverzeichnis:
VITE_APITUBE_API_KEY=your_key_here
Projektstruktur (src/ Verzeichnis):
src/
hooks/
useRateLimitedFetch.ts
useNewsStream.ts
components/
NewsCard.tsx
CategoryFilter.tsx
NewsDashboard.tsx
types/
news.ts
lib/
apitube.ts
App.tsx
main.tsx
Wie strukturiere ich ein React-Dashboard-Projekt?
Strukturieren Sie ein React Dashboard-Projekt um Datenanliegen, nicht um Seitenlayout. Platzieren Sie die Abruflogik in hooks/, Typen in types/, API-Clients in lib/ und Präsentationskomponenten in components/. Jede Komponente, die ein Abfrageergebnis rendert, sollte ihre Daten von einem einzigen typisierten Hook erhalten – rufen Sie niemals fetch inline auf. Dies hält Caching, Wiederholungsversuche und Rate-Limit-Logik an einem Ort.
5. Nachrichtenabruf mit dem APITube API
Beginnen Sie mit dem REST-Endpunkt. Definieren Sie den Antworttyp einmal und verwenden Sie ihn überall wieder.
src/types/news.ts:
export interface NewsArticle {
id: string;
title: string;
description: string;
href: string;
published_at: string;
language: { code: string };
author?: { name: string };
categories?: { id: string; name: string }[];
sentiment?: {
overall: { score: number; polarity: 'positive' | 'neutral' | 'negative' };
title: { score: number };
body: { score: number };
};
source?: { domain: string; country?: string };
}
export interface NewsResponse {
status: string;
results: NewsArticle[];
has_next_pages: boolean;
page: number;
per_page: number;
}
src/lib/apitube.ts:
const BASE_URL = 'https://api.apitube.io';
const API_KEY = import.meta.env.VITE_APITUBE_API_KEY;
export async function fetchNews(params: URLSearchParams): Promise<NewsResponse> {
const res = await fetch(`${BASE_URL}/v1/news/everything?${params}`, {
headers: { 'X-API-Key': API_KEY },
});
if (!res.ok) throw { status: res.status, message: res.statusText };
return res.json();
}
APITube authentifiziert sich mit dem X-API-Key-Header (kein Bearer-Token – ein häufiger Fehler bei der Migration von anderen Nachrichten-APIs). Die Antwortstruktur ist über /v1/news/everything und den SSE-Stream stabil, sodass derselbe NewsArticle-Typ beide abdeckt.
Wie rufe ich Nachrichten-Daten in einer React-App ab?
Nachrichten-Daten in einer React-App abrufen mit einem typisierten Wrapper um die Nachrichten-API plus einem benutzerdefinierten Hook, der den Lade- und Fehlerstatus verwaltet. Rendern Sie immer aus einem einzigen Hook pro Datenquelle — rufen Sie fetch niemals direkt in einer Komponente auf — sodass Ratenlimit-Handling, Caching und Authentifizierung an einem Ort leben. Übergeben Sie URLSearchParams in den Wrapper, um die Abfragekonstruktion typsicher zu halten.
6. Echtzeit-Updates mit Server-Sent Events
Der SSE-Endpunkt von APITube unter /v1/news/stream sendet neue Artikel, die mit denselben Filtern wie der REST-Endpunkt übereinstimmen. Der EventSource des Browsers verwaltet die Verbindung, Wiederverbindung und Backoff ohne zusätzliche Bibliothek.
src/hooks/useNewsStream.ts:
import { useEffect, useState } from 'react';
import type { NewsArticle } from '../types/news';
interface StreamOptions {
category?: string;
language?: string;
enabled?: boolean;
}
export function useNewsStream(opts: StreamOptions) {
const [articles, setArticles] = useState<NewsArticle[]>([]);
const [status, setStatus] = useState<'connecting' | 'open' | 'closed'>('closed');
useEffect(() => {
if (!opts.enabled) return;
const params = new URLSearchParams({
'api_key': import.meta.env.VITE_APITUBE_API_KEY,
'language.code': opts.language ?? 'en',
});
if (opts.category) params.set('category.id', opts.category);
const url = `https://api.apitube.io/v1/news/stream?${params}`;
const source = new EventSource(url);
setStatus('connecting');
source.onopen = () => setStatus('open');
source.onmessage = (event) => {
const article = JSON.parse(event.data) as NewsArticle;
setArticles((prev) => [article, ...prev].slice(0, 100));
};
source.onerror = () => setStatus('closed');
return () => {
source.close();
setStatus('closed');
};
}, [opts.category, opts.language, opts.enabled]);
return { articles, status };
}
Drei Details, die in der Produktion wichtig sind:
- Der API Schlüssel befindet sich in der Query-String für SSE. Browser können keine benutzerdefinierten Header für
EventSource-Anfragen setzen. APITube akzeptiert sowohlX-API-Key-Header (REST) als auchapi_key-Query-Parameter (SSE). - Begrenzen Sie den In-Memory-Puffer auf 100 Artikel. Ohne dies wird ein beschäftigter Filter wie
language.code=eneinen Tab über eine lange Sitzung hinweg aus dem Speicher laufen lassen. - Eine Bereinigungsfunktion zurückgeben, die die
EventSourceschließt. React StrictMode wird in der Entwicklung gemountet-unmountet-remountet – ohne Bereinigung öffnen Sie zwei Verbindungen und verbrennen Ihr Rate Limit.
Wie verwende ich Server-Sent Events in React?
Verwenden Sie Server-Sent Events in React, indem Sie eine EventSource innerhalb einer useEffect erstellen, den Status im onmessage-Handler aktualisieren und eine Bereinigungsfunktion zurückgeben, die source.close() aufruft. Der Browser kümmert sich automatisch um die Wiederverbindung mit exponentiellem Backoff. Übergeben Sie Abfrageparameter, um den Stream serverseitig zu filtern, damit Sie keine Artikel übertragen, die die Benutzeroberfläche verwerfen würde.
7. Umgang mit Ratenbegrenzungen: Backoff und veralteter Cache
Die kostenlose Stufe beträgt 100 Anfragen pro Tag. Der SSE-Stream zählt nicht dagegen – aber der REST-Fallback, der anfängliche Seitenaufruf und Filteränderungen schon. Wenn Sie das Limit erreichen, gibt APITube 429 Too Many Requests zurück.
Ratenbegrenzungen sind ein UX-Problem, kein Backend-Problem. Wenn ein Dashboard
429 Too Many Requestserreicht, muss der Benutzer wissen, dass der Feed eingefroren ist und wie lange – nicht nur auf stumm geschaltete, veraltete Daten starren. Zeigen Sie ein Banner an, zählen Sie herunter und bedienen Sie den zuletzt bekannten guten Cache.
Die meisten Tutorials versuchen stillschweigend, bei 429 erneut zu versuchen. Dieses Muster ist in Ordnung für einen Hintergrundjob. Für ein Live-Dashboard bricht es den Vertrag: Der Benutzer sieht "frische" Zeitstempel auf Daten, die Minuten alt sind.
src/hooks/useRateLimitedFetch.ts:
import { useEffect, useRef, useState } from 'react';
import { fetchNews } from '../lib/apitube';
import type { NewsResponse } from '../types/news';
interface State {
data: NewsResponse | null;
error: Error | null;
retryIn: number; // seconds until next attempt, 0 if idle
isStale: boolean; // true when showing cached data during backoff
}
export function useRateLimitedFetch(params: URLSearchParams) {
const [state, setState] = useState<State>({
data: null, error: null, retryIn: 0, isStale: false,
});
const cacheRef = useRef<Map<string, NewsResponse>>(new Map());
useEffect(() => {
const key = params.toString();
let cancelled = false;
let attempt = 0;
async function run() {
// Serve stale cache immediately if we have it.
const cached = cacheRef.current.get(key);
if (cached) setState((s) => ({ ...s, data: cached, isStale: true }));
while (!cancelled) {
try {
const data = await fetchNews(params);
cacheRef.current.set(key, data);
setState({ data, error: null, retryIn: 0, isStale: false });
return;
} catch (err: any) {
if (err.status !== 429) {
setState((s) => ({ ...s, error: err }));
return;
}
// Exponential backoff: 2, 4, 8, 16, 32, 60 (capped) seconds.
const delay = Math.min(2 ** (attempt + 1), 60);
attempt += 1;
for (let i = delay; i > 0 && !cancelled; i--) {
setState((s) => ({ ...s, retryIn: i, isStale: true }));
await new Promise((r) => setTimeout(r, 1000));
}
}
}
}
run();
return () => { cancelled = true; };
}, [params.toString()]);
return state;
}
Der Hook erledigt drei Dinge, die Sie von einem naiven fetch nicht bekommen:
- Stale-while-revalidate: Wenn eine gecachte Antwort für dieselbe Abfrage existiert, wird sie sofort gerendert, während im Hintergrund ein frischer Abruf läuft.
- Exponentielles Backoff begrenzt auf 60 s: 2 → 4 → 8 → 16 → 32 → 60. Dies ist dieselbe Kurve, die der native
EventSourcedes Browsers verwendet. - Exponiert
retryIn, sodass die UI ein Banner „Verbindung wird wiederhergestellt in 12s…“ rendern kann. Der Benutzer weiß, was passiert. Er schließt den Tab nicht.
Wie gehe ich mit API-Ratenlimits in einer React-App um?
API Ratenlimits in einer React-App mit exponentiellem Backoff bei 429-Antworten plus einem Stale-While-Revalidate-Cache behandeln. Cachen Sie die letzte erfolgreiche Antwort pro Abfrageschlüssel, rendern Sie sie sofort, wenn der Hook erneut ausgeführt wird, und stellen Sie einen retryIn-Zähler bereit, damit die UI ein Banner „Verbindung wird in Xs wiederhergestellt“ anzeigen kann, anstatt stillschweigend erneut zu versuchen. Behandeln Sie 429 als benutzerorientierten Zustand, nicht als Backend-Detail.
8. Filtern nach Kategorie, Sprache und Sentiment
Das Dashboard wäre kein Dashboard ohne Filter. APITube gibt strukturierte categories, language und – einzigartig – ein sentiment-Objekt mit overall, title und body-Scores für jeden Artikel zurück.
Sentiment-Filterung ist wichtiger, als es klingt. Ein Krypto-Dashboard, ein Markenüberwachungstool und eine Finanznachrichten-App müssen alle negative Signale schnell erkennen. Die meisten Nachrichten-APIs liefern Ihnen einen rohen Textblock und überlassen die Bewertung Ihnen.
src/components/NewsDashboard.tsx:
import { useMemo, useState } from 'react';
import { useRateLimitedFetch } from '../hooks/useRateLimitedFetch';
import { useNewsStream } from '../hooks/useNewsStream';
import { NewsCard } from './NewsCard';
import { CategoryFilter } from './CategoryFilter';
export function NewsDashboard() {
const [category, setCategory] = useState<string | undefined>();
const [sentimentFilter, setSentimentFilter] =
useState<'all' | 'positive' | 'negative'>('all');
const params = useMemo(() => {
const p = new URLSearchParams({ 'language.code': 'en', per_page: '20' });
if (category) p.set('category.id', category);
return p;
}, [category]);
const initial = useRateLimitedFetch(params);
const stream = useNewsStream({ category, language: 'en', enabled: true });
const articles = useMemo(() => {
const combined = [...stream.articles, ...(initial.data?.results ?? [])];
if (sentimentFilter === 'all') return combined;
return combined.filter(
(a) => a.sentiment?.overall.polarity === sentimentFilter
);
}, [stream.articles, initial.data, sentimentFilter]);
return (
<div className="dashboard">
<aside>
<CategoryFilter value={category} onChange={setCategory} />
<select
value={sentimentFilter}
onChange={(e) => setSentimentFilter(e.target.value as any)}
>
<option value="all">All sentiment</option>
<option value="positive">Positive</option>
<option value="negative">Negative</option>
</select>
</aside>
<main>
{initial.retryIn > 0 && (
<div className="banner">
Rate-limited — reconnecting in {initial.retryIn}s
{initial.isStale && ' (showing cached results)'}
</div>
)}
<div className="status">Stream: {stream.status}</div>
{articles.map((a) => <NewsCard key={a.id} article={a} />)}
</main>
</div>
);
}
NewsCard ist eine dünne, präsentationsorientierte Komponente – nur Titel, Quelle, Zeitstempel und ein Sentiment-Chip, der grün/grau/rot basierend auf article.sentiment?.overall.polarity gefärbt ist. Vollständiger Quellcode im Repository.
9. Bereitstellung für Vercel
Die gesamte App ist statisch + clientseitig – keine Serverlaufzeit, keine API-Routen. Vercel erledigt das mit einem einzigen Befehl.
npm install -g vercel
vercel
Einrichtung der Umgebungsvariablen:
Vite präfigiert clientseitig exponierte Variablen mit VITE_. Das bedeutet, Ihr API-Schlüssel wird im Bundle mitgeliefert und gegen APITube vom Browser des Benutzers ausgeführt. Für ein persönliches Dashboard oder ein authentifiziertes Admin-Panel ist das in Ordnung. Für eine öffentliche Marketing-Website proxyen Sie stattdessen über eine serverlose Funktion.
Fügen Sie den Schlüssel im Vercel-Dashboard unter Projekteinstellungen → Umgebungsvariablen hinzu:
VITE_APITUBE_API_KEY=<your_key>
CORS: APITube erlaubt jeden Ursprung auf beiden REST und SSE Endpunkten, wenn die Anfrage einen gültigen API Schlüssel enthält, daher ist keine CORS Konfiguration auf Ihrer Seite erforderlich. Wenn Sie zu einer Proxy-Architektur wechseln, setzen Sie Access-Control-Allow-Origin auf Ihrer Vercel-Funktion.
Bereitstellen und Sie haben ein funktionierendes React News Dashboard unter <project>.vercel.app in weniger als einer Minute.
10. Wohin als Nächstes
Der Build funktioniert. Erweiterungen:
- Tauschen Sie den In-Memory-Cache gegen
localStorageaus, damit ein Tab-Neuladen keinen leeren Feed anzeigt, während die SSE-Verbindung aufgebaut wird. - Fügen Sie
useSWRhinzu, wenn Sie kostenlos komponentenübergreifendes Caching wünschen. Der hier gezeigteuseRateLimitedFetch-Hook ist bewusst abhängigkeitsfrei, damit Sie den Mechanismus sehen können. - Fügen Sie
media[]-Rendering hinzu für Artikel-Thumbnails — APITube gibt sie in derselben Antwort zurück. - Filtern nach
topics[]undentities[]für eine NLP-gestützte Vertikale (Finanzen, Krypto, Sport). APITubes Entity-Extraktion ist integriert. - Tauschen Sie
EventSourcegegen@microsoft/fetch-event-sourceaus, wenn Sie benutzerdefinierte Header für die Stream-Verbindung benötigen.
Vollständiger Quellcode: github.com/apitube/react-news-dashboard-tutorial
Häufig gestellte Fragen
Funktioniert dies mit React Server Components?
Teilweise. Der Fetch-Hook läuft problemlos auf dem Client. EventSource ist nur im Browser verfügbar — Sie können keinen Stream in einem RSC await. Rendern Sie das Dashboard als Client Component ('use client') und Sie sind bereit.
Kann ich dieses Muster mit einer anderen Nachrichten-API verwenden?
Ja. Tauschen Sie src/lib/apitube.ts gegen den Wrapper Ihres Anbieters aus und aktualisieren Sie NewsArticle, um seiner Antwortform zu entsprechen. Das Hook-Muster – useRateLimitedFetch + useNewsStream – ist anbieterunabhängig. Beachten Sie, dass die meisten Nachrichten-APIs keinen SSE-Endpunkt bereitstellen, sodass Sie auf das Abfragen alle 30-60 s zurückfallen.
Wie vermeide ich es, im Entwicklungsmodus die kostenlose Stufe zu überschreiten?
Zwischenspeichern Sie aggressiv während der Entwicklung — setzen Sie staleTime: Infinity in Ihrer Cache-Schicht und aktualisieren Sie manuell. Schützen Sie außerdem die SSE-Verbindung mit einem enabled-Flag, damit sie sich nicht bei jedem Hot Reload öffnet.
Funktioniert SSE in Safari?
Ja, seit Safari 5. Alle modernen Browser unterstützen EventSource. Die einzige Eigenart ist, dass Safari die Verbindung nach ca. 60 s Leerlaufzeit stillschweigend schließt – APITube sendet alle 30 s einen Keep-Alive-Kommentar, um dies zu verhindern.


