SVG en Producción: Rendimiento y Seguridad 2025 — Código Mínimo, Efecto Máximo
Publicado: 23 sept 2025 · Tiempo de lectura: 5 min · Por el equipo editorial de Unified Image Tools
TL;DR
- Normalizar rutas/transformaciones, convertir texto a contorno cuando sea necesario
- Remover metadatos/rastros de editor innecesarios, evitar CLS con viewBox y proporciones
- Restringir referencias de recursos externos/scripts—gestionar con CSP
Enlaces internos: Imágenes accesibles en práctica — Límites entre alt/decorativas/ilustraciones 2025, Diseño de Miniaturas OGP 2025 — Sin Cortes, Ligeras, Efectivas
Introducción
SVG es "gráficos vectoriales descritos en texto," ofreciendo ventajas de ser liviano, escalable sin degradación y fácil aplicación de estilos. Sin embargo, tiene escollos como nodos redundantes de software de edición, riesgos de scripts/referencias externas, y problemas de rendimiento por filtros pesados. Este artículo organiza integralmente optimización, seguridad, entrega y accesibilidad para costo mínimo, efecto máximo en producción.
Optimización de Rendimiento (Dieta de Estructura y Coordenadas)
- Optimización automatizada por defecto con SVGO etc. (forzar en CI)
- Configurar precision (ej., 2-3 dígitos) para redondear coordenadas, habilitar
convertPathData
/convertTransform
/mergePaths
- Remover rastros de editor con
removeMetadata
/removeEditorsNSData
/cleanupIDs
- Configurar precision (ej., 2-3 dígitos) para redondear coordenadas, habilitar
- Usar
viewBox
como línea base, ajustarwidth/height
con CSS por caso de uso (responsive) - Establecer explícitamente
preserveAspectRatio
(ej.,xMidYMid meet
) para prevenir distorsión/CLS - No sobre-fusionar rutas, usar
<symbol>
para elementos reutilizables y entrega sprite - Evitar filtros pesados/blur/gradientes multi-etapa, cambiar a alternativas bitmap o aproximaciones CSS
Ejemplo config SVGO (svgo.config.js
):
module.exports = {
multipass: true,
js2svg: { indent: 0, pretty: false },
plugins: [
{ name: 'preset-default', params: { overrides: { removeViewBox: false } } },
'cleanupIds',
'convertPathData',
'convertTransform',
{ name: 'removeAttrs', params: { attrs: ['data-*', 'id'] } },
],
};
Estrategia de Sprite (<symbol> + <use>)
Para reducir sobrecarga HTTP y costos de re-renderizado con múltiples iconos, consolidar en un archivo en tiempo de build para caché efectivo.
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-search" viewBox="0 0 24 24">...</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">...</symbol>
</svg>
<!-- Uso -->
<svg class="icon" aria-hidden="true"><use href="#icon-search" /></svg>
Nota: Referencias de sprite externas (href="/sprite.svg#icon"
) tienen problemas de compatibilidad CORS/CSP, así que por defecto usar same-origin/inline.
Seguridad y Sanitización (Contención XSS/Referencias Externas)
SVG tiene características dinámicas como scripts/manejadores de eventos/foreignObject
que pueden convertirse en criaderos de XSS cuando se tratan como datos de entrada. Establecer estos principios:
- Rechazar atributos
script
/foreignObject
/on*
(protección dual con build y sanitizador) - Prohibir/restringir referencias externas (
<use href>
,<image href>
,<link>
) a same-origin - Bloquear
javascript:
etc. en URLs de datos - Fijar MIME a
image/svg+xml
para prevenir inyección de contenido arbitrario
Ejemplo DOMPurify (Node/Edge):
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
export function sanitizeSVG(svg) {
return DOMPurify.sanitize(svg, {
USE_PROFILES: { svg: true },
FORBID_TAGS: ['script', 'foreignObject'],
FORBID_ATTR: ['on*', 'style'], // si se evitan estilos inline
ALLOWED_URI_REGEXP: /^(data:image\/(svg\+xml|png|jpeg);|https?:|#)/i,
});
}
Ejemplo CSP (encabezado HTTP o <meta httpEquiv>
):
Content-Security-Policy:
default-src 'self';
img-src 'self' data: https:;
object-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline';
Entrega y Caché (Optimización Capa HTTP)
- Content-Type:
image/svg+xml; charset=utf-8
- Habilitar compresión de texto (Brotli/gzip). Extensión
.svgz
usualmente innecesaria por costos operacionales - Nombres de archivo versionados con
Cache-Control: max-age=31536000, immutable
<svg>
inline es rápido para visualización inicial pero nota reutilización/caché limitado (solo iconos pequeños)- Referencias externas entregadas same-origin para evitar CORS. SRI generalmente innecesario para same-origin
SVG inline Next.js (wrapper accesible):
type Props = { title?: string; desc?: string; focusable?: boolean } & React.SVGProps<SVGSVGElement>;
export function Icon(props: Props) {
const { title, desc, focusable = false, ...rest } = props;
const titleId = title ? 'svg-title' : undefined;
const descId = desc ? 'svg-desc' : undefined;
return (
<svg role="img" aria-labelledby={[titleId, descId].filter(Boolean).join(' ') || undefined} focusable={focusable} {...rest}>
{title && <title id={titleId}>{title}</title>}
{desc && <desc id={descId}>{desc}</desc>}
{/* ...rutas... */}
</svg>
);
}
Accesibilidad (Semántica y Gestión de Foco)
- Gráficos significativos obtienen
<title>
/<desc>
conrole="img"
, decorativos usanaria-hidden="true"
/focusable="false"
- Al convertir texto a contorno, verificar legibilidad y nitidez de contorno al hacer zoom (alternativas de hinting)
- Animaciones cumplen con
prefers-reduced-motion
, respetando preferencias del usuario
Respaldos y Alternativas
- Entornos legacy obtienen alternativas PNG/WebP (generación bitmap automatizada en build)
- Usar
<picture>
para cambio por caso de uso onoscript
fail-safe para decoraciones no críticas
<picture>
<source type="image/svg+xml" srcset="/logo.svg" />
<img src="/logo.png" width="200" height="40" alt="Logo del sitio" />
<noscript><img src="/logo.png" alt="Logo del sitio" /></noscript>
</picture>
Estudios de Caso (Breve)
Caso 1: Renderizado pesado por salida cruda de editor
- Síntoma: Grupos excesivos/precisión de coordenadas, uso de filtros pesados ralentizando renderizado inicial
- Solución: SVGO con precision=3,
convertPathData
/mergePaths
, eliminación de filtros - Resultado: 42% reducción de archivo, mejora TTI, menor uso CPU en re-renderizado
Caso 2: Referencia sprite externa bloqueada por CSP
- Síntoma:
<use href="/sprite.svg#icon">
bloqueado por CSP, iconos faltantes - Solución: Cambiar a sprite inline/entrega same-origin, relajación CSP mínima
- Resultado: Visualización estable, eficiencia de caché mejorada
FAQ
P. ¿Debe convertirse el texto a contorno?
R. Convertir para logos donde la integridad de forma es crítica. No convertir texto de cuerpo (daña legibilidad/traducibilidad).
P. ¿Qué tal incrustar bitmaps con <image>
?
R. Posible pero notar manejo de referencia externa/URL de datos. Gestión de espacio de color/tamaño se vuelve difícil, así que usar con moderación.
P. ¿Cómo manejar expresiones de filtro?
R. Probar alternativas CSS primero. Si absolutamente necesario, considerar usar versiones rasterizadas de menor resolución.
Lista de Verificación (Para Despliegue)
- [ ] Forzar SVGO en CI (precision/convertPathData/cleanupIds)
- [ ]
viewBox
/preserveAspectRatio
explícito, CLS cero - [ ] Eliminar filtros pesados/blur (alternativas raster si es necesario)
- [ ] Prohibir
script
/foreignObject
/on*
, aplicar sanitización + CSP - [ ] Entrega same-origin,
image/svg+xml
+ compresión + caché a largo plazo - [ ] Accesibilidad (title/desc/role/focusable) y respaldos listos