Frameworks

Icônes dans Next.js

glyphfox fonctionne avec les deux routeurs. Dans l'App Router, les composants React ont besoin d'une frontière client tenant en un seul fichier, et les Server Components peuvent générer les mêmes icônes en SVG simple, sans envoyer la moindre ligne de JavaScript.

Installation

Installez le paquet. Il est livré compilé en ESM et en CommonJS avec ses déclarations de types, si bien qu'il n'y a rien à ajouter à next.config.js : ni transpilePackages, ni loader, ni feuille de style.

terminal
npm install glyphfox

Les composants React proviennent de glyphfox/react et sont présentés en détail dans le guide React. Cette page traite de ce qui change avec Next.js : où les composants peuvent s'exécuter, comment garder les icônes hors du bundle client et ce que rend le serveur.

Pourquoi l'App Router exige une frontière client

Dans l'App Router, chaque module est un Server Component, sauf si son fichier est marqué par "use client". Les composants glyphfox gèrent l'état du survol, du clic et du morph avec useState, useEffect et useRef, et le paquet ne contient pas de directive "use client". Rendre <HomeIcon /> directement depuis app/page.tsx échoue donc, avec une erreur signalant des hooks qui ne fonctionnent que dans les Client Components.

La solution n'est pas de convertir des pages entières en Client Components. Marquez un seul petit fichier comme frontière et importez vos icônes depuis celui-ci. Les Server Components peuvent rendre des Client Components : les icônes sont toujours rendues en HTML sur le serveur, puis hydratées dans le navigateur.

Un seul fichier client pour vos icônes

components/icons.ts
'use client';

export { HomeIcon, SearchIcon, BellIcon, MenuIcon, Icon, MorphIcon, Fusion } from 'glyphfox/react';
app/page.tsx
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>
  );
}

Réexportez les noms un par un. Next.js ne prend pas en charge export * dans une frontière client, et les exports nommés préservent le tree shaking : seules les icônes listées dans le fichier, et uniquement celles qu'une page utilise réellement, se retrouvent dans les chunks client.

Dans un fichier qui commence déjà par "use client", vous pouvez importer directement depuis glyphfox/react. Le fichier frontière ne sert qu'aux icônes que vous rendez depuis des fichiers serveur.

Icônes interactives dans les Client Components

Les gestionnaires d'événements et les refs ne peuvent pas passer d'un Server Component à un Client Component, car les props qui franchissent cette frontière doivent être sérialisables. Tout ce qui réagit à l'utilisateur, comme un bouton bascule avec un morph, a sa place dans son propre Client Component :

components/theme-toggle.tsx
'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>
  );
}

Le serveur rend l'icône de départ, et le morph se joue dans le navigateur une fois le composant hydraté. Gardez un état initial identique des deux côtés : lire localStorage ou l'heure pendant le rendu crée un désaccord entre le serveur et le client, et React signale une incohérence d'hydratation.

Icônes sans JavaScript dans les Server Components

La plupart des icônes ne changent plus une fois la page chargée. Pour celles-ci, un Server Component peut convertir les données d'une icône en chaîne SVG avec renderToString de glyphfox/core. Ce code ne s'exécute que sur le serveur : les icônes arrivent sous forme de balisage et n'ajoutent rien au bundle client.

components/server-icon.tsx
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) }} />;
}
app/docs/page.tsx
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>
  );
}

Chaque import glyphfox/icons/NAME est un petit module de données. Si vous voulez utiliser des noms et la syntaxe complète des noms (file+check, cloud&lock:side, heart/solid), utilisez plutôt toSvg depuis le point d'entrée principal. Il embarque tout le registre d'icônes, mais dans un Server Component, ce poids reste côté serveur :

app/status/page.tsx
import { toSvg } from 'glyphfox';

export default function Status() {
  return <span className="icon" dangerouslySetInnerHTML={{ __html: toSvg('file+check', { size: 20 }) }} />;
}

Les options reprennent les noms du core : class au lieu de className, et style sous forme de chaîne. dangerouslySetInnerHTML nécessite un élément conteneur ; faites en sorte qu'il se comporte comme l'icône elle-même, pour qu'il n'ajoute aucune hauteur de ligne :

app/globals.css
.icon {
  display: inline-flex;
  line-height: 0;
  vertical-align: middle;
}

Les états vivants fonctionnent toujours sans JavaScript, car l'animation est une petite feuille de style intégrée au SVG : renderToString(loader, { animate: 'spin' }) produit une icône qui tourne, et renderToString(menu, { morphTo: x, trigger: 'hover' }) une icône qui se transforme au survol grâce à une transition CSS. Les déclencheurs de clic et d'état nécessitent un Client Component.

Icônes par nom côté serveur

Icon, morphTo et Fusion recherchent les noms dans un registre que remplit chaque composant d'icône importé. Un nom que personne n'a importé est récupéré par un import dynamique après le chargement de la page : le serveur envoie donc un SVG vide à sa place, et l'icône apparaît brusquement après l'hydratation. Enregistrez dans le fichier frontière les données de chaque nom que vous rendez à partir de chaînes ; ce fichier s'exécute sur le serveur pendant le rendu, puis à nouveau dans le navigateur :

components/icons.ts
'use client';

import { registerIcons } from 'glyphfox/react';
import { iconSettings, iconUsers, iconChartBar } from 'glyphfox';

registerIcons([iconSettings, iconUsers, iconChartBar]);

export { Icon, HomeIcon } from 'glyphfox/react';

Les exports de données bénéficient du tree shaking, comme les composants. Attention à une différence entre environnements : les builds de développement peuvent évaluer tous les modules d'icônes, si bien qu'un enregistrement manquant peut passer inaperçu avec next dev et ne produire une icône vide qu'après next build, qui supprime les modules inutilisés.

Pages Router

Le Pages Router n'a pas de Server Components : les composants y fonctionnent tels quels dans les pages, les layouts et _app. Chaque page est rendue sur le serveur puis hydratée, ce qui rend l'enregistrement décrit plus haut tout aussi utile : placez registerIcons dans pages/_app.tsx si vous rendez des icônes par nom.

pages/_app.tsx
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} />;
}

Une icône comme favicon

L'App Router utilise app/icon.svg comme icône du site. La CLI peut y écrire n'importe quelle icône avec une couleur explicite, puisqu'un favicon n'est entouré d'aucun texte dont currentColor pourrait reprendre la couleur :

terminal
npx glyphfox copy fox --color "#ff7a1a" --weight 2.5 --out app/icon.svg

Pour des icônes d'application PNG en plusieurs tailles, le kit de marque du Studio exporte un ensemble de favicons personnalisé avec votre couleur, votre épaisseur et votre style.

Ce que contient la sortie du build

Avec webpack, next build génère de nombreux petits chunks client, environ un par icône de la collection. Ils proviennent du chargeur à la demande qui se trouve derrière Icon et morphTo : son import dynamique pointe vers le dossier des icônes, si bien que le bundler prépare un chunk pour chaque icône susceptible d'être demandée par son nom. Chacun pèse quelques centaines d'octets et n'est téléchargé que lorsqu'une page rend un nom qui n'a pas été importé. Quel que soit le bundler, enregistrer les noms que vous rendez à partir de chaînes (voir plus haut) garantit qu'ils ne sont jamais demandés. Les icônes que vous importez directement se trouvent dans vos chunks habituels. Les mesures figurent dans le guide du tree shaking.

Props

Les composants acceptent les mêmes props que dans n'importe quelle application React. Quand un Server Component les rend à travers la frontière, chaque prop ci-dessous est une valeur simple et peut la franchir ; seuls les callbacks comme onClick et ref doivent être définis dans un Client Component. Les options du core de renderToString et toSvg portent les mêmes noms.

PropTypePar défautDescription
sizenumber, string24Largeur et hauteur ; les nombres sont en pixels.
colorstringcurrentColorCouleur principale.
secondaryColorstringcolorCouleur du calque duotone et du remplissage plein.
secondaryOpacitynumber0.3 / 1Opacité du calque duotone ou du remplissage plein.
weightnumber2Trait de 1 à 3 sur la grille de 24, avec compensation optique en dessous de 24 px.
absoluteWeightbooleanfalseGarde le trait à weight pixels quelle que soit la taille.
cornersround, sharproundJointures et extrémités arrondies ou vives.
variantoutline, duotone, solidoutlineAxe de style ; solid vient des formes fermées.
rotatenumber0Rotation en degrés.
flipXbooleanfalseMiroir horizontal.
flipYbooleanfalseMiroir vertical.
mirrorRtlbooleanfalseMiroir dans les mises en page de droite à gauche.
animatedraw, pop, wiggle, spin, pulse, bounce, shakeaucunPréréglage d'état vivant.
triggermount, hover, click, statemountMoment où l'animation ou le morph se joue.
activebooleanfalseL'état pour le déclencheur state.
durationnumber (ms)selon le préréglageDurée de l'animation ou du morph.
delaynumber (ms)0Délai de l'animation ou du morph.
loopboolean, numberfalseRépète à l'infini ou un certain nombre de fois (spin et pulse bouclent par défaut).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotaucunBadge avec espace de découpe automatique.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCoin du badge.
morphToname, dataaucunCible du morph ; le déclencheur décide quand elle s'affiche.
titlestringaucunNom accessible : title, role img et aria-label.
decorativebooleantrue sans titleMasque l'icône aux technologies d'assistance.

Pièges courants

Erreurs de hooks dans les Server Components
Importer depuis glyphfox/react dans un fichier serveur échoue, car les composants utilisent des hooks. Importez-les via un fichier "use client", ou rendez du SVG statique avec renderToString.
export * dans la frontière
Next.js refuse export * dans une frontière client. Listez les icônes avec des exports nommés.
Des fonctions en props depuis le serveur
onClick, onMouseEnter et les refs ne sont pas sérialisables. Déplacez les icônes interactives dans un Client Component et ne transmettez que des données.
Icônes vides après next build
Un nom passé à Icon qui n'a jamais été importé donne une icône vide côté serveur. Enregistrez ses données avec registerIcons dans le fichier frontière ou dans _app.
Les noms inconnus lèvent une exception
toSvg lève une exception pour un nom qui n'existe pas, ce qui se traduit par une erreur serveur. Vérifiez que les noms fournis par l'utilisateur figurent dans iconNames de glyphfox avant de les rendre.
Espace en trop autour des icônes serveur
Le conteneur de dangerouslySetInnerHTML est un élément en ligne doté de sa propre boîte de ligne. Donnez-lui display: inline-flex et line-height: 0.

Questions fréquentes

Ai-je besoin de transpilePackages pour glyphfox ?

Non. Le paquet fournit du JavaScript standard en ESM et en CommonJS avec ses déclarations de types, si bien que Next.js le résout et l'intègre au bundle comme n'importe quelle dépendance compilée.

Puis-je utiliser le web component glyph-fox dans Next.js ?

Oui, importez glyphfox/web depuis un Client Component. Le module peut être évalué sans risque pendant le rendu côté serveur, mais un élément personnalisé ne se dessine qu'une fois mis à niveau par le navigateur : le HTML du serveur contient donc un élément vide à l'emplacement de l'icône. Dans une application React, les composants sont mieux adaptés, car leur SVG figure dans le HTML du serveur ; le guide des web components présente l'élément.

Icônes de ce guide