SVG vs PNG, JPG and WebP: When SVG Wins and When It Doesn't

A practical guide to SVG: real file size measurements against PNG and WebP, when SVG is the wrong choice, how to export and optimise clean files, accessibility, security risks of user uploads, and the three ways to put one on a page.

10 min read

By Kingsley Michael · Updated

SVG (Scalable Vector Graphics) stores an image as drawing instructions: a rectangle here, a circle there, a path with these curves, filled with this colour. PNG, JPG and WebP store a grid of pixels. That one difference explains almost everything below: when SVG is tiny and sharp, when it is the wrong tool, and why it needs more care than a PNG when you accept it from strangers.

A real measurement: one logo, four sizes

Claims like "SVG is smaller" are easy to make and rarely shown, so here is a test I ran on a Mac while writing this.

I wrote a simple logo by hand: a blue rounded square, a white ring and a white check mark. Three shapes, 256 by 256 units. I padded it the way design tools tend to (an XML declaration, a comment, a group with a layer id, coordinates written as 128.000), then:

  • rasterised it to PNG at 256, 512 and 1024 pixels square with macOS sips (sips -s format png -z 512 512 logo.svg --out logo_512.png)
  • converted each PNG to lossless WebP with cwebp -lossless -z 9 (libwebp 1.6.0)
  • ran the SVG through SVGO 4.0.0 with its default settings
  • measured gzip size with gzip -9 -c file | wc -c, because web servers normally compress SVG (it is text) but not PNG or WebP (already compressed)
FileBytes
SVG as written703
SVG as written, gzipped393
SVG after SVGO386
SVG after SVGO, gzipped248
PNG 256 x 25611,066
PNG 512 x 512 (2x)26,131
PNG 1024 x 1024 (4x)65,446
Lossless WebP 256 x 2562,778
Lossless WebP 512 x 5126,356
Lossless WebP 1024 x 102412,970

So for this logo, the optimised, gzipped SVG is 248 bytes and covers every size. The 2x PNG you would ship for high-density screens is about 100 times bigger, and even lossless WebP, which is very good at flat colour, is around 25 times bigger at 2x. The PNGs came straight out of sips without a PNG optimiser, so a tool like oxipng would shave some off them, but not two orders of magnitude.

Two caveats so you do not over-read this. A logo with three shapes is the best case for SVG. And at these sizes all the files are small in absolute terms; the difference matters most when you have dozens of icons, or when the logo is the biggest thing above the fold.

When SVG is the right choice

  • Logos, icons and simple illustrations. Flat shapes and a handful of colours. This is what the format was made for.
  • Anything shown at more than one size. One SVG replaces the 1x, 2x and 3x PNG set, and stays sharp when someone zooms the page.
  • Graphics that need to change colour. Dark mode, hover states and themes can be handled in CSS (see currentColor below) instead of exporting a variant for each.
  • Charts, diagrams and UI decoration. These are often generated by code anyway, and text inside an SVG stays real text that can be selected and found with Ctrl+F.
  • Simple animation. Inline SVG can be animated with CSS or JavaScript, for example a check mark drawing itself.

When SVG is the wrong choice

  • Photographs. A photo has no clean edges, just millions of slightly different pixels. Use JPG, WebP or AVIF. Tracing a photo into SVG produces a large file that looks posterised, and an SVG that simply wraps an embedded JPG (a <image href="data:image/jpeg;base64,...">) is just the JPG plus about 33% base64 overhead.
  • Very complex artwork. Detailed illustrations with thousands of paths, gradients, blurs and filters cost CPU every time they are drawn. File size can still be fine. As a stress test I generated an SVG of 5,000 randomly placed, randomly coloured circles: 267,991 bytes raw and 60,147 gzipped, against 476,769 bytes for a 512 pixel PNG and 320,658 for lossless WebP. Random dots are a worst case for PNG too, so this does not mean complex SVG is always small. The practical point is to look at render cost as well: if a large, detailed SVG makes scrolling janky on a mid-range phone, a raster export at the size you actually display may be the better trade.
  • Email. Support for SVG in email clients is poor (Gmail, for one, does not display it). Use PNG in newsletters and signatures.
  • Social preview images. Open Graph and X card images should be PNG or JPG. Platforms generally do not render an SVG og:image.

If you need a PNG of an SVG for one of those cases, the SVG Code to Image tool renders SVG markup to PNG, JPG or WebP at the size you pick, in your browser. For moving between raster formats there is the Image Converter.

Exporting a clean SVG

Most bloated SVGs come from export settings rather than from the artwork. Before exporting from Figma, Illustrator, Inkscape or Affinity:

  1. Convert text to outlines if the font will not be available where the SVG is used. Otherwise the browser substitutes a fallback font.
  2. Flatten what you can. Merge shapes that share a fill, remove hidden layers and empty groups.
  3. Avoid embedding raster images unless the design needs one. A single pasted bitmap defeats the purpose.
  4. Keep the viewBox. It is what lets the SVG scale. You can drop fixed width and height attributes and size it with CSS instead.
  5. Use "presentation attributes" or inline styles rather than a <style> block if the SVG will be inlined into a page, because class names in a <style> block apply to the whole page and can clash between two inlined icons.

If you are starting from a raster logo and have no vector original, the SVG Converter traces it into paths. Tracing works well on high-contrast, flat artwork and poorly on photos. Note that it uploads the image to our server, where the potrace engine does the tracing.

