<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shahab Ud Din</title>
    <description>The latest articles on DEV Community by Shahab Ud Din (@shahabdev).</description>
    <link>https://dev.to/shahabdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4222419%2Fb23aadcd-4810-4b2a-8f96-bc6a0557866e.png</url>
      <title>DEV Community: Shahab Ud Din</title>
      <link>https://dev.to/shahabdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shahabdev"/>
    <language>en</language>
    <item>
      <title>How I built a free JPEG-to-SVG converter that runs 100% in the browser</title>
      <dc:creator>Shahab Ud Din</dc:creator>
      <pubDate>Mon, 02 Nov 2026 13:17:57 +0000</pubDate>
      <link>https://dev.to/shahabdev/how-i-built-a-free-jpeg-to-svg-converter-that-runs-100-in-the-browser-842</link>
      <guid>https://dev.to/shahabdev/how-i-built-a-free-jpeg-to-svg-converter-that-runs-100-in-the-browser-842</guid>
      <description>&lt;h1&gt;
  
  
  How I built a free JPEG-to-SVG converter that runs 100% in the browser
&lt;/h1&gt;

&lt;p&gt;Every online image converter I tried did the same thing: upload your file to somebody's server, wait in a queue, download the result, hope your image is not kept for "analytics". I wanted a converter with no upload step at all. So I built one: &lt;a href="https://jpegtosvg.com" rel="noopener noreferrer"&gt;jpegtosvg.com&lt;/a&gt;. It turns JPEG, PNG and WebP images into SVG paths entirely on your device. No account, no file uploads, no backend that ever sees your image.&lt;/p&gt;

&lt;p&gt;Here is how it works under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core decision: no backend
&lt;/h2&gt;

&lt;p&gt;Most converters need a server because raster-to-vector tracing is CPU heavy. My decision was to skip the server completely. The whole site is a static build deployed to Cloudflare Pages with &lt;code&gt;wrangler pages deploy&lt;/code&gt;. There is no database, no API, no conversion queue, no API secret anywhere. The build step (&lt;code&gt;tsx scripts/build.ts&lt;/code&gt;) generates plain HTML, and that is what gets served.&lt;/p&gt;

&lt;p&gt;This has three consequences I really like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is structural, not a promise.&lt;/strong&gt; Your image never leaves your device, so there is nothing to leak, sell, or subpoena.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost is near zero.&lt;/strong&gt; Static hosting on Cloudflare Pages means I pay nothing per conversion. A server-side tracer would bill me CPU time for every image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cannot go down under load.&lt;/strong&gt; There is no conversion server to overload. Ten thousand people converting at once costs me the same as one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff is obvious: the user's device does the work. So the engineering effort went into making heavy computation feel light.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tracing engine: ImageTracerJS in a Web Worker
&lt;/h2&gt;

&lt;p&gt;The actual raster-to-vector work is done by &lt;a href="https://github.com/jankovicsandras/imagetracer" rel="noopener noreferrer"&gt;ImageTracerJS&lt;/a&gt;, a JavaScript port of the classic bitmap tracing approach: quantize the colors, trace the boundaries of each color region into paths, and emit SVG. It preserves colors, not just black-and-white silhouettes.&lt;/p&gt;

&lt;p&gt;Running that on the main thread would freeze the page for seconds on a large image. So all tracing runs inside a dedicated Web Worker (&lt;code&gt;tracer-worker.js&lt;/code&gt;). The UI thread only handles the file picker, the preview, and progress messages. If the worker crashes or hangs, a timeout kills it and the UI reports the failure cleanly instead of locking up the tab. A shared worker instance is reused when tracing several images in a row, then terminated by the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taming large images before tracing
&lt;/h2&gt;

&lt;p&gt;Tracing cost grows fast with pixel count, so the pipeline caps the work before the tracer ever sees it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input limits: 12 MB file size, 32 megapixels.&lt;/li&gt;
&lt;li&gt;Before tracing, the image is drawn to a canvas scaled so the longest side is at most 1200 pixels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That 1200-pixel cap is the single biggest performance decision. A 4000-pixel photo becomes a 1200-pixel working copy, which traces in a fraction of the time, and the resulting SVG scales back up losslessly because it is vector output. The user gets a small file that renders sharp at any size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format support without a server
&lt;/h2&gt;

&lt;p&gt;Browsers cannot decode everything natively, so the converter bundles small decoders, all client-side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HEIC/HEIF&lt;/strong&gt; (iPhone photos) via &lt;code&gt;heic2any&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TIFF&lt;/strong&gt; via &lt;code&gt;utif2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF pages&lt;/strong&gt; rendered with &lt;code&gt;pdfjs-dist&lt;/code&gt;, then traced like any other raster input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text extraction&lt;/strong&gt; with &lt;code&gt;tesseract.js&lt;/code&gt;, run on-device with the same privacy story&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is lazy-loaded. The PDF renderer, for example, is only imported when the user actually drops a PDF. The initial page load stays light.&lt;/p&gt;

&lt;h2&gt;
  
  
  SVG cleanup: not every path deserves to survive
&lt;/h2&gt;

&lt;p&gt;Raw tracer output is verbose and can contain junk, so there is a cleanup pass (&lt;code&gt;vector.js&lt;/code&gt;, about 180 lines). It sanitizes the SVG through an allowlist of elements (&lt;code&gt;svg&lt;/code&gt;, &lt;code&gt;g&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;rect&lt;/code&gt;, &lt;code&gt;circle&lt;/code&gt;, &lt;code&gt;ellipse&lt;/code&gt;, &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;polyline&lt;/code&gt;, &lt;code&gt;polygon&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;tspan&lt;/code&gt; and a few more) and drops everything else. The cleanup is deliberately conservative: it is meant for static artwork and does not try to simplify geometry, because aggressive simplification destroys the details people actually wanted vectorized.&lt;/p&gt;

&lt;p&gt;There is also a banner export tool that preserves proportions and accepts output dimensions from 32 to 4096 pixels, for the common "I need this logo as a crisp banner" case.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Web Workers are non-negotiable for client-side compute.&lt;/strong&gt; The difference between "the tab froze for 8 seconds" and "a progress bar moved for 8 seconds" is the difference between a tool people trust and one they close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downscale before you compute.&lt;/strong&gt; Capping the working resolution was worth more than any algorithmic optimization. Vector output means the input resolution barely matters for the final quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static sites can do real work.&lt;/strong&gt; We default to reaching for a backend, but a surprising amount of "server work" is just computation that a modern phone or laptop handles fine. The backend I did not build is the backend I never have to monitor, scale, or pay for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy as architecture beats privacy as policy.&lt;/strong&gt; "We delete your files after 24 hours" is a promise. "Your files never leave your device" is a fact. Users understand the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The converter is live at &lt;a href="https://jpegtosvg.com" rel="noopener noreferrer"&gt;jpegtosvg.com&lt;/a&gt;. Drop in a JPEG, PNG or WebP and watch your browser do the whole job. If you build something with heavy client-side computation, I would love to hear how you kept the UI responsive.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>svg</category>
    </item>
  </channel>
</rss>
