Frameworks

Iconos en Next.js

glyphfox funciona con los dos routers. En el App Router, los componentes de React necesitan un límite de cliente de un solo archivo, y los Server Components pueden renderizar los mismos iconos como SVG puro, sin enviar nada de JavaScript.

Instalación

Instala el paquete. Incluye ESM y CommonJS ya compilados, con declaraciones de tipos, así que no hay nada que añadir a next.config.js: ni transpilePackages, ni loader, ni hoja de estilos.

terminal
npm install glyphfox

Los componentes de React vienen de glyphfox/react y se explican en detalle en la guía de React. Esta página trata de lo que cambia en Next.js: dónde pueden ejecutarse los componentes, cómo mantener los iconos fuera del bundle del cliente y qué renderiza el servidor.

Por qué el App Router necesita un límite de cliente

En el App Router, todos los módulos son Server Components salvo que el archivo se marque con "use client". Los componentes de glyphfox guardan el estado de hover, clic y morph con useState, useEffect y useRef, y el paquete no incluye la directiva "use client". Por eso, renderizar <HomeIcon /> directamente desde app/page.tsx falla con un error sobre hooks que solo funcionan en Client Components.

La solución no es convertir páginas enteras en Client Components. Marca un archivo pequeño como límite e importa tus iconos a través de él. Los Server Components pueden renderizar Client Components: los iconos se siguen renderizando a HTML en el servidor y después se hidratan en el navegador.

Un único archivo de cliente para tus iconos

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>
  );
}

Reexporta los nombres uno por uno. Next.js no admite export * en un límite de cliente, y las exportaciones con nombre mantienen intacto el tree shaking: solo los iconos que aparecen en el archivo, y solo los que una página usa de verdad, terminan en los chunks del cliente.

Dentro de un archivo que ya empieza con "use client" puedes importar directamente desde glyphfox/react. El archivo de límite solo existe para los iconos que renderizas desde archivos de servidor.

Iconos interactivos en Client Components

Los manejadores de eventos y las refs no pueden pasar de un Server Component a un Client Component, porque las props que cruzan ese límite deben ser serializables. Todo lo que reacciona al usuario, como un interruptor con morph, va en su propio 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>
  );
}

El servidor renderiza el icono inicial, y el morph se reproduce en el navegador una vez que el componente se ha hidratado. Mantén el primer estado idéntico en ambos lados: si lees localStorage o la hora durante el renderizado, el servidor y el cliente no coinciden y React señala un desajuste de hidratación.

Iconos sin JavaScript en Server Components

La mayoría de los iconos no cambian nunca una vez cargada la página. Para esos, un Server Component puede convertir los datos de un icono en una cadena SVG con renderToString de glyphfox/core. El código solo se ejecuta en el servidor, así que los iconos llegan como marcado y no añaden nada al bundle del cliente.

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>
  );
}

Cada importación de glyphfox/icons/NAME es un pequeño módulo de datos. Si quieres usar nombres y la sintaxis completa de nombres (file+check, cloud&lock:side, heart/solid), usa en su lugar toSvg del punto de entrada principal. Trae todo el registro de iconos, pero en un Server Component eso solo pesa en el servidor:

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

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

Las opciones usan los nombres del core: class en lugar de className, y style como cadena. dangerouslySetInnerHTML necesita un elemento contenedor; haz que se comporte como el propio icono para que no añada altura de línea:

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

Los estados vivos siguen funcionando sin JavaScript, porque la animación es una pequeña hoja de estilos dentro del SVG: renderToString(loader, { animate: 'spin' }) gira, y renderToString(menu, { morphTo: x, trigger: 'hover' }) se transforma al pasar el cursor con una transición CSS. Los disparadores de clic y de estado necesitan un Client Component.

Iconos por nombre en el servidor

Icon, morphTo y Fusion buscan los nombres en un registro que va llenando cada componente de icono importado. Un nombre que nadie importó se obtiene con una importación dinámica después de cargar la página, lo que significa que el servidor envía un SVG vacío para ese nombre y el icono aparece de golpe tras la hidratación. Registra en el archivo de límite los datos de cada nombre que renderizas a partir de cadenas; ese archivo se ejecuta en el servidor durante el renderizado y de nuevo en el navegador:

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';

Las exportaciones de datos pasan por el tree shaking igual que los componentes. Cuidado con una diferencia entre entornos: las compilaciones de desarrollo pueden evaluar todos los módulos de iconos, así que, si falta un registro, todo puede parecer correcto con next dev y el icono renderizarse vacío solo después de next build, donde se descartan los módulos sin usar.

Pages Router

El Pages Router no tiene Server Components, así que los componentes funcionan tal cual en páginas, layouts y _app. Cada página se renderiza en el servidor y se hidrata, por lo que el registro anterior es igual de útil: pon registerIcons en pages/_app.tsx si renderizas iconos por nombre.

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} />;
}