Optimising with SVGO

SVGO is the standard optimiser. It removes editor metadata and comments, rounds numbers, merges paths and shortens path data. On my test logo it went from 703 to 386 bytes. You can see what it did in the output:

<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><title>Fix42 test logo</title><rect width="256" height="256" fill="#2563eb" rx="48" ry="48"/><circle cx="128" cy="128" r="76" fill="none" stroke="#fff" stroke-width="16"/><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="18" d="m96 128 24 24 44-48"/></svg>

The path M 96.000 128.000 L 120.000 152.000 L 164.000 104.000 became m96 128 24 24 44-48, #FFFFFF became #fff, the comment, XML declaration, unused xlink namespace and the layer group are gone.

Run it with npx svgo input.svg -o output.svg, or set it up in your build. Two things to check after optimising:

  • The viewBox survived. SVGO 4 no longer removes it by default, but older versions and some custom configs do, and without it the image stops scaling.
  • The <title> survived if you rely on it for accessibility. SVGO 4 keeps it by default; older presets removed it.

Always look at the result before committing it. Aggressive precision settings can visibly distort small curves.

Three ways to put an SVG on a page

1. <img src="logo.svg" alt="Fix42">

The simplest option. The browser caches the file, scripts inside it do not run, and you give it alt text like any image. The downside is that page CSS cannot reach inside it, so you cannot recolour it on hover or in dark mode.

2. Inline <svg> in the HTML

The markup sits directly in the page. You can style and animate every part with CSS, and it costs no extra request. The downsides: it is not cached separately, it repeats on every page that uses it, and a large inline SVG bloats your HTML. Good for small icons that need theming, poor for a 50KB illustration.

3. CSS background-image: url(pattern.svg)

Fine for decoration such as patterns and dividers. It is invisible to screen readers, which is correct for decoration and wrong for anything meaningful. Like <img>, it cannot be restyled from the page.

A common middle ground for icon sets is an SVG sprite: one file of <symbol> elements, referenced with <svg><use href="/icons.svg#search"/></svg>. You get caching and you can still set colour from CSS.

Theming with currentColor

In an inline SVG (or a <use> reference), set fills and strokes to currentColor:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <path fill="none" stroke="currentColor" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>

The icon now takes the CSS color of its parent. Put it inside a link and it changes colour on hover with the text. Switch to dark mode and it follows. This does not work for SVGs loaded through <img> or background-image, because those are isolated documents that do not inherit page styles.

Accessibility

  • <img>: give it a proper alt, or alt="" if it is decorative.
  • Inline SVG that means something: add role="img" and a <title> as the first child, and point to it with aria-labelledby:
<svg role="img" aria-labelledby="logo-title" viewBox="0 0 256 256">
  <title id="logo-title">Fix42</title>
  ...
</svg>
  • Inline SVG that is decorative, such as an icon next to a text label: add aria-hidden="true" so screen readers skip it.
  • Icon-only buttons: put the label on the button (aria-label="Search") and hide the icon.

Also check contrast. A light grey icon on white fails the same way light grey text does.

Security: be careful with SVGs you did not make

Because SVG is XML that browsers treat as a document, it can contain <script> elements, event handler attributes like onload, javascript: links and <foreignObject> with embedded HTML. If your site lets users upload SVGs, for avatars or attachments for example, and then serves them from your own domain, someone can upload a file that runs JavaScript in your users' sessions when the file is opened directly. That is stored cross-site scripting (XSS).

What helps:

  • Sanitise on upload. Run uploaded SVGs through a sanitiser built for SVG, such as DOMPurify with its SVG profile, which strips scripts, event handlers and dangerous elements. Do not write your own regex filter.
  • Display user SVGs with <img>, not inline. Scripts in an SVG loaded through <img> do not run. Inlining user-supplied markup into your page is the dangerous case.
  • Serve uploads from a separate domain (a different origin from your app, so no access to its cookies), and consider a strict Content-Security-Policy on those responses, or Content-Disposition: attachment so the file downloads instead of opening.
  • Or convert to PNG on upload if you do not need vectors. Rasterising removes the problem entirely.

None of this applies to SVGs your own team exports and commits. It applies to files from people you do not control.

Browser support

Every current browser supports SVG in <img>, inline in HTML, and as a CSS background. Internet Explorer 9, released in 2011, was the last major browser to add basic support, so there is no realistic need for PNG fallbacks on websites today. Support gaps are in places other than browsers: email clients, social preview images, and some older desktop software that imports images. For favicons, browsers have supported SVG for some years, but keep a PNG or ICO fallback alongside it because support is not universal.

Quick checklist

  • Logo, icon, diagram, flat illustration: SVG, optimised with SVGO, gzip enabled on the server.
  • Photo: JPG, WebP or AVIF.
  • Needs to change colour with the theme: inline SVG or sprite with currentColor.
  • Large decorative illustration used on many pages: <img> so it caches.
  • Email or social preview: PNG.
  • Accepting uploads from users: sanitise, display with <img>, serve from a separate origin, or rasterise.

If you want to check what your current images weigh, open your browser's developer tools, go to the Network tab, filter by "Img" and reload. The Webpage Size Checker gives a similar breakdown for any URL.

KM

Kingsley Michael

Founder

Creator of Fix42, building free tools that solve everyday digital friction. Passionate about making powerful utilities accessible to everyone without subscriptions or data harvesting.

Share this article