{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreihkwjnywwcfa6s4n2wqyj3cxcbnltdxmc3jxu3cr7z5kx3zbhs4fe",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mp3cvf52gny2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreifrrtw3krvz2bi3v33n45bbsbhs6fkhjo2ydzpblgrjekqzucfokm"
    },
    "mimeType": "image/webp",
    "size": 72794
  },
  "path": "/jamiepark-design/zero-dependency-favicon-generation-with-canvas-api-build-your-own-in-50-lines-2bjj",
  "publishedAt": "2026-06-25T01:24:45.000Z",
  "site": "https://dev.to",
  "tags": [
    "javascript",
    "webdev",
    "tutorial",
    "frontend",
    "genfavicon.org"
  ],
  "textContent": "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.\n\n##  What You Actually Need in 2026\n\nThe minimum favicon set has grown. Here's what covers every major browser and platform:\n\nFile | Size | Purpose\n---|---|---\n`favicon.ico` | 32×32 | Legacy browser fallback\n`favicon-16.png` | 16×16 | Browser tab (small screens)\n`favicon-32.png` | 32×32 | Browser tab (standard)\n`apple-touch-icon.png` | 180×180 | iOS Home Screen, Safari\n`icon-192.png` | 192×192 | Android PWA, Chrome install\n`icon-512.png` | 512×512 | PWA splash screen\n\nThat's six files. Most generators create 15+ — including manifest.json, browserconfig.xml, and sizes no one uses anymore. You don't need the bloat.\n\n##  The Canvas API Approach\n\nThe 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.\n\nHere's the core:\n\n\n\n    function generateFaviconSet(svgPathData, color = '#1e293b') {\n      const sizes = [\n        { name: 'favicon-16.png', w: 16 },\n        { name: 'favicon-32.png', w: 32 },\n        { name: 'apple-touch-icon.png', w: 180 },\n        { name: 'icon-192.png', w: 192 },\n        { name: 'icon-512.png', w: 512 },\n      ];\n\n      const canvas = document.createElement('canvas');\n      const ctx = canvas.getContext('2d');\n\n      sizes.forEach(({ name, w }) => {\n        canvas.width = w;\n        canvas.height = w;\n        ctx.clearRect(0, 0, w, w);\n        const scale = (w * 0.8) / 100;\n        const offset = w * 0.1;\n        ctx.save();\n        ctx.translate(offset, offset);\n        ctx.scale(scale, scale);\n        const path = new Path2D(svgPathData);\n        ctx.fillStyle = color;\n        ctx.fill(path);\n        ctx.restore();\n        const link = document.createElement('a');\n        link.download = name;\n        link.href = canvas.toDataURL('image/png');\n        link.click();\n      });\n    }\n\n\nThe 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.\n\n##  Why Not Use `toBlob()`?\n\n`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.\n\n##  One Click → ZIP of All Sizes\n\nWrapping 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.\n\nBut 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.\n\n##  The ICO Format: One Quirk\n\nCanvas can export PNG and JPEG, but not ICO. For `favicon.ico`, you have two clean options:\n\n  1. **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.\n  2. **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.\n\n\n\nPersonally, 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.\n\n##  The Real Win: Your Build Step Gets Simpler\n\nEvery 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.\n\nIf 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.\n\n_Code snippets in this article are MIT-licensed. Use them however you want._",
  "title": "Zero-Dependency Favicon Generation with Canvas API — Build Your Own in 50 Lines"
}