Renderizado en el servidor
Los iconos de glyphfox son SVG puro, así que se renderizan en el servidor como cualquier otro marcado. Los detalles que importan son de dónde salen los datos de los iconos, qué ocurre durante la hidratación y qué formas de renderizado necesitan JavaScript en el navegador.
Qué renderiza cada paquete en el servidor
Todos los paquetes usan el mismo renderizador, así que un icono se ve idéntico tanto si lo renderiza React en un servidor como Vue en el navegador o toSvg en un script de compilación. Lo que cambia es si el servidor es capaz de generar el marcado.
| Forma de renderizado | Salida en el servidor | JavaScript en el navegador |
|---|---|---|
| Componentes de React, Vue, Svelte y Angular | marcado svg completo | solo para morphs y para disparadores de clic y de estado |
toSvg y renderToString | una cadena svg completa | ninguno |
El elemento glyph-fox | un elemento vacío | necesario: el icono aparece cuando el elemento se inicializa |
El runtime de data-glyph | un <i> vacío | necesario: el runtime lo reemplaza |
| Clases de Tailwind CSS | un elemento vacío con los estilos de tu CSS | ninguno |
| Sprite SVG | un svg con una referencia use | ninguno |
Los detalles de cada framework están en sus propias guías: el límite de cliente del App Router de Next.js, los plugins en Nuxt, los layouts en SvelteKit, los iconos sin JavaScript en Astro y Qwik, y SolidStart. Esta página trata lo que tienen en común.
Datos de los iconos en el servidor
Un componente como HomeIcon lleva sus propios datos y los registra cuando se ejecuta su módulo, así que siempre se renderiza en el servidor. En cambio, todo lo que hace referencia a un icono por su nombre busca ese nombre en un registro: Icon name, morphTo, las partes de Fusion, MorphIcon, useMorph, morphStore y el input name del componente de Angular.
Un nombre que no está registrado se carga bajo demanda con una importación dinámica. Esa carga solo empieza en el navegador, después del montaje, así que el servidor renderiza un marcador de posición del tamaño correcto y nada más:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="glyphfox glyphfox-empty" aria-hidden="true"></svg>
Registra los nombres que usas en un módulo que importen tanto el punto de entrada del servidor como el del cliente. Las exportaciones de datos, como iconSettings, son pequeñas y admiten tree shaking igual que los componentes.
import { registerIcons } from 'glyphfox/react'; // or glyphfox/vue, glyphfox/svelte import { iconSettings, iconFile, iconCloud, iconLock, iconX } from 'glyphfox'; // Names used through Icon, morphTo, Fusion and MorphIcon. registerIcons([iconSettings, iconFile, iconCloud, iconLock, iconX]);
En Angular, la misma lista se pasa a provideGlyphFox({ icons }), que se ejecuta en el servidor y en el navegador; consulta la guía de Angular.
Cuando el servidor sabe más que el cliente
El problema contrario es igual de real. Cuando el servidor ejecuta el paquete sin pasarlo por un bundler, como hace Node con las dependencias que se mantienen externas, importar cualquier cosa de glyphfox/react o glyphfox/vue evalúa todo el punto de entrada, y cada uno de los 419 módulos de iconos se registra. Entonces <Icon name="settings" /> se renderiza por completo en el servidor.
En el bundle del navegador se aplica tree shaking, así que ahí settings no está registrado. El primer renderizado en el cliente produce el marcador de posición vacío, el marcado deja de coincidir con el HTML del servidor y el framework informa de una discrepancia de hidratación antes de que el icono se cargue de nuevo. Registrar los nombres de forma explícita, como se indica arriba, hace que ambos lados coincidan.
Cadenas SVG con toSvg
Fuera de un framework de componentes, toSvg(spec, props) devuelve el marcado como cadena. Acepta la sintaxis de nombres completa y todas las props compartidas, no depende del navegador y no realiza operaciones de E/S, así que funciona en plantillas de servidor, generadores de sitios estáticos, funciones serverless y scripts de compilación.
import express from 'express'; import { toSvg } from 'glyphfox'; const app = express(); app.get('/', (req, res) => { res.send(`<!doctype html> <title>Files</title> <ul> <li>${toSvg('file+check', { size: 18 })} report.pdf</li> <li>${toSvg('cloud&lock:overlap', { size: 18, title: 'Encrypted backup' })} backup.zip</li> </ul>`); }); app.listen(3000);
El renderizado es rápido, pero los modificadores, las fusiones y el estilo sólido hacen un trabajo geométrico real. Para los iconos que se repiten en cada petición, guarda las cadenas en caché; la salida solo depende de la especificación y de las props.
import { toSvg } from 'glyphfox'; const cache = new Map(); export function icon(spec, props = {}) { const key = spec + JSON.stringify(props); let svg = cache.get(key); if (svg === undefined) { svg = toSvg(spec, props); cache.set(key, svg); } return svg; }
toSvg del punto de entrada principal carga todos los iconos, lo cual no es problema en un proceso de servidor. Donde importa el tamaño del código, como en las funciones edge con límite de tamaño, usa renderToString de glyphfox/core con los datos de cada icono desde glyphfox/icons/NAME; la guía de tree shaking tiene las cifras.
import { renderToString } from 'glyphfox/core'; import download from 'glyphfox/icons/download'; export const downloadIcon = renderToString(download, { size: 18 });
Hidratación, morphs y estados vivos
El servidor renderiza el estado que tiene el icono en el momento del renderizado. Los morphs se calculan en el navegador: MorphIcon, morphTo y los hooks empiezan a animar después de la hidratación, porque los efectos no se ejecutan en el servidor. Un icono con morphTo, trigger="state" y active ya en true renderiza su destino en el servidor, así que un menú que empieza abierto muestra la x en el HTML.
Los estados vivos no necesitan hidratación en absoluto. Su CSS está dentro del SVG, así que una animación mount se reproduce en cuanto el navegador analiza el HTML y una animación hover funciona antes de que se cargue ningún script. Una animación state que ya está activa se renderiza con su clase gf-on y también se reproduce desde el HTML. Los clics y los cambios de estado posteriores necesitan el framework, porque es el que alterna el atributo y la clase que ponen en marcha la animación. La regla prefers-reduced-motion viaja en la misma hoja de estilos y también se aplica al marcado del servidor.
El marcado es determinista. Las clases de animación, los ids de máscara y las clases de morph son hashes de su contenido, no contadores ni valores aleatorios, así que el servidor y el navegador producen los mismos atributos y los ids nunca provocan una discrepancia de hidratación.
El web component en páginas renderizadas en el servidor
El elemento glyph-fox se dibuja en un shadow root cuando se ejecuta su script. No usa shadow DOM declarativo, así que el HTML del servidor contiene un elemento vacío y sin tamaño, y la línea que lo rodea se desplaza cuando aparece el icono. Reserva el espacio hasta que el elemento esté definido:
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; vertical-align: middle; }
Para los iconos que se ven sin hacer scroll, renderiza SVG en línea en el servidor con un componente o con toSvg, y reserva el elemento para las partes de la página que cambian en el cliente. La guía de web components explica el elemento en detalle.
Correos electrónicos
El renderizado en el servidor a menudo acaba en una plantilla de correo electrónico, y ese es el único lugar en el que las cadenas SVG no sirven: muchos clientes de correo no muestran SVG en línea ni imágenes SVG. Usa archivos PNG en ese caso. El explorador de iconos permite descargar cualquier icono como PNG al tamaño que necesites, con tu color y tu peso.
Opciones de toSvg
toSvg y renderToString aceptan las mismas props que los componentes, como opciones normales.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
size | number, string | 24 | Ancho y alto; los números son píxeles. |
color | string | currentColor | Color principal. |
secondaryColor | string | color | Color de la capa duotone y del relleno sólido. |
secondaryOpacity | number | 0.3 / 1 | Opacidad de la capa duotone o del relleno sólido. |
weight | number | 2 | Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px. |
absoluteWeight | boolean | false | Mantiene el trazo en weight píxeles a cualquier tamaño. |
corners | round, sharp | round | Uniones y extremos redondeados o rectos. |
variant | outline, duotone, solid | outline | Eje de estilo; solid se obtiene de las formas cerradas. |
rotate | number | 0 | Rotación en grados. |
flipX | boolean | false | Refleja en horizontal. |
flipY | boolean | false | Refleja en vertical. |
mirrorRtl | boolean | false | Refleja en layouts de derecha a izquierda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | ninguno | Preset de estado vivo. |
trigger | mount, hover, click, state | mount | Cuándo se reproduce la animación o el morph. |
active | boolean | false | El estado para el disparador state. |
duration | number (ms) | según el preset | Duración de la animación o del morph. |
delay | number (ms) | 0 | Retraso de la animación o del morph. |
loop | boolean, number | false | Repite sin fin o un número de veces (spin y pulse se repiten por defecto). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | ninguno | Insignia con hueco de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Esquina de la insignia. |
morphTo | name, data | ninguno | Destino del morph; el disparador decide cuándo se muestra. |
title | string | ninguno | Nombre accesible: title, role img y aria-label. |
decorative | boolean | true sin title | Oculta el icono a las tecnologías de apoyo. |
Errores comunes
- Iconos vacíos en el HTML del servidor
- Un icono renderizado por nombre que nunca se registró es, en el servidor, un
svgvacío con la claseglyphfox-empty. Registra los nombres o importa el componente de ese icono. - Avisos de discrepancia de hidratación
- Si el servidor renderiza un nombre que el navegador todavía no tiene, el marcado difiere en el primer renderizado en el cliente. Las causas habituales son importar en el servidor un punto de entrada completo sin bundler y los nombres que vienen de datos. Registra los nombres en código compartido.
- Iconos que dependen del navegador
- Un icono elegido a partir de
localStorage, del esquema de color o del tamaño de la ventana no puede coincidir en el servidor. Renderiza un icono neutro en el servidor y cámbialo después del montaje, o lee el valor de una cookie que el servidor pueda ver. - Desplazamientos del diseño causados por el elemento
- Un
glyph-foxsin definir no tiene tamaño. Reserva el espacio con:not(:defined)o renderiza SVG en línea en el servidor. - Destinos de morph en builds de producción
- En un build de producción de Vite, los nombres que no se importan ni se registran se quedan vacíos en el navegador, porque el cargador bajo demanda que hay dentro de
node_modulesno se transforma. El renderizado en el servidor no lo oculta; registrar los nombres lo soluciona en ambos lados.
Preguntas frecuentes
¿Funcionan los componentes de React en React Server Components?
No directamente: usan hooks, así que necesitan un límite de cliente. En un Server Component puedes renderizar la salida de toSvg o renderToString con dangerouslySetInnerHTML sin enviar JavaScript para el icono. La guía de Next.js muestra ambas opciones.
¿Por qué mi icono está vacío en el HTML del servidor?
Porque se renderiza por nombre, a través de Icon, morphTo, Fusion o MorphIcon, y sus datos no estaban registrados cuando el servidor lo renderizó. Llama a registerIcons con las exportaciones de datos en un módulo que importen ambos puntos de entrada.