Un icono como favicon

El App Router toma app/icon.svg como icono del sitio. La CLI escribe ahí cualquier icono con un color explícito, ya que un favicon no tiene un color de texto alrededor que currentColor pueda seguir:

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

Para iconos de aplicación en PNG de varios tamaños, el kit de marca del Estudio exporta un conjunto de favicons con tu color, peso y estilo.

Qué contiene el resultado de la compilación

Con webpack, next build genera muchos chunks de cliente diminutos, más o menos uno por cada icono del conjunto. Vienen del cargador bajo demanda que hay detrás de Icon y morphTo: su importación dinámica apunta a la carpeta de iconos, así que el bundler prepara un chunk para cada icono que podría pedirse por nombre. Pesan unos cientos de bytes cada uno y solo se descargan cuando una página renderiza un nombre que no se importó. Sea cual sea el bundler, si registras los nombres que renderizas a partir de cadenas (ver arriba), nunca se piden. Los iconos que importas directamente van en tus chunks normales. Las mediciones están en la guía de tree shaking.

Props

Los componentes aceptan las mismas props que en cualquier aplicación de React. Cuando un Server Component los renderiza a través del límite, todas las props que aparecen a continuación son valores simples y pueden cruzarlo; solo los callbacks como onClick y ref deben asignarse dentro de un Client Component. Las opciones del core de renderToString y toSvg usan los mismos nombres.

PropTipoPor defectoDescripción
sizenumber, string24Ancho y alto; los números son píxeles.
colorstringcurrentColorColor principal.
secondaryColorstringcolorColor de la capa duotone y del relleno sólido.
secondaryOpacitynumber0.3 / 1Opacidad de la capa duotone o del relleno sólido.
weightnumber2Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px.
absoluteWeightbooleanfalseMantiene el trazo en weight píxeles a cualquier tamaño.
cornersround, sharproundUniones y extremos redondeados o rectos.
variantoutline, duotone, solidoutlineEje de estilo; solid se obtiene de las formas cerradas.
rotatenumber0Rotación en grados.
flipXbooleanfalseRefleja en horizontal.
flipYbooleanfalseRefleja en vertical.
mirrorRtlbooleanfalseRefleja en layouts de derecha a izquierda.
animatedraw, pop, wiggle, spin, pulse, bounce, shakeningunoPreset de estado vivo.
triggermount, hover, click, statemountCuándo se reproduce la animación o el morph.
activebooleanfalseEl estado para el disparador state.
durationnumber (ms)según el presetDuración de la animación o del morph.
delaynumber (ms)0Retraso de la animación o del morph.
loopboolean, numberfalseRepite sin fin o un número de veces (spin y pulse se repiten por defecto).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotningunoInsignia con hueco de recorte automático.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightEsquina de la insignia.
morphToname, dataningunoDestino del morph; el disparador decide cuándo se muestra.
titlestringningunoNombre accesible: title, role img y aria-label.
decorativebooleantrue sin titleOculta el icono a las tecnologías de apoyo.

Errores comunes

Errores de hooks en Server Components
Importar desde glyphfox/react en un archivo de servidor falla porque los componentes usan hooks. Importa a través de un archivo con "use client" o renderiza SVG estático con renderToString.
export * en el límite
Next.js rechaza export * en un límite de cliente. Enumera los iconos mediante exportaciones con nombre.
Funciones como props desde el servidor
onClick, onMouseEnter y las refs no son serializables. Mueve los iconos interactivos a un Client Component y pásale solo datos.
Iconos vacíos después de next build
Un nombre de Icon que nunca se importó se renderiza vacío en el servidor. Registra sus datos con registerIcons en el archivo de límite o en _app.
Los nombres desconocidos lanzan un error
toSvg lanza un error con un nombre que no existe, y eso se convierte en un error del servidor. Comprueba que los nombres que aporta el usuario estén en iconNames de glyphfox antes de renderizarlos.
Espacio extra alrededor de los iconos renderizados en el servidor
El contenedor de dangerouslySetInnerHTML es un elemento en línea con su propia caja de línea. Dale display: inline-flex y line-height: 0.

Preguntas frecuentes

¿Necesito transpilePackages para glyphfox?

No. El paquete incluye JavaScript puro en ESM y CommonJS con declaraciones de tipos, así que Next.js lo resuelve y lo empaqueta como cualquier dependencia compilada.

¿Puedo usar el web component glyph-fox en Next.js?

Sí, importa glyphfox/web desde un Client Component. El módulo se puede evaluar sin problemas durante el renderizado en el servidor, pero un custom element solo se dibuja cuando el navegador lo actualiza, así que el HTML del servidor tiene un elemento vacío donde irá el icono. En una aplicación de React, los componentes encajan mejor porque su SVG está en el HTML del servidor; la guía de web components explica el elemento.

Iconos de esta guía