DEV Community

Zero-Dependency Favicon Generation with Canvas API - Build Your Own in 50 Lines

Every React project I've worked on eventually adds a favicon generation dependency. favicons alone pulls in sharp, which pulls in native binaries, which breaks on that one teammate's M1 Mac and eats 15 minutes of your afternoon. I got tired of it and built a zero-dependency favicon generator using nothing but the Canvas API. It ships all six sizes you need for a modern web app in under 50 lines.

What You Actually Need in 2026

The minimum favicon set has grown. Here's what covers every major browser and platform:

File Size Purpose
favicon.ico 32×32 Legacy browser fallback
favicon-16.png 16×16 Browser tab (small screens)
favicon-32.png 32×32 Browser tab (standard)
apple-touch-icon.png 180×180 iOS Home Screen, Safari
icon-192.png 192×192 Android PWA, Chrome install
icon-512.png 512×512 PWA splash screen

That's six files. Most generators create 15+ - including manifest.json, browserconfig.xml, and sizes no one uses anymore. You don't need the bloat.

The Canvas API Approach

The trick is that Canvas can render SVG paths (via Path2D) and then export to PNG at any resolution. No DOM, no fonts, no external libraries. Here's the core:

function generateFaviconSet(svgPathData, color = '#1e293b') {
  const sizes = [
    { name: 'favicon-16.png', w: 16 },
    { name: 'favicon-32.png', w: 32 },
    { name: 'apple-touch-icon.png', w: 180 },
    { name: 'icon-192.png', w: 192 },
    { name: 'icon-512.png', w: 512 },
  ];

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  sizes.forEach(({ name, w }) => {
    canvas.width = w;
    canvas.height = w;
    ctx.clearRect(0, 0, w, w);

    const scale = (w * 0.8) / 100;
    const offset = w * 0.1;

    ctx.save();
    ctx.translate(offset, offset);
    ctx.scale(scale, scale);

    const path = new Path2D(svgPathData);
    ctx.fillStyle = color;
    ctx.fill(path);

    ctx.restore();

    const link = document.createElement('a');
    link.download = name;
    link.href = canvas.toDataURL('image/png');
    link.click();
  });
}

The key insight: Canvas's Path2D constructor accepts the exact same path data string as SVG's d attribute. You can prototype your icon in any SVG editor, copy the d="..." value, and feed it directly into the function above. No parsing, no conversion.

Why Not Use toBlob()?

toBlob() would be more memory-efficient than toDataURL(), but it's asynchronous and creates event-handling complexity when generating multiple sizes. For favicon-sized images (max 512×512), toDataURL() keeps the code synchronous and readable.

One Click → ZIP of All Sizes

Wrapping this into a proper tool means adding JSZip for packaging and a file input for custom images. I built exactly that at genfavicon.org - upload any image, get all six favicon sizes in one ZIP, plus the HTML <link> tags pre-generated. The entire thing runs in the browser; nothing gets uploaded to any server.

But if you want to build your own pipeline - maybe you need a custom color palette or want to integrate it into a design system tool - the Canvas approach above is the foundation. No sharp, no imagemagick, no native deps.

The ICO Format: One Quirk

Canvas can export PNG and JPEG, but not ICO. For favicon.ico, you have two clean options:

  • Skip it. Chrome, Firefox, Edge, and Safari all support PNG favicons via <link rel="icon" type="image/png">. The ICO format is only needed for IE11 and some ancient Android browsers.
  • Use a tiny encoder. A minimal ICO encoder is about 30 lines - it's just a BMP header + PNG data wrapped in an ICO container.

Personally, I generate a 32×32 PNG and use <link rel="icon" type="image/png" sizes="32x32"> for modern browsers plus a tiny .ico fallback for the stragglers.

The Real Win: Your Build Step Gets Simpler

Every project I've switched from sharp-based favicon generation to an in-browser tool has deleted roughly three dependencies and 20 lines of build config. For Next.js specifically, this means no sharp in node_modules, no native rebuilds on npm install, and no "works on my machine" moments when the designer updates the icon.

If you don't want to write the Canvas code yourself, genfavicon.org does the same thing with a drag-and-drop UI. But the Canvas approach is genuinely simple enough that you can embed it in an internal tool in an afternoon.

Code snippets in this article are MIT-licensed. Use them however you want.

Comments

No comments yet. Start the discussion.