Icons in Next.js
glyphfox funktioniert in beiden Routern. Im App Router benötigen die React-Komponenten eine Client-Boundary aus einer einzigen Datei, und Server Components können dieselben Icons als reines SVG ausgeben, das keinerlei JavaScript mitliefert.
Installation
Installieren Sie das Paket. Es wird als kompiliertes ESM und CommonJS mit Typdeklarationen ausgeliefert, daher müssen Sie in next.config.js nichts ergänzen: kein transpilePackages, keinen Loader und kein Stylesheet.
npm install glyphfox
Die React-Komponenten stammen aus glyphfox/react und werden in der React-Anleitung ausführlich behandelt. Diese Seite zeigt, was sich in Next.js ändert: wo Komponenten laufen dürfen, wie Sie Icons aus dem Client-Bundle heraushalten und was der Server rendert.
Warum der App Router eine Client-Boundary braucht
Im App Router ist jedes Modul eine Server Component, es sei denn, die Datei ist mit "use client" gekennzeichnet. Die glyphfox-Komponenten verwalten ihren Zustand für Hover, Klick und Morphing mit useState, useEffect und useRef, und das Paket enthält keine "use client"-Direktive. Wenn Sie <HomeIcon /> direkt in app/page.tsx rendern, scheitert das deshalb mit einer Fehlermeldung, dass Hooks nur in Client Components funktionieren.
Die Lösung besteht nicht darin, ganze Seiten zu Client Components zu machen. Kennzeichnen Sie eine kleine Datei als Boundary und importieren Sie Ihre Icons über diese Datei. Server Components können Client Components rendern; die Icons werden trotzdem auf dem Server zu HTML gerendert und anschließend im Browser hydriert.
Eine Client-Datei für Ihre Icons
'use client'; export { HomeIcon, SearchIcon, BellIcon, MenuIcon, Icon, MorphIcon, Fusion } from 'glyphfox/react';
import { HomeIcon, SearchIcon, Fusion } from '@/components/icons'; export default function Page() { return ( <header className="bar"> <a href="/"><HomeIcon size={20} /> Home</a> <SearchIcon title="Search" /> <Fusion icons={['cloud', 'lock']} layout="overlap" /> </header> ); }
Re-exportieren Sie die Namen einzeln. Next.js unterstützt export * in einer Client-Boundary nicht, und benannte Exporte erhalten das Tree-Shaking: Nur die in der Datei aufgeführten Icons landen in den Client-Chunks, und davon auch nur die, die eine Seite tatsächlich verwendet.
In einer Datei, die bereits mit "use client" beginnt, können Sie direkt aus glyphfox/react importieren. Die Boundary-Datei ist nur für Icons gedacht, die Sie aus Server-Dateien rendern.
Interaktive Icons in Client Components
Event-Handler und Refs lassen sich nicht von einer Server Component an eine Client Component übergeben, weil Props an dieser Grenze serialisierbar sein müssen. Alles, was auf Benutzeraktionen reagiert, etwa ein Umschalter mit Morph, gehört in eine eigene Client Component:
'use client'; import { useState } from 'react'; import { MorphIcon } from 'glyphfox/react'; export function ThemeToggle() { const [dark, setDark] = useState(true); return ( <button type="button" onClick={() => setDark((d) => !d)} aria-label={dark ? 'Switch to the light theme' : 'Switch to the dark theme'} > <MorphIcon name={dark ? 'moon' : 'sun'} duration={300} /> </button> ); }
Der Server rendert das Ausgangs-Icon, und das Morphing läuft im Browser ab, sobald die Komponente hydriert ist. Halten Sie den ersten Zustand auf beiden Seiten identisch: Wenn Sie beim Rendern localStorage oder die Uhrzeit auslesen, weichen Server und Client voneinander ab, und React meldet einen Hydration-Mismatch.
Icons ohne JavaScript in Server Components
Die meisten Icons ändern sich nach dem Laden der Seite nie. Für diese Icons kann eine Server Component die Icon-Daten mit renderToString aus glyphfox/core in einen SVG-String umwandeln. Der Code läuft nur auf dem Server, daher kommen die Icons als Markup an und vergrößern das Client-Bundle nicht.
import { renderToString } from 'glyphfox/core'; import type { IconData, SvgOptions } from 'glyphfox'; type Props = SvgOptions & { icon: IconData }; export function ServerIcon({ icon, ...options }: Props) { return <span className="icon" dangerouslySetInnerHTML={{ __html: renderToString(icon, options) }} />; }
import fileText from 'glyphfox/icons/file-text'; import externalLink from 'glyphfox/icons/external-link'; import { ServerIcon } from '@/components/server-icon'; export default function Docs() { return ( <a href="https://example.com/handbook"> <ServerIcon icon={fileText} size={18} /> Handbook <ServerIcon icon={externalLink} size={14} title="opens in a new tab" /> </a> ); }
Jeder Import von glyphfox/icons/NAME ist ein kleines Datenmodul. Wenn Sie mit Namen und der vollständigen Namenssyntax arbeiten möchten (file+check, cloud&lock:side, heart/solid), verwenden Sie stattdessen toSvg aus dem Haupteinstiegspunkt. Es bringt die gesamte Icon-Registry mit, doch in einer Server Component belastet das nur den Server:
import { toSvg } from 'glyphfox'; export default function Status() { return <span className="icon" dangerouslySetInnerHTML={{ __html: toSvg('file+check', { size: 20 }) }} />; }
Die Optionen tragen die Namen des Cores: class statt className und style als String. Für dangerouslySetInnerHTML ist ein Wrapper-Element nötig; sorgen Sie dafür, dass es sich wie das Icon selbst verhält, damit es keine zusätzliche Zeilenhöhe erzeugt:
.icon { display: inline-flex; line-height: 0; vertical-align: middle; }
Lebendige Zustände funktionieren auch ohne JavaScript, weil die Animation ein kleines Stylesheet im SVG ist: renderToString(loader, { animate: 'spin' }) rotiert, und renderToString(menu, { morphTo: x, trigger: 'hover' }) morpht beim Hover per CSS-Transition. Auslöser für Klick und Zustand benötigen eine Client Component.
Icons per Name auf dem Server
Icon, morphTo und Fusion schlagen Namen in einer Registry nach, die jede importierte Icon-Komponente befüllt. Ein Name, den niemand importiert hat, wird nach dem Laden der Seite per dynamischem Import nachgeladen. Der Server sendet dafür also ein leeres SVG, und das Icon taucht erst nach der Hydration auf. Registrieren Sie die Daten aller Namen, die Sie aus Strings rendern, in der Boundary-Datei; sie läuft während des Renderings auf dem Server und erneut im Browser:
'use client'; import { registerIcons } from 'glyphfox/react'; import { iconSettings, iconUsers, iconChartBar } from 'glyphfox'; registerIcons([iconSettings, iconUsers, iconChartBar]); export { Icon, HomeIcon } from 'glyphfox/react';
Die Daten-Exporte unterliegen wie die Komponenten dem Tree-Shaking. Achten Sie auf einen Unterschied zwischen den Umgebungen: Entwicklungs-Builds werten unter Umständen jedes Icon-Modul aus. Eine fehlende Registrierung kann daher mit next dev unauffällig bleiben und erst nach next build leer gerendert werden, weil dort ungenutzte Module entfallen.
Pages Router
Der Pages Router kennt keine Server Components, daher funktionieren die Komponenten unverändert in Seiten, Layouts und _app. Jede Seite wird auf dem Server gerendert und hydriert, deshalb ist die obige Registrierung hier genauso nützlich: Rufen Sie registerIcons in pages/_app.tsx auf, wenn Sie Icons per Name rendern.
import type { AppProps } from 'next/app'; import { registerIcons } from 'glyphfox/react'; import { iconSettings, iconUsers } from 'glyphfox'; registerIcons([iconSettings, iconUsers]); export default function App({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; }
Ein Icon als Favicon
Der App Router erkennt app/icon.svg als Website-Icon. Die CLI schreibt jedes beliebige Icon mit einer expliziten Farbe dorthin, denn ein Favicon hat keine umgebende Textfarbe, an der sich currentColor orientieren könnte:
npx glyphfox copy fox --color "#ff7a1a" --weight 2.5 --out app/icon.svg
Für PNG-App-Icons in mehreren Größen exportiert das Brand-Kit im Studio ein Favicon-Set, abgestimmt auf Ihre Farbe, Strichstärke und Ihren Stil.
Was die Build-Ausgabe enthält
Mit webpack erzeugt next build viele winzige Client-Chunks, ungefähr einen pro Icon im Set. Sie stammen vom On-Demand-Loader hinter Icon und morphTo: Sein dynamischer Import zeigt auf den Icon-Ordner, daher legt der Bundler für jedes Icon, das per Name angefordert werden könnte, einen Chunk an. Jeder ist nur einige hundert Byte groß und wird nur heruntergeladen, wenn eine Seite einen Namen rendert, der nicht importiert wurde. Unabhängig vom Bundler gilt: Wenn Sie die Namen registrieren, die Sie aus Strings rendern (siehe oben), werden diese Chunks nie angefordert. Die Icons, die Sie direkt importieren, liegen in Ihren regulären Chunks. Messwerte finden Sie in der Anleitung zum Tree-Shaking.
Props
Die Komponenten akzeptieren dieselben Props wie in jeder React-App. Wenn eine Server Component sie über die Boundary rendert, sind alle unten aufgeführten Props einfache Werte und können die Boundary passieren; nur Callbacks wie onClick sowie ref müssen innerhalb einer Client Component gesetzt werden. Die Core-Optionen von renderToString und toSvg tragen dieselben Namen.
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
size | number, string | 24 | Breite und Höhe; Zahlen sind Pixel. |
color | string | currentColor | Hauptfarbe. |
secondaryColor | string | color | Farbe der Duotone-Ebene und der Solid-Füllung. |
secondaryOpacity | number | 0.3 / 1 | Deckkraft der Duotone-Ebene oder der Solid-Füllung. |
weight | number | 2 | Strichstärke von 1 bis 3 im 24er-Raster, unter 24 px optisch ausgeglichen. |
absoluteWeight | boolean | false | Hält den Strich bei jeder Größe auf weight Pixeln. |
corners | round, sharp | round | Abgerundete oder spitze Verbindungen und Enden. |
variant | outline, duotone, solid | outline | Stilachse; solid entsteht aus den geschlossenen Formen. |
rotate | number | 0 | Drehung in Grad. |
flipX | boolean | false | Horizontal spiegeln. |
flipY | boolean | false | Vertikal spiegeln. |
mirrorRtl | boolean | false | In Layouts von rechts nach links spiegeln. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | keiner | Voreinstellung für den lebendigen Zustand. |
trigger | mount, hover, click, state | mount | Wann die Animation oder der Morph abläuft. |
active | boolean | false | Der Zustand für den Auslöser state. |
duration | number (ms) | je nach Voreinstellung | Dauer der Animation oder des Morphs. |
delay | number (ms) | 0 | Verzögerung der Animation oder des Morphs. |
loop | boolean, number | false | Endlos oder eine bestimmte Anzahl von Malen wiederholen (spin und pulse laufen standardmäßig in Schleife). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | keiner | Badge mit automatischer Aussparung. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Ecke des Badges. |
morphTo | name, data | keiner | Ziel des Morphs; der Auslöser bestimmt, wann es erscheint. |
title | string | keiner | Zugänglicher Name: title, role img und aria-label. |
decorative | boolean | true ohne title | Verbirgt das Icon vor assistiven Technologien. |
Stolperfallen
- Hook-Fehler in Server Components
- Ein Import aus
glyphfox/reactin einer Server-Datei schlägt fehl, weil die Komponenten Hooks verwenden. Importieren Sie über eine Datei mit"use client", oder rendern Sie statisches SVG mitrenderToString. export *in der Boundary- Next.js lehnt
export *in einer Client-Boundary ab. Führen Sie die Icons als benannte Exporte auf. - Funktionen als Props vom Server
onClick,onMouseEnterund Refs sind nicht serialisierbar. Verlagern Sie interaktive Icons in eine Client Component und reichen Sie nur Daten nach unten weiter.- Leere Icons nach
next build - Ein
Icon-Name, der nie importiert wurde, wird auf dem Server leer gerendert. Registrieren Sie seine Daten mitregisterIconsin der Boundary-Datei oder in_app. - Unbekannte Namen werfen Fehler
toSvgwirft bei einem nicht existierenden Namen einen Fehler, der zu einem Serverfehler wird. Gleichen Sie von Nutzern übergebene Namen vor dem Rendern miticonNamesausglyphfoxab.- Zusätzlicher Abstand um Server-Icons
- Der Wrapper für
dangerouslySetInnerHTMList ein Inline-Element mit eigener Zeilenbox. Geben Sie ihmdisplay: inline-flexundline-height: 0.
Häufige Fragen
Brauche ich transpilePackages für glyphfox?
Nein. Das Paket liefert reines JavaScript als ESM und CommonJS mit Typdeklarationen, daher löst Next.js es wie jede andere kompilierte Abhängigkeit auf und bündelt es.
Kann ich die Web Component glyph-fox in Next.js verwenden?
Ja, importieren Sie glyphfox/web in einer Client Component. Das Modul lässt sich beim serverseitigen Rendering gefahrlos auswerten, aber ein Custom Element zeichnet erst, nachdem der Browser sein Upgrade durchgeführt hat. Das Server-HTML enthält daher an der Stelle des Icons ein leeres Element. In einer React-App sind die Komponenten die bessere Wahl, weil ihr SVG bereits im Server-HTML steht; die Anleitung zu Web Components beschreibt das Element.