Prompt-to-PR: Dunkelmodus hinzufügen
End-to-End-SOP zum Hinzufügen eines flackerfreien Dunkelmodus zu einer Next.js Tailwind-App mit next-themes — Klassenstrategie, Systemeinstellung und persistierter Umschalter.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptTailwind
Falsch implementierter Dunkelmodus verursacht bei jedem Seitenaufruf ein Aufblitzen des falschen Designs (FOIT). Dieses Playbook liefert eine flackerfreie Implementierung mit next-themes und Tailwinds class-Strategie.
1. Anforderung
Fügen Sie einen persistenten Dunkel-/Hell-/Systemmodus-Umschalter zu einer Next.js 15 App Router-Site mit Tailwind hinzu. Das Design muss:
- Flackerfrei beim ersten Laden (kein weißer Blitz im Dunkelmodus).
- In
localStoragegespeichert. - Standardmäßig die Betriebssystemeinstellung (
system) verwenden. - Über einen Button in der Site-Header umschaltbar sein.
2. Erster Prompt
Add flicker-free dark mode to this Next.js 15 App Router project with Tailwind.
Requirements:1. Install `next-themes`.2. In `tailwind.config.ts`, set `darkMode: "class"`.3. Create `src/components/ThemeProvider.tsx` — a thin Client Component wrapper around `next-themes` ThemeProvider: <ThemeProvider attribute="class" defaultTheme="system" enableSystem> {children} </ThemeProvider> Mark it "use client".4. Wrap the root layout's `<body>` with ThemeProvider in `src/app/layout.tsx`. Keep the body tag; just wrap children.5. Create `src/components/ThemeToggle.tsx` — a Client Component with a button that cycles: light -> dark -> system. Use `useTheme()` from next-themes. Show the current theme as an icon or label. Handle the mounted check to avoid hydration mismatch (render null or a skeleton until mounted).6. Add `<ThemeToggle />` to the existing site header/nav component.7. Do not change any color values or CSS — only add `dark:` Tailwind variants where the existing design clearly has background and text colors that need inversion. Update: bg-white -> bg-white dark:bg-gray-950, text-gray-900 -> text-gray-900 dark:text-gray-100. Do not invert every element — only top-level layout containers.3. Erwartete Dateiänderungen
package.json (next-themes)tailwind.config.ts (darkMode: "class")src/app/layout.tsx (ThemeProvider wrapper)src/components/ThemeProvider.tsx (new — "use client" wrapper)src/components/ThemeToggle.tsx (new — theme cycling button)src/components/Header.tsx (add ThemeToggle — or whichever nav file)4. Überprüfungscheckliste
tailwind.config.tsenthältdarkMode: "class"— nicht"media"(media erlaubt keinen Umschalter).ThemeProviderist eine Client-Komponente ("use client"), diechildrenaus dem Server-Komponenten-Root-Layout umschließt.- Root
<html>oder<body>hat KEINE hartcodierteclass="dark"—next-themesfügt diese dynamisch hinzu. ThemeTogglerendertnull(oder einen Platzhalter), bismounted === trueist, um Hydrationskonflikte zu vermeiden.- Kein fehlendes
suppressHydrationWarningauf<html>—next-themeserfordert es; bestätigen Sie, dass es auf dem<html>-Tag vorhanden ist. dark:-Varianten erscheinen nur auf Layout-Ebene-Containern, nicht auf jedem einzelnen Element.- Der von
next-themesverwendetelocalStorage-Schlüssel ist der Standardtheme— kein benutzerdefinierter Schlüssel, der mit anderem Speicher kollidiert.
5. Testbefehle
bun dev
# In Chrome DevTools: set prefers-color-scheme to dark# Reload — confirm no white flash before dark theme applies
# Toggle the button — confirm light/dark/system cycle# Reload after each — confirm preference persists
bun tsc --noEmitbun run build# Confirm no hydration warnings in the browser console after bun run start6. Häufige Fehler
- Weißer Blitz beim Laden (FOIT) —
ThemeProviderumschließt nicht das Root-Layout, odersuppressHydrationWarningfehlt auf<html>.next-themesinjiziert ein Skript in<head>, um die Klasse vor dem Rendern zu setzen — das funktioniert nur, wenn es das äußerste Element umschließt. - Hydrationskonflikt-Fehler —
ThemeTogglerendert designabhängige Inhalte (Symbol) vormounted. Fügen Sieconst [mounted, setMounted] = useState(false)unduseEffect(() => setMounted(true), [])hinzu. Geben Sie einen Platzhalter zurück, bismountedwahr ist. dark:-Klassen werden nicht angewendet —tailwind.config.tshat immer nochdarkMode: "media"oder der Schlüssel fehlt. Die Klassenstrategie erfordert den Wert"class".- Umschalter nicht sichtbar —
<ThemeToggle>wird in einer Server-Komponente ohne"use client"-Grenze importiert. Die Importkette muss durch eine Client-Komponente führen.
7. Korrektur-Prompt
There is a white flash before dark mode applies on page load.
Confirm:1. In src/app/layout.tsx, the <html> tag has suppressHydrationWarning.2. ThemeProvider wraps {children} directly inside <body>.3. ThemeProvider uses attribute="class" (not attribute="data-theme").
next-themes injects an inline script into <head> that reads localStorage andsets the class on <html> synchronously before paint. This only works if theThemeProvider is rendered above the content it styles.8. PR-Beschreibung
## Feature: Flicker-free dark mode via next-themes
- `tailwind.config.ts`: `darkMode: "class"`- `ThemeProvider` wraps root layout — `next-themes` sets `<html class="dark">` synchronously before first paint (no flash)- `ThemeToggle` cycles light / dark / system; persists to `localStorage`- Mounted guard in ThemeToggle prevents hydration mismatch- `dark:` variants added to top-level layout containers only