{
"$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"
}