SVG em Produção: Performance e Segurança 2025 — Código Mínimo, Efeito Máximo

Publicado: 23 de set. de 2025 · Tempo de leitura: 5 min · Pela equipe editorial da Unified Image Tools

TL;DR

  • Normalizar caminhos/transformações, converter texto em contorno quando necessário
  • Remover metadados/rastros de editor desnecessários, evitar CLS com viewBox e proporções
  • Restringir referências de recursos externos/scripts—gerenciar com CSP

Links internos: Imagens Acessíveis na Prática — Limites entre Alt/Decorativas/Ilustrações 2025, Design de Thumbnails OGP 2025 — Sem Cortes, Leves, Comunicativas

Introdução

SVG é "gráficos vetoriais descritos em texto," oferecendo vantagens de leveza, escalonamento sem degradação e aplicação fácil de estilos. No entanto, tem armadilhas como nós redundantes de software de edição, riscos de scripts/referências externas, e problemas de performance de filtros pesados. Este artigo organiza de forma abrangente otimização, segurança, entrega e acessibilidade para custo mínimo, efeito máximo em produção.

Otimização de Performance (Dieta de Estrutura e Coordenadas)

  • Otimização automatizada padrão com SVGO etc. (forçar no CI)
    • Configurar precision (ex., 2-3 dígitos) para arredondar coordenadas, habilitar convertPathData/convertTransform/mergePaths
    • Remover rastros de editor com removeMetadata/removeEditorsNSData/cleanupIDs
  • Usar viewBox como baseline, ajustar width/height com CSS por caso de uso (responsivo)
  • Definir explicitamente preserveAspectRatio (ex., xMidYMid meet) para prevenir distorção/CLS
  • Não fazer over-merge de caminhos, usar <symbol> para elementos reutilizáveis e entrega sprite
  • Evitar filtros pesados/blur/gradientes multi-estágio, mudar para alternativas bitmap ou aproximações CSS

Exemplo configuração 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'] } },
  ],
};

Estratégia Sprite (<symbol> + <use>)

Para reduzir sobrecarga HTTP e custos de re-renderização com múltiplos ícones, consolidar em um arquivo no build time para cache efetivo.

<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: Referências sprite externas (href="/sprite.svg#icon") têm problemas de compatibilidade CORS/CSP, então por padrão usar same-origin/inline.

Segurança e Sanitização (Contenção XSS/Referências Externas)

SVG tem recursos dinâmicos como scripts/manipuladores de eventos/foreignObject que podem se tornar criadouros de XSS quando tratados como dados de entrada. Estabelecer estes princípios:

  • Rejeitar atributos script/foreignObject/on* (proteção dupla com build e sanitizador)
  • Proibir/restringir referências externas (<use href>, <image href>, <link>) para same-origin
  • Bloquear javascript: etc. em URLs de dados
  • Fixar MIME em image/svg+xml para prevenir injeção de conteúdo arbitrário

Exemplo 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'], // se evitando estilos inline
    ALLOWED_URI_REGEXP: /^(data:image\/(svg\+xml|png|jpeg);|https?:|#)/i,
  });
}

Exemplo CSP (cabeçalho HTTP ou <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 e Cache (Otimização Camada HTTP)

  • Content-Type: image/svg+xml; charset=utf-8
  • Habilitar compressão de texto (Brotli/gzip). Extensão .svgz geralmente desnecessária por custos operacionais
  • Nomes de arquivo versionados com Cache-Control: max-age=31536000, immutable
  • <svg> inline é rápido para exibição inicial mas note reutilização/cache limitado (apenas ícones pequenos)
  • Referências externas entregues same-origin para evitar CORS. SRI geralmente desnecessário para same-origin

SVG inline Next.js (wrapper acessível):

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>}
      {/* ...caminhos... */}
    </svg>
  );
}

Acessibilidade (Semântica e Gerenciamento de Foco)

  • Gráficos significativos recebem <title>/<desc> com role="img", decorativos usam aria-hidden="true"/focusable="false"
  • Ao converter texto em contorno, verificar legibilidade e nitidez do contorno no zoom (alternativas de hinting)
  • Animações cumprem prefers-reduced-motion, respeitando preferências do usuário

Fallbacks e Alternativas

  • Ambientes legacy recebem alternativas PNG/WebP (geração bitmap automatizada no build)
  • Usar <picture> para troca por caso de uso ou noscript fail-safe para decorações não-críticas
<picture>
  <source type="image/svg+xml" srcset="/logo.svg" />
  <img src="/logo.png" width="200" height="40" alt="Logo do site" />
  <noscript><img src="/logo.png" alt="Logo do site" /></noscript>
</picture>

Estudos de Caso (Breve)

Caso 1: Renderização pesada por saída bruta de editor

  • Sintoma: Grupos excessivos/precisão de coordenadas, uso de filtros pesados desacelerando renderização inicial
  • Solução: SVGO com precision=3, convertPathData/mergePaths, eliminação de filtros
  • Resultado: 42% redução de arquivo, melhoria TTI, menor uso de CPU no re-renderização

Caso 2: Referência sprite externa bloqueada por CSP

  • Sintoma: <use href="/sprite.svg#icon"> bloqueado por CSP, ícones ausentes
  • Solução: Mudar para sprite inline/entrega same-origin, relaxamento CSP mínimo
  • Resultado: Exibição estável, eficiência de cache melhorada

FAQ

P. O texto deve ser convertido em contorno?

R. Converter para logos onde integridade da forma é crítica. Não converter texto de corpo (prejudica legibilidade/traduzibilidade).

P. E sobre incorporar bitmaps com <image>?

R. Possível mas note o manuseio de referência externa/URL de dados. Gerenciamento de espaço de cor/tamanho fica difícil, então usar com parcimônia.

P. Como lidar com expressões de filtro?

R. Tentar alternativas CSS primeiro. Se absolutamente necessário, considerar versões rasterizadas de menor resolução.

Checklist (Para Deploy)

  • [ ] Forçar SVGO no CI (precision/convertPathData/cleanupIds)
  • [ ] viewBox/preserveAspectRatio explícito, CLS zero
  • [ ] Eliminar filtros pesados/blur (alternativas raster se necessário)
  • [ ] Proibir script/foreignObject/on*, aplicar sanitização + CSP
  • [ ] Entrega same-origin, image/svg+xml + compressão + cache longo prazo
  • [ ] Acessibilidade (title/desc/role/focusable) e fallbacks prontos

Ferramentas relacionadas

Artigos relacionados