Die Wetter-API gehört in Next.js hinter einen Route Handler oder in eine Server-Komponente: Der Schlüssel bleibt in `process.env` ohne `NEXT_PUBLIC_`-Präfix, der Browser spricht ausschließlich mit deiner eigenen Route. An dieser einen Entscheidung hängt der Rest — wer den Schlüssel lesen kann, wie oft der Anbieter angefragt wird und was die Oberfläche zeigt, wenn er nicht antwortet.
Die meisten Anleitungen enden beim ersten erfolgreichen `fetch`. Danach fängt die Arbeit an. Ich betreibe zwei Wetter-Projekte: PlanBar, eine PWA mit Regenradar, Ensemble-Prognose und Sport-Fenster, sowie ein Dashboard, das den Rohdatenstrom einer eigenen Wetterstation in Wiesmoor aufbereitet. Die folgenden Regeln stammen aus dem Betrieb.
`NEXT_PUBLIC_` ist kein Versteck
Variablen mit diesem Präfix werden beim Build in das JavaScript eingesetzt, das jeder Besucher herunterlädt. Ein Wetter-Schlüssel dort ist kein „halb geschützter" Schlüssel, sondern ein öffentlicher. Das ist auch der Grund für die klassische Fehlermeldung, mit der Entwickler in Foren landen: Der Key wandert ins Client-Bundle, funktioniert eine Weile, und irgendwann sperrt der Anbieter das Kontingent.
Prüfen dauert eine Minute:
```bash npm run build grep -r "DEIN_KEY_HIER" .next/static | head ```
Kommt eine Zeile zurück, liegt der Schlüssel im Auslieferungsstand. Kommt nichts zurück, spricht der Browser über deine Route.
```ts // app/api/wetter/route.ts const round2 = (v: number) => Math.round(v * 100) / 100;
export async function GET(request: Request) { const { searchParams } = new URL(request.url); const lat = round2(Number(searchParams.get("lat"))); const lon = round2(Number(searchParams.get("lon")));
const res = await fetch( `${PROVIDER}/forecast?lat=${lat}&lon=${lon}&key=${process.env.WETTER_API_KEY}`, { next: { revalidate: 1800 } }, // 30 Minuten, bewusst gesetzt );
if (!res.ok) return Response.json(await lastKnown(lat, lon), { headers: { "x-wetter-stale": "1" }, });
return Response.json(normalize(await res.json())); } ```
Setz die Cache-Angabe explizit, statt dich auf den Standard zu verlassen. Das Verhalten von `fetch` ohne Angabe hat sich zwischen Next.js-Major-Versionen geändert — schau in der Doku deiner Version nach, welcher Standard dort gilt, und schreib ihn trotzdem hin. Dann bleibt der Code bei einem Upgrade lesbar.
Noch ein Punkt, der Stunden kosten kann: Ein frisch erstellter Schlüssel ist bei manchen Anbietern nicht sofort gültig. Ein 401 zehn Minuten nach der Registrierung ist deshalb oft kein Code-Fehler, sondern Wartezeit.
Vier Schichten zwischen `fetch` und UI
Ein Wetter-Feature besteht nicht aus einem Aufruf, sondern aus vier klar getrennten Schichten:
- Transport — Route Handler, Schlüssel, Timeouts, Retry-Verhalten.
- Normalisierung — Anbieter-JSON in ein eigenes Domänenmodell übersetzen.
- Cache — TTL pro Datenklasse, quantisierte Cache-Keys.
- Darstellung und Degradation — Formatierung, Zeitzone, Zustände für „veraltet" und „nicht verfügbar".
Die Naht zwischen Schicht 1 und 2 ist die wichtigste. Anbieter ändern Feldnamen, Tarife und Endpunkte; wenn dein React-Baum direkt auf `main.temp` oder `current_weather.weathercode` zugreift, ist ein Wechsel ein Refactoring durch die halbe Codebasis. Liegt dazwischen ein eigener Typ, ist er ein Adapter von 150 Zeilen.
Cache-TTL: Die Rechnung vor der Tarifwahl
Ohne Cache skaliert dein Upstream-Volumen mit den Besuchern. Mit Cache skaliert es mit den Standorten. Das ist der ganze Punkt — und die Rechnung dazu passt in eine Zeile:
Requests pro Monat = Standorte × (60 ÷ TTL in Minuten) × 24 × 30
Fünf feste Standorte bei 10 Minuten TTL ergeben rechnerisch 21.600 Upstream-Anfragen im Monat. Dieselben fünf Standorte bei 30 Minuten TTL: 7.200. Die Besucherzahl taucht in der Formel nicht auf. Eine Seite mit 10.000 Aufrufen ohne Cache-Schicht liegt dagegen bei 10.000 Anfragen — und zwar für Daten, die sich in dieser Zeit kaum bewegt haben.
Ein einheitliches TTL für alles ist der zweite Fehler. Wetterdaten haben unterschiedliche Halbwertszeiten:
| Datenklasse | TTL, mit dem ich starte | Grund |
|---|---|---|
| Radar, Nowcast | 5–15 Minuten | eigener Produktzyklus, unabhängig vom Modelllauf |
| Aktuelle Messwerte | 10–20 Minuten | Stationsmeldungen kommen unregelmäßig |
| Stundenprognose | 30–60 Minuten | ändert sich nur mit dem Modelllauf |
| Tagesprognose | 3–6 Stunden | gleiche Quelle, gröbere Auflösung |
| Klimamittel, Statistik | 24 Stunden und mehr | ändert sich pro Saison, nicht pro Stunde |
Der Hebel dahinter: Globale Wettermodelle rechnen typischerweise in festen Zyklen, häufig alle sechs Stunden. Ein TTL von fünf Minuten auf der Tagesprognose holt dann 71-mal dieselbe Antwort, bevor sich überhaupt etwas ändern kann. Prüf die Laufzeiten deines Anbieters in dessen Doku und richte das TTL daran aus, nicht am Gefühl.
Koordinaten runden, bevor sie den Server verlassen
Die Browser-Geolocation liefert Koordinaten mit sechs Nachkommastellen. Das entspricht rund elf Zentimetern — jeder Nutzer erzeugt damit praktisch seinen eigenen Cache-Key, und der Cache ist wirkungslos, obwohl er technisch funktioniert.
Zwei Nachkommastellen reichen für jede Wetteranzeige. Ein Hundertstelgrad ist etwa 1,11 km in Nord-Süd-Richtung; in Ost-West-Richtung sind es auf der Breite von Hannover (52,4° N) rund 0,68 km, weil dort `111,32 km × cos(52,4°)` gilt. Für ein Stadtgebiet von 20 × 20 km bedeutet das: Höchstens etwa 18 × 29 = 522 mögliche Cache-Keys statt einer offenen Menge.
Der zweite Effekt ist datenschutzseitig relevant: Du gibst dem Drittanbieter dann ein Kilometerraster statt einer Hausnummer. Ob und wie du die Weitergabe von Standortdaten dokumentieren musst, klärst du mit deinem Datenschutzbeauftragten — die technische Vorarbeit ist eine Zeile Code.
Ein Domänenmodell, das den Anbieterwechsel überlebt
Die Übersetzungsarbeit unterschätzen fast alle. Windgeschwindigkeit kommt je nach Quelle in m/s, km/h oder Knoten; Niederschlag als Summe pro Stunde oder als Rate; Temperatur meist in Grad Celsius, in älteren Endpunkten auch in Kelvin. Der WMO-Schlüssel für das aktuelle Wetter belegt die Codes 00 bis 99 — deine Oberfläche braucht davon selten mehr als ein Dutzend Zustände.
```ts export type Vorhersagestunde = { zeitpunkt: string; // ISO 8601 mit Offset, kein Local-String temperaturC: number; windKmh: number; niederschlagMm: number; zustand: "klar" | "bewoelkt" | "regen" | "schnee" | "gewitter" | "nebel"; quelle: "modell" | "station"; }; ```
Das Feld `quelle` klingt nach Überbau, bis du eine zweite Datenquelle hast. Im Wiesmoor-Dashboard liegen Messwerte einer eigenen Station neben der Modellprognose; ohne Herkunftskennzeichnung kannst du im UI nicht unterscheiden, was gemessen und was gerechnet ist — und genau diese Unterscheidung ist das, was ein Wetter-Feature vom Widget trennt.
Zweimal im Jahr hat der Tag nicht 24 Stunden
Solange die aktuelle EU-Regelung gilt, wird in Deutschland am letzten Sonntag im März und im Oktober umgestellt. Der eine Tag hat 23 lokale Stunden, der andere 25. Jedes Array mit 24 festen Slots bricht an diesen beiden Tagen — meist still, mit einer doppelten oder fehlenden Zeile in der Stundenliste.
Die Regel dagegen ist einfach: Iteriere über die Zeitstempel, die die API liefert, und leite die Anzeige daraus ab. Nie über einen Zähler von 0 bis 23, außer du renderst bewusst UTC. Formatiere mit `Intl.DateTimeFormat` und einer ausdrücklich gesetzten `timeZone` — sonst nimmt die Server-Komponente die Zeitzone des Servers und der Browser die des Nutzers.
Damit hängt der zweite typische Fehler zusammen: Relative Angaben wie „in 3 Stunden" werden serverseitig zu einem anderen Zeitpunkt berechnet als beim Hydrieren im Browser. Das Ergebnis ist ein Hydration-Mismatch, der in der Konsole als Warnung auftaucht und in der Anzeige als kurzes Springen. Render serverseitig den absoluten Zeitpunkt und rechne die relative Angabe erst clientseitig aus.
Was das Widget zeigt, wenn der Anbieter 429 antwortet
Ein Wetter-Widget ohne sichtbaren Zeitstempel ist ein Bug. Der Nutzer kann sonst nicht unterscheiden, ob er aktuelle Daten sieht oder eine Momentaufnahme von gestern Abend. Vier Stufen, in dieser Reihenfolge:
- Frisch — Antwort aus dem Cache innerhalb des TTL, normale Darstellung.
- Veraltet, aber brauchbar — der letzte gute Datensatz wird ausgeliefert und im Hintergrund erneuert, mit dezentem Hinweis „Stand: 14:05".
- Alt — jenseits einer Grenze, die du festlegst (bei stündlichen Daten oft drei bis sechs Stunden), wird der Hinweis prominent statt dezent.
- Nicht verfügbar — expliziter Zustand mit Wiederholen-Aktion.
Was in keiner dieser Stufen vorkommt: Eine leere Kachel, ein Dauer-Spinner oder eine Null als Temperatur. Der Fallback braucht einen Speicher — eine Tabellenzeile pro Cache-Key mit dem letzten erfolgreichen Ergebnis reicht.
Neun Fragen an den Anbieter, bevor du den ersten `fetch` schreibst
Preise und Freikontingente ändern sich; die verbindliche Auskunft steht auf der Produktseite des Anbieters, nicht in einem Blogartikel. Die Weather-API-Übersicht von OpenWeather ist ein Beispiel für die Stelle, an der du das nachschlägst. Diese neun Punkte kläre ich vorher:
- Ist die kommerzielle Nutzung im gewählten Tarif abgedeckt?
- Besteht eine Attributionspflicht, und wo genau muss der Hinweis stehen?
- Gilt das Limit pro Minute, pro Tag oder pro Monat? Ein Tageslimit verträgt Lastspitzen, ein Minutenlimit nicht.
- Wie reagiert die API bei Überschreitung — Fehlercode oder Drosselung?
- Wie oft rechnet das zugrunde liegende Modell neu?
- Gibt es Historie, und wie weit zurück?
- Werden Unsicherheiten oder Ensemble-Spannen ausgeliefert, oder nur ein Einzelwert?
- Wo stehen die Server, und gibt es einen Auftragsverarbeitungsvertrag?
- Wie ist die Kündigungs- und Migrationslage, wenn der Tarif sich ändert?
Bei OpenWeatherMap, Open-Meteo und den übrigen gängigen Diensten fallen die Antworten unterschiedlich aus — die Wahl hängt davon ab, ob du eine Dekoration, ein Planungswerkzeug oder eine Auswertung mit Historie baust, nicht davon, wer gerade weiter oben in einer Liste steht.
Aufwand: 19 bis 31 Stunden
So kalkuliere ich eine betriebsfähige Wetter-API-Integration in Next.js — ein Standort-Widget mit Stunden- und Tagesprognose, nicht die komplette Wetter-App:
| Baustein | Stunden |
|---|---|
| Route Handler, Key-Handling, Env-Setup | 2–3 |
| Normalisierung inkl. Mapping auf UI-Zustände | 5–8 |
| Cache-Schicht mit Koordinaten-Quantisierung | 3–5 |
| Zeitformatierung, Zeitzonen, DST-Fälle | 2–4 |
| Degradation und Fehlerzustände in der UI | 3–5 |
| Tests gegen eingefrorene Fixtures | 4–6 |
| Summe | 19–31 |
Der Tutorial-Weg — `fetch` in einer Client-Komponente, Werte direkt rendern — ist in anderthalb Stunden fertig. Die Differenz ist kein Gold-Plating, sondern der Unterschied zwischen Demo und Betrieb. Wer auf die Wetterdaten zusätzlich ein KI-Feature setzt, etwa Textempfehlungen aus der Prognose, rechnet Token- und Infrastrukturkosten separat; die Faktoren dafür habe ich in KI-Integration in Web-Apps: Kostenfaktoren und Budgetplanung aufgeschlüsselt.
Der Punkt mit den eingefrorenen Fixtures verdient einen Satz: Teste gegen gespeicherte Antworten, nicht gegen die Live-API. Sonst hängt deine Pipeline am Rate Limit des Anbieters, und ein roter Build sagt dir nicht, ob dein Code kaputt ist oder das Kontingent leer.
Häufige Fragen
Welche API bietet kostenlose Wetterdaten an? Mehrere Dienste haben Einstiegskontingente, die Bedingungen ändern sich aber. Statt einer Momentaufnahme prüfst du drei Dinge auf der Preisseite des Anbieters: Kommerzielle Nutzung erlaubt, Limit-Einheit (Minute oder Tag) und Attributionspflicht. Diese drei entscheiden mehr über die Eignung als der Betrag.
Wo finde ich den API-Schlüssel, und warum bekomme ich trotzdem 401? Der Schlüssel steht im Konto-Bereich nach der Registrierung. Ein 401 direkt danach hat meist eine von drei Ursachen: Der Schlüssel ist noch nicht aktiviert, er steht im falschen Query-Parameter, oder die Env-Variable ist beim Build nicht geladen worden. Log serverseitig `process.env.WETTER_API_KEY?.length` — nicht den Wert.
Server Component oder Route Handler? Server-Komponente, wenn die Daten beim Seitenaufbau feststehen und der Standort aus der URL kommt. Route Handler, sobald der Client nachlädt — Standortwechsel, Auto-Refresh, Suchfeld. Beides zusammen ist der Normalfall: Erstansicht serverseitig gerendert, Aktualisierungen über die eigene Route.
Wann du dir die eigene Integration sparen kannst
Wenn niemand im Team benennen kann, welche Entscheidung ein Nutzer aufgrund der Wetteranzeige trifft, ist das Widget Dekoration. Dann reicht ein Embed des Anbieters, und die 19 bis 31 Stunden fließen in ein Feature, das jemand vermisst. Erst wenn die Anzeige eine Handlung auslöst — Termin verschieben, Route wählen, Trainingsfenster setzen — lohnen sich Normalisierung, Cache und Degradationslogik.
Wenn du an diesem Punkt entscheiden musst, ob das intern gebaut, an einen Freelancer oder an eine Agentur vergeben wird, hilft der Vergleich in Next.js Agentur DACH: Freelancer vs. Agentur im Vergleich.