Shipping Notes29 de agosto de 2026

Monta el marco, no la foto.

Un icono necesita un marco para sujetarlo y una imagen que mostrar en él. Los paquetes `@web-portfolio/icons` y `@web-portfolio/icons-sanity` existen para que el desarrollador solo tenga que colocar el marco; la imagen puede provenir de donde corresponda, según la decisión que se tome al respecto.

Monta el marco, no la foto.

Esto comenzó como una pregunta, no como un plan: ¿debería un icono ser contenido o código? La lista de habilidades de un portafolio cambia con más frecuencia que sus componentes, y cada nueva habilidad implica un nuevo icono; por lo tanto, tratar los iconos como algo que Sanity podría almacenar y editar sin necesidad de volver a desplegar parecía la opción lógica. Sin embargo, esta idea no resultó ser tan sencilla.

A dónde realmente llevó la idea de "ícono como contenido".

La primera versión almacenaba un icono como código SVG sin formato en un campo de texto plano, porque esa era la forma más lógica para representar un "icono como contenido": el usuario pega lo que tiene y la interfaz de usuario lo renderiza. Sin embargo, ahí es donde dejó de ser obvio.

Cada icono insertado provenía de una fuente diferente: uno con un trazado limpio de dos colores, otro con un relleno predefinido y un elemento <g> anidado lleno de estilos internos que ningún editor podía ver ni eliminar. Algunos tenían un viewBox de 24x24, otros de 512x512, y algunos no tenían ninguno en absoluto. No había una forma estándar para construir un componente a partir de ellos.

Así que el componente intentó imponer un valor específico: un tamaño, un color, un grosor de línea, y esa fue la parte que empeoró la uniformidad, en lugar de mejorarla. `currentColor` solo funciona si el código fuente original no define ya su propio relleno, y una modificación del ancho de línea tampoco se aplica una vez que un icono establece el suyo propio. Cada nuevo icono pegado necesitaba su propia configuración, lo cual contradice el propósito de un campo de contenido que supuestamente no debería requerir ningún cambio en el código.

Un campo de contenido que requiere un cambio de código cada vez que alguien lo utiliza realmente no es un campo de contenido.

Debajo de todo eso estaba la parte realmente peligrosa: ese campo se renderizaba con `dangerouslySetInnerHTML`, inyectando directamente lo que se pegaba en el DOM, lo cual representaba un riesgo de seguridad real, no solo un problema de formato. Las soluciones propuestas simplemente añadían código sin abordar el problema fundamental: el valor del campo seguía siendo exactamente lo que se había pegado en él.

Las correcciones que solo implican cambios en el código tampoco lo solucionan.

Volver a la forma en que todos los demás manejan los iconos en el código no resuelve el problema del contenido. Las fuentes de iconos combinan el marco y la imagen en un nombre de clase: `<i class="fa fa-docker">`, y una errata simplemente muestra un espacio en blanco. Las hojas de sprites funcionan de la misma manera, pero a través de un ID. Los componentes SVG integrados son los más difíciles de manejar: la imagen se incrusta en el grafo de importación, por lo que no hay ningún registro ni campo. Las bibliotecas de terceros como lucide-react mejoran la experiencia del desarrollador, pero importar { Docker } sigue siendo un cambio en el código, aunque sea uno más agradable.

Cada una es una forma perfectamente válida de escribir un icono en código. Ninguna de ellas permite que la imagen mostrada cambie sin modificar el código, lo cual era precisamente el objetivo.

Colócalo: un marco que solo necesita una foto.

@web-portfolio/icons soluciona eso. `<Icon>` es un marco; su atributo "name" toma una imagen de un registro que los paquetes core crean al combinar devicon, Material Symbols y Simple Icons mediante SVGO. `<Icon>` no puede distinguir entre un valor literal y una variable, por lo que la misma llamada funciona en ambos casos:

packages/react — usagetsx
import { Icon } from '@web-portfolio/icons'

<Icon name="docker" size={32} />          // a literal picture
<Icon name={skill.icon} size={32} />       // a picture from a CMS field

Eso es lo que necesitaba el componente inicial @web-portfolio/icons-sanity: una imagen es simplemente una cadena de texto, por lo que puede ser seleccionada por un editor en lugar de tener que ser escrita por un desarrollador. Su esquema de tipo `iconRef` convierte un campo en una cuadrícula de búsqueda dentro de Sanity Studio; `IconPickerInput` solo escribe la información de una imagen que encuentra en el mismo registro del que lee `<Icon>`. El trabajo de un desarrollador nunca ha sido elegir la imagen, sino simplemente configurar el marco.

También vale la pena considerar el costo: dado que ambos paquetes incluyen el mismo registro, lo único que se transmite desde Sanity al frontend es esa cadena de texto, que ocupa aproximadamente 7 bytes en promedio, frente a los ~955 bytes del código real de un icono típico (hasta varios KB para un logotipo complejo). Almacenar SVG sin procesar en un campo del CMS, en lugar de usar este plugin, cuesta entre 130 y 285 veces más por cada icono, en cada solicitud. Las fuentes de iconos y las hojas de sprites evitan ese costo al hacer referencia a un nombre; los componentes SVG integrados y las bibliotecas basadas en importación no pueden hacerlo, a menos que primero construyan el registro exacto que este paquete ya proporciona.

Esa mismo cambio soluciona un problema común: enviar una imagen antes de que el departamento de diseño la apruebe. Coloque la imagen más cercana hoy mismo —nombre="link"— y cambie solo ese valor cuando se envíe la imagen definitiva, sin necesidad de volver a importarla ni crear un nuevo componente. En su lugar, diríjalo a través de un campo del CMS, y quien sea responsable en ese momento (diseño, marketing, o el equipo que esté gestionando la campaña de esta semana) podrá cambiar la imagen directamente en Studio, sin necesidad de realizar ninguna publicación.

El elemento de marcador de posición se incluye en el mismo "commit" que la funcionalidad. Solo la imagen se modifica posteriormente.

Donde realmente destaca.

Evaluado según los mismos cuatro enfoques, y dividido en tres preguntas: ¿cómo es escribirlo?, ¿cuánto cuesta ejecutarlo?, y qué ocurre cuando el icono necesita ser cambiado y la persona que lo cambia no es un desarrollador.

Writing it

icons + icons-sanity
@web-portfolio/icons
Third-party library
Inline SVG
SVG sprite
Icon font
Adding an icon in code
La misma llamada; la cadena puede provenir de un campo.
<Icon nombre="docker"/> — un atributo.
import { Docker} — el editor ofrece sugerencias automáticas.
Un archivo de componente nuevo para cada icono, copiado y pegado.
#docker: el mismo riesgo de errores al escribir sin mirar.
fa-docker: no se verifica si el glifo existe.
Type safety / autocomplete
Picker solo escribe claves reales; esto es una garantía de Studio, no un tipo específico.
El nombre es una cadena de texto simple; solo un error tipográfico generará una advertencia.
Exportaciones con nombre — un error tipográfico impide la compilación.
Cada icono es una importación con su propio nombre.
Una cadena de identificación: el mismo problema.
Una cadena que representa el nombre de la clase; nada verifica si existe.
Styling — color, stroke, size
Mismas propiedades, mismo componente.
propiedades de tamaño/color/grosor para el componente <Icon>.
Propiedades de tamaño, color y grosor de línea especificadas.
Solo tan preciso como la persona que lo haya copiado.
El atributo "fill: currentColor" funciona de manera eficiente.
El color se hereda, pero el grosor está integrado en la forma del glifo.

Running it

icons + icons-sanity
@web-portfolio/icons
Third-party library
Inline SVG
SVG sprite
Icon font
Bundle impact
Mismo costo que los iconos por separado.
Un registro que incluye los 633 iconos.
Árbol sacudido por icono.
Solo se envían los iconos importados.
Incluye todos los iconos en una sola hoja.
El archivo de fuente completo se carga de ambas maneras.
Runtime selection
Igual, a través del selector.
No se requiere cableado.
Igual — crea el mapa tú mismo.
Necesita un mapa de nombres manual.
Ya se identificó mediante ID.
Ya con clave de cadena.

Living with it

icons + icons-sanity
@web-portfolio/icons
Third-party library
Inline SVG
SVG sprite
Icon font
Selecting from dynamic data
Contenido de Lives in Sanity, con actualizaciones sin necesidad de volver a desplegar.
El nombre puede venir de cualquier parte; nada te ayuda a elegir el correcto.
Igual que antes: cada icono es un componente, no una clave de búsqueda.
El icono es una importación específica, no un valor intercambiable.
Igual — una cadena de identificación sin selector.
Una cadena de texto: nada genera un selector para ella.
Non-developer control
IconPickerInput incluye el selector ya integrado.
Sin superficie de edición; aún un desarrollador está modificando una propiedad.
Igual, es una importación a nivel de código.
Cambiar el icono implica modificar el código.
Sigue siendo así: alguien todavía tiene que crear esa función.
Se necesita un selector construido manualmente que conozca los nombres de los glifos.
Adding a brand-new icon
La misma restricción: la posibilidad de pegar código SVG directamente por parte de un administrador es una vía de escape.
Inicializa el origen, ejecuta "generate-registry" y vuelve a publicar.
Recibirás lo que la biblioteca proporciona, nada más.
Pega el código SVG, listo; no hay archivo compartido que modificar.
Edita el archivo de sprites y vuelve a implementarlo.
Regenerar el archivo de fuente, reconstruir y volver a implementar.
Consistency across sources
Mismo registro: Studio también marca un valor que ya no es válido.
Múltiples conjuntos de iconos se han unificado en un único registro.
Es consistente internamente, pero los logotipos de la marca generalmente no se incluyen.
No hay disciplina compartida a menos que alguien la haga cumplir.
Solo tan bueno como los materiales que se utilizaron para elaborarlo.
Una familia, un lenguaje visual, construido a partir de elementos comunes.
License & provenance
Misma atribución, heredada sin cambios.
Se debe indicar la fuente una sola vez, tanto en el registro como en el archivo README.
Una licencia para todo el paquete, generalmente clara.
No se realiza seguimiento; la atribución permanece con quien lo pegó, si es que alguien lo hizo.
Generalmente, una vez que los iconos se copian en un archivo, el proceso deja de ser rastreable.
Independientemente de lo que diga la licencia del paquete de fuentes, rara vez se aplica por cada glifo.

Bundle impact, @web-portfolio/icons row: the ✕ is a client-side cost only. Render <Icon> in a server component and the registry never reaches the browser at all.

El compromiso, sin adornos.

Las desventajas mencionadas anteriormente son reales: el tamaño del paquete y la seguridad de tipos. El tamaño del paquete es un problema porque registry.generated.ts contiene todos los iconos en un solo objeto, en lugar de fragmentos separados que un empaquetador puede optimizar; así, <Icon> carga los 633 iconos incluso si una página utiliza solo uno. Una biblioteca basada en importaciones evita este costo, pero solo sacrificando la selección en tiempo de ejecución, lo que anula el propósito de almacenar los iconos como contenido. La solución, según la nota a pie de página anterior: renderizar <Icon> en el servidor, y así el registro nunca llega al navegador; la diferencia de tamaño desaparece. Esta también es la configuración predeterminada ideal para los iconos en general, especialmente ahora que pueden ser contenido en lugar de código: el valor solo existe en el momento de la solicitud, por lo que nunca hubo una razón para enviarlo al navegador. Renderizado de esta manera, <Icon> se comporta igual que un SVG incrustado y una importación optimizada, sin JavaScript adicional, y supera a las fuentes web (que aún necesitan su propio archivo) y a las hojas de sprites (que empaquetan todo o requieren su propia solicitud).

La seguridad de tipos funciona de manera similar. Si se escribe un nombre manualmente, es simplemente texto; nada detecta errores tipográficos. El selector de icons-sanity solo permite que un editor elija un nombre que realmente exista, pero esa garantía reside en la interfaz de Studio, no en el esquema subyacente; aún así, un error tipográfico puede filtrarse desde fuera de Studio. Se planea implementar esta verificación a nivel del esquema en la próxima versión menor.

Cuándo deberías considerar usar esto.

Esto es adecuado para un proyecto respaldado por un CMS, como un portafolio, un sitio web de una agencia o una página de marketing, donde "qué icono usar" es una decisión de contenido, no una cuestión de ingeniería. Si nada se modifica fuera del código fuente, simplemente use la biblioteca optimizada: la imagen siempre iba a permanecer igual, así que no había ningún marco que valiera la pena implementar desde el principio.

Si configuras el marco correctamente una vez, cambiar la imagen deja de ser una remodelación; simplemente retiras una y colocas otra. El marco permanece en la pared: no hay que hacer nuevos agujeros ni existe el riesgo de que se desalinee. Esa es la verdadera ventaja de crear el registro y el selector: `<Icon name>`, el registro, e `IconPickerInput` no cambian más. Solo cambia el nombre que se les pasa.

Monta el marco, entrega la foto y vuelve a preparar los envíos.

Author

Jatin Kumar

Frontend Engineer

Icon gallery@web-portfolio/icons@web-portfolio/icons-sanitygithub.com/jatinrao/iconsSanity Plugin