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
  • Usar viewBox como línea base, ajustar width/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> con role="img", decorativos usan aria-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 o noscript 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

Herramientas relacionadas

Artículos relacionados