# Next.js Component Library im App Router einrichten

> Next.js Component Library im App Router einrichten, getestet mit Next.js 16.4: use client, Tokens in globals.css, Dark Mode ohne Flackern, Updates per CLI.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 13. Okt. 2026
- Kategorie: [Next.js und Tailwind CSS](https://wingo-ui.com/de/blog/category/nextjs-tailwind)
- Lesezeit: 8 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/next-js-component-library-setup
- Englische Fassung: https://wingo-ui.com/blog/next-js-component-library-setup

## Kurz gesagt

Eine Komponentenbibliothek passt zum Next.js App Router, wenn jede interaktive Datei mit use client beginnt, beim Laden des Moduls nichts auf window zugreift, die Farben als CSS-Variablen in globals.css liegen und das Theme vor dem ersten Paint gesetzt wird. In einer neuen Next.js-16-App entfernst du den prefers-color-scheme-Block des Starters, führst npx wingo-ui@latest init aus, fügst Komponenten hinzu und umschließt das Root-Layout mit next-themes und UIProvider, mit suppressHydrationWarning am html-Tag. Server-Seiten importieren die Komponenten direkt; Event-Handler wandern in kleine Client-Dateien.

Die meisten Anleitungen für eine Next.js Component Library hören beim Installationsbefehl auf. Die Fragen kommen aber direkt danach: Welche Dateien brauchen `"use client"`? Warum kann eine Server-Seite kein `onClick` an einen Button geben? Warum flackert das dunkle Theme beim Neuladen? Und warum tut ein Theme Toggle in einer frischen App gar nichts? Dieser Guide beantwortet zuerst diese Fragen. Danach geht er ein komplettes Next.js Component Library Setup mit echten Befehlen durch, von `create-next-app` bis zum Update der Komponenten Monate später.

Alles hier haben wir am 13. Oktober 2026 ausgeführt, mit `create-next-app@latest` (Next.js 16.4.0, React 19.3.0, Tailwind CSS v4), der `wingo-ui` CLI 1.0.3 und next-themes 0.4.6. Die Bibliothek ist unsere eigene, Wingo UI, aber Teil 1 gilt für jede Bibliothek, deren Quellcode dir gehört, auch für ein shadcn Next.js App Router Projekt. Wie die Tokens im Detail funktionieren, erklärt der Pillar-Artikel zu [Tailwind v4 Design Tokens](https://wingo-ui.com/de/blog/tailwind-v4-design-tokens).

## Was macht eine Komponentenbibliothek fit für den App Router?

Vier Eigenschaften. Hat eine Bibliothek sie, steht dir der App Router nicht im Weg. Fehlen sie, schreibst du am Ende Wrapper.

### "use client" genau in der Datei, die es braucht

Im App Router ist jede Datei eine Server Component, bis sie etwas anderes sagt. Eine Bibliothek aus use client Komponenten markiert jede interaktive Datei ganz oben. So liegt die Grenze in der Bibliothek, und deine Seiten bleiben Server Components. Jede Datei von Wingo UI, die State, Effects oder Context nutzt, beginnt mit der Direktive, darunter [Button](https://wingo-ui.com/components/button), [Card](https://wingo-ui.com/components/card), [Dialog](https://wingo-ui.com/components/dialog) und der `UIProvider` in `lib/ui-config.tsx`.

Daraus ergibt sich eine Regel, die du dir merken solltest: Eine Server-Seite kann diese Komponenten rendern und ihnen Strings, Zahlen, Objekte und JSX übergeben, aber keine Funktionen. `<Button onClick={...}>` in einer Server-Seite schlägt fehl, weil React keine Funktion vom Server an den Browser schicken kann. Verschieb den Handler in eine kleine Client-Datei. Schritt 6 zeigt das Muster.

### Kein window auf Modulebene

Beim Server-Rendering wird jedes Modul auf dem Server importiert, und dort gibt es weder `window` noch `document` noch `matchMedia`. Eine Bibliothek, die diese Objekte beim Laden des Moduls oder während des Renderns liest, stürzt ab oder rendert auf beiden Seiten etwas anderes. Unsere hält sich an eine Regel: kein `window` und kein `document` auf Modulebene oder während des Renderns. Browser-Werte werden in Effects oder in `useSyncExternalStore` mit einem Server-Fallback gelesen. Deshalb gibt `useIsMobile()` auf dem Server `false` zurück und aktualisiert sich nach der Hydration. Der [Guide zum React Use Mobile Hook](https://wingo-ui.com/de/blog/react-use-mobile-hook-ssr) zeigt, warum das fürs Layout wichtig ist.

### Ein Theme ohne Flackern

Der Server weiß nicht, ob jemand Dark Mode gewählt hat, denn diese Wahl liegt in `localStorage`. Die Lösung ist ein winziges Script, das die Klasse auf `<html>` vor dem ersten Paint setzt. next-themes fügt dieses Script für dich ein. React sieht dann ein `<html>`-Element, das vom Server-HTML abweicht, und deshalb trägt das Root-Layout `suppressHydrationWarning`. Mehr dazu in Schritt 5 und in unseren [Guides zu Next.js und Tailwind CSS](https://wingo-ui.com/de/blog/category/nextjs-tailwind).

### Tokens in CSS, nicht in einer JavaScript-Runtime

Farben, Radien, Schatten und Animationszeiten sind CSS-Variablen in `globals.css`, die über `@theme inline` auf Utilities abgebildet werden. Dark Mode tauscht die Variablen unter `.dark`. Kein JavaScript muss laufen, damit klar ist, was `bg-surface` bedeutet. Server Components rendern also die richtigen Farben, und es gibt keinen Theme Provider, auf den du warten musst. Deshalb lassen sich Next.js Tailwind v4 Komponenten so leicht themen: Du bearbeitest eine einzige Datei.

## Wie richte ich eine Next.js Component Library Schritt für Schritt ein?

### 1. App anlegen

Überspring das in einem bestehenden Projekt, das schon mit Tailwind CSS v4 läuft.

```bash
npx create-next-app@latest my-app --ts --tailwind --eslint --app --import-alias "@/*"
cd my-app
```

Mit 16.4.0 legt der Befehl eine `app/globals.css` mit `@import "tailwindcss";`, eine `tsconfig.json` mit dem Alias `@/*` und eine dunkle Palette in `@media (prefers-color-scheme: dark)` an.

### 2. Den dunklen Media-Block des Starters entfernen

Diesen Schritt übersieht man leicht. Findet `wingo-ui init` einen `prefers-color-scheme`-Block, respektiert es ihn und legt die dunklen Werte der Bibliothek ebenfalls dort ab. Das passt für eine App, die nur der Systemeinstellung folgt. next-themes schaltet aber eine `.dark`-Klasse um, und eine Media Query ignoriert diese Klasse. Lösch diesen Block aus `app/globals.css`, bevor du init ausführst:

```css
@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}
```

Ohne den Block schreibt init stattdessen einen `.dark`-Block und `@custom-variant dark (&:is(.dark *));`. Wir haben init in beiden Varianten ausgeführt: Mit dem Block meldete es `dark mode: media`, ohne ihn `dark mode: class`. Die [Theming-Doku](https://wingo-ui.com/docs/theming) beschreibt beide Modi.

### 3. init ausführen

```bash
npx wingo-ui@latest init
```

Die CLI erkennt das Framework anhand der `package.json`, den Alias `@/*` anhand der `tsconfig.json` und das Stylesheet, das Tailwind importiert. Dann:

- installiert sie `clsx` und `tailwind-merge` und legt `lib/utils.ts` mit dem Helper `cn` an,
- führt sie die Design Tokens in `app/globals.css` zusammen und ergänzt nur Namen, die du noch nicht hast, sodass `--background` und `--foreground` aus dem Starter bleiben,
- schreibt sie `wingo-ui.json` mit Framework, Aliassen, Stylesheet und Paketmanager.

Unser Durchlauf gab `added 72 variables, 46 dark values, 72 @theme mappings, @custom-variant dark, base border color (dark mode: class)` aus. Mit `--yes` überspringt sie die Abfrage, ob du einen Coding-Agenten verbinden willst.

### 4. Komponenten hinzufügen

```bash
npx wingo-ui@latest add button dialog card
```

Die Dateien landen in `components/ui`, `hooks` und `lib`. Der Befehl holt alles, was die drei Items importieren: den [Drawer](https://wingo-ui.com/components/drawer), zu dem Dialog auf Smartphones wird, `lib/motion.ts`, `lib/tones.ts`, `lib/ui-config.tsx` und vier Hooks. Außerdem installiert er die npm-Pakete (`@radix-ui/react-dialog`, `motion`, `vaul`, `lucide-react` und ein paar weitere). Kostenlose Items brauchen weder Account noch Key.

`wingo-ui.json` speichert jetzt jedes Item mit seiner Version und einem SHA-256-Hash pro Datei, und die CLI legt die installierten Kopien in `.wingo-ui/` ab. Committe beides: Damit kann `update` neue Releases in Dateien mergen, die du bearbeitet hast.

### 5. Das Root-Layout mit den Providern umschließen

Installier next-themes und ersetz dann `app/layout.tsx`:

```bash
npm install next-themes
```

```tsx
// app/layout.tsx
import { ThemeProvider } from "next-themes"
import { UIProvider } from "@/lib/ui-config"
import "./globals.css"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
          <UIProvider locale="en-US" defaults={{ button: { radius: "full" } }}>
            {children}
          </UIProvider>
        </ThemeProvider>
      </body>
    </html>
  )
}
```

Das Layout bleibt eine Server Component. `ThemeProvider` und `UIProvider` sind Client Components, und die Props, die sie bekommen, sind reine Daten. Das ist also erlaubt.

- `attribute="class"` sorgt dafür, dass next-themes `class="dark"` auf `<html>` setzt. Danach richten sich die `.dark`-Tokens und die `dark:`-Variante.
- `suppressHydrationWarning` ist nötig, weil das Theme-Script `<html>` ändert, bevor React hydriert. Es gilt nur für die Attribute dieses einen Elements, eine Ebene tief, und versteckt daher keine echten Mismatches in deinen Seiten.
- `disableTransitionOnChange` verhindert, dass alle Farbübergänge gleichzeitig animieren, wenn das Theme wechselt.
- [`UIProvider`](https://wingo-ui.com/components/ui-config) setzt app-weite Defaults pro Komponentenname und die Locale für Datums- und Zahlenformate. Hier ist jeder Button voll abgerundet, solange ein Button `radius` nicht selbst setzt; explizite Props gewinnen immer.

### 6. Eine Client-Komponente in einer Server-Seite nutzen

Seiten bleiben asynchrone Server Components, die Daten laden. Komponenten ohne Handler renderst du direkt von der Seite aus, und JSX darf durch Props wandern. Ein ganzer Dialog mit Trigger und Footer funktioniert also ohne eigene Client-Datei:

```tsx
// app/page.tsx
import { Button } from "@/components/ui/button"
import { Dialog, DialogClose } from "@/components/ui/dialog"
import { InviteForm } from "./invite-form"

async function getProject() {
  return { name: "Website-Relaunch", members: 4 }
}

export default async function Page() {
  const project = await getProject()

  return (
    <main className="mx-auto flex max-w-xl flex-col gap-6 p-6">
      <h1 className="text-xl font-semibold">{project.name}</h1>
      <Dialog
        trigger={<Button tone="brand">Leute einladen</Button>}
        title="Leute einladen"
        description={`Sie bekommen Zugriff auf ${project.name}.`}
        footer={
          <DialogClose asChild>
            <Button variant="soft">Fertig</Button>
          </DialogClose>
        }
      >
        <InviteForm />
      </Dialog>
    </main>
  )
}
```

Der Teil mit State und `onClick` kommt in eine eigene Datei:

```tsx
// app/invite-form.tsx
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"

export function InviteForm() {
  const [sent, setSent] = useState(false)
  return (
    <Button variant="outline" onClick={() => setSent(true)} disabled={sent}>
      {sent ? "Einladung gesendet" : "Einladung an emma@example.com senden"}
    </Button>
  )
}
```

Beide Dateien bestehen `tsc --noEmit` und ESLint mit der Konfiguration, die `create-next-app` erzeugt. Der Dialog unten ist dieselbe Komponente mit einem Formular darin. Probier ihn auch auf einem schmalen Bildschirm aus.

> Live-Demo (Dialog): Tippe auf Edit, um den Dialog zu öffnen. Unter 768px öffnet sich dieselbe Komponente als Bottom Sheet, das du zum Schließen nach unten ziehst. Probier sie unter [Dialog](https://wingo-ui.com/components/dialog) aus und installier sie mit `npx wingo-ui@latest add dialog`.

### 7. Einen Theme Toggle ergänzen

[Theme Toggle](https://wingo-ui.com/components/theme-toggle) ist eine Pro-Komponente: ein animierter Button mit Sonne und Mond, ein Segmented Control oder ein Dropdown, alle auf Basis von next-themes. Im kostenlosen Tarif reicht eine kurze Client-Datei:

```tsx
// components/theme-switch.tsx
"use client"

import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeSwitch() {
  const { resolvedTheme, setTheme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      aria-label="Theme wechseln"
      onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
    >
      <Moon className="dark:hidden" />
      <Sun className="hidden dark:block" />
    </Button>
  )
}
```

Die Icons wechseln per CSS. Das Server-HTML und der erste Client-Render stimmen also überein, und du brauchst kein mounted-Flag. Die häufigsten Ursachen für die Hydration-Warnung behandelt unser [Fix für den next-themes Hydration Error](https://wingo-ui.com/de/blog/next-themes-hydration-error), die Variante und die Tokens hinter dem Toggle unser [Guide zu Tailwind v4 Dark Mode](https://wingo-ui.com/de/blog/tailwind-v4-dark-mode). Weitere Artikel findest du unter [Next.js und Tailwind CSS](https://wingo-ui.com/de/blog/category/nextjs-tailwind).

### 8. Für Pro-Komponenten anmelden

Pro-Items brauchen einen Key. Melde die CLI einmal pro Rechner an:

```bash
npx wingo-ui@latest login
```

Sie öffnet den Browser, um das Gerät freizugeben, und speichert einen Key unter `~/.config/wingo-ui`. Auf einem Server oder in der CI übergibst du stattdessen einen Key aus deinem Account:

```bash
npx wingo-ui@latest login --key wui_...
# oder
export WINGO_UI_API_KEY=wui_...
```

Ohne Key bricht das Hinzufügen eines Pro-Items mit einer Meldung ab, die das Item nennt und auf die Tarife verlinkt. Die Tarife stehen auf der [Preisseite](https://wingo-ui.com/de/pricing).

### 9. Komponenten aktuell halten

```bash
npx wingo-ui@latest outdated      # was sich upstream geändert hat
npx wingo-ui@latest diff button   # deine Datei gegen das neueste Release
npx wingo-ui@latest update        # neue Releases in deine Kopien mergen
```

Wir haben ein Label in einer installierten Datei geändert und `diff` ausgeführt: Es gab einen Unified Diff unserer Änderung gegen upstream aus. `update` macht einen Three-Way-Merge mit der Kopie in `.wingo-ui/`, behält deine Änderungen und markiert echte Konflikte wie git. Die [CLI-Doku](https://wingo-ui.com/docs/cli) listet jedes Flag auf.

## Was prüfe ich, wenn etwas falsch aussieht?

| Symptom | Wahrscheinliche Ursache | Lösung |
| --- | --- | --- |
| Theme Toggle ändert nichts | Dunkle Werte stehen in einem `prefers-color-scheme`-Media-Block | Schritt 2, dann init erneut ausführen |
| „Functions cannot be passed directly to Client Components“ | `onClick` wird von einer Server-Seite übergeben | Handler in eine Client-Datei verschieben (Schritt 6) |
| Hydration-Warnung auf `<html>` | `suppressHydrationWarning` fehlt | Nur auf `<html>` ergänzen |
| Komponenten ohne Styles, `bg-surface` wirkt nicht | Tokens fehlen im Stylesheet, das Tailwind liest | `npx wingo-ui@latest init` erneut ausführen; es ergänzt nur fehlende Tokens |
| Pro-Item lässt sich nicht installieren | Kein Key auf diesem Rechner | `npx wingo-ui@latest login` oder `WINGO_UI_API_KEY` |

## Wie geht es weiter?

Fang mit den kostenlosen Basics an: `npx wingo-ui@latest add button dialog card`. Danach folgst du der [Installations-Doku](https://wingo-ui.com/docs/installation) für die shadcn CLI und den manuellen Weg. Wenn du UI mit Claude Code, Codex oder Cursor schreibst, verbinde den [MCP-Server](https://wingo-ui.com/docs/mcp), damit der Agent die Bibliothek durchsucht, bevor er eine Komponente schreibt. Du baust mit Vite statt mit Next.js? Das [shadcn Vite Setup](https://wingo-ui.com/de/blog/shadcn-vite-setup) zeigt dieselben Schritte dort.

## Komponenten in diesem Artikel

- [Button](https://wingo-ui.com/components/button) (Free): The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps. Installation: `npx wingo-ui@latest add button`
- [Dialog](https://wingo-ui.com/components/dialog) (Free): A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling. Installation: `npx wingo-ui@latest add dialog`
- [UIProvider](https://wingo-ui.com/components/ui-config) (Free): A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy. Installation: `npx wingo-ui@latest add ui-config`
- [Theme Toggle](https://wingo-ui.com/components/theme-toggle) (Pro): A React theme toggle for light, dark and system mode on next-themes: an animated sun and moon button, a segmented control or a menu. Installation: `npx wingo-ui@latest add theme-toggle`

## Häufige Fragen

### Kann ich eine Client-Komponentenbibliothek in Next.js Server Components nutzen?

Ja. Eine Server-Seite kann eine Datei mit use client importieren, rendern und ihr Strings, Zahlen und JSX übergeben. Funktionen wie onClick kann sie nicht übergeben, also steckst du Handler in eine kleine Client-Komponente und renderst diese in der Seite.

### Warum braucht das Root-Layout suppressHydrationWarning?

Bevor React hydriert, gibt ein Script von next-themes dem html-Element die Klasse dark oder light. Das Server-HTML hatte diese Klasse nicht, also würde React einen Mismatch melden. Das Attribut unterdrückt die Warnung nur für dieses eine Element.

### Warum wechselt mein Theme Toggle nach wingo-ui init die Farben nicht?

Das Stylesheet von create-next-app bringt einen Block @media (prefers-color-scheme: dark) mit, und init legt die dunklen Werte dort ab, wenn es einen findet. Damit die Klasse von next-themes gewinnt, löschst du den Block vor init oder verschiebst die dunklen Werte in einen .dark-Block und ergänzt @custom-variant dark (&:is(.dark *));

### Brauchen die kostenlosen Komponenten von Wingo UI einen Account oder API-Key?

Nein. Kostenlose Items wie button, card und dialog installierst du mit npx wingo-ui@latest add, ganz ohne Key. Pro-Items brauchen einmal pro Rechner npx wingo-ui@latest login oder WINGO_UI_API_KEY in der CI.

### Funktioniert dieses Setup mit Next.js 15?

Wir haben jeden Schritt auf Next.js 16.4.0 ausgeführt. Die Bibliothek braucht React 19 und Tailwind CSS v4, beides geht auch in einem Next.js-15-Projekt mit App Router. Dort haben wir die Schritte aber nicht wiederholt.

---

Quelle: https://wingo-ui.com/de/blog/next-js-component-library-setup
