<?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: Calvin</title>
    <description>The latest articles on DEV Community by Calvin (@sunkehappy).</description>
    <link>https://dev.to/sunkehappy</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%2F536815%2Fde043d5a-af99-41ef-94b1-9ba542596cb8.png</url>
      <title>DEV Community: Calvin</title>
      <link>https://dev.to/sunkehappy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sunkehappy"/>
    <language>en</language>
    <item>
      <title>Building OmniGIF: A Client-Side GIF Toolkit with WebAssembly (No Server Uploads)</title>
      <dc:creator>Calvin</dc:creator>
      <pubDate>Wed, 26 Aug 2026 01:30:38 +0000</pubDate>
      <link>https://dev.to/sunkehappy/building-omnigif-a-client-side-gif-toolkit-with-webassembly-no-server-uploads-29o1</link>
      <guid>https://dev.to/sunkehappy/building-omnigif-a-client-side-gif-toolkit-with-webassembly-no-server-uploads-29o1</guid>
      <description>&lt;p&gt;Most online GIF tools send your file to a remote server. I built &lt;a href="https://www.omnigif.com" rel="noopener noreferrer"&gt;OmniGIF&lt;/a&gt; to prove that's unnecessary — modern browsers can decode, encode, and transform GIFs entirely on the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture problem
&lt;/h2&gt;

&lt;p&gt;Traditional flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Upload to server → Process → Download result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OmniGIF flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Load page → Process in browser (JS + WASM) → Download result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file never hits our infrastructure. From a privacy and latency perspective, this changes the product entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js 15 (SSG)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i18n&lt;/td&gt;
&lt;td&gt;next-intl (EN + ZH)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Cloudflare (OpenNext)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;PostHog&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The site is statically generated. There's no server-side file processing — which is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client-side libraries
&lt;/h2&gt;

&lt;p&gt;GIF work on the web is surprisingly mature. OmniGIF relies on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/matt-way/gifuct-js" rel="noopener noreferrer"&gt;gifuct-js&lt;/a&gt;&lt;/strong&gt; — GIF89a decoder; parses frames and delay metadata locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/jnordberg/gif.js" rel="noopener noreferrer"&gt;gif.js&lt;/a&gt;&lt;/strong&gt; — GIF encoder running in a Web Worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ffmpegwasm.netlify.app/" rel="noopener noreferrer"&gt;ffmpeg.wasm&lt;/a&gt;&lt;/strong&gt; — FFmpeg compiled to WebAssembly for Live Photo / video → GIF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/photopea/UPNG.js" rel="noopener noreferrer"&gt;UPNG.js&lt;/a&gt;&lt;/strong&gt; — PNG/APNG encode/decode for high-color animated workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gifsicle-wasm-browser&lt;/strong&gt; — GIF optimization in the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSZip&lt;/strong&gt; — bundling extracted frames into ZIP downloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per &lt;a href="https://developer.mozilla.org/en-US/docs/WebAssembly" rel="noopener noreferrer"&gt;MDN's WebAssembly docs&lt;/a&gt;, Wasm brings near-native performance to the web — which matters when you're encoding 30 frames at 1080p.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the toolkit includes
&lt;/h2&gt;

&lt;p&gt;31 tools across four categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  GIF Converter
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.omnigif.com/gif-converter/gif-to-png" rel="noopener noreferrer"&gt;GIF to PNG&lt;/a&gt; / &lt;a href="https://www.omnigif.com/gif-converter/gif-to-webp" rel="noopener noreferrer"&gt;WebP&lt;/a&gt; / &lt;a href="https://www.omnigif.com/gif-converter/gif-to-apng" rel="noopener noreferrer"&gt;APNG&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.omnigif.com/gif-converter/png-to-gif" rel="noopener noreferrer"&gt;PNG to GIF&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-converter/apng-to-gif" rel="noopener noreferrer"&gt;APNG to GIF&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.omnigif.com/live-to-gif" rel="noopener noreferrer"&gt;Live Photo to GIF&lt;/a&gt; — MOV/MP4 → GIF with quality/FPS controls&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GIF Tools
&lt;/h3&gt;

&lt;p&gt;Frame inspection, &lt;a href="https://www.omnigif.com/gif-tools/crop-gif" rel="noopener noreferrer"&gt;crop&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-tools/trim-gif" rel="noopener noreferrer"&gt;trim&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-tools/reverse-gif" rel="noopener noreferrer"&gt;reverse&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-tools/gif-compressor" rel="noopener noreferrer"&gt;compress&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-tools/compress-gif-to-size" rel="noopener noreferrer"&gt;compress to target size&lt;/a&gt;, text/watermark overlay, &lt;a href="https://www.omnigif.com/gif-tools/gif-overlay" rel="noopener noreferrer"&gt;GIF overlay&lt;/a&gt;, watermark removal.&lt;/p&gt;

&lt;h3&gt;
  
  
  GIF Maker
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.omnigif.com/gif-maker/intensify" rel="noopener noreferrer"&gt;Intensify&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-maker/shake-image-effect" rel="noopener noreferrer"&gt;shake&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-maker/fire-text" rel="noopener noreferrer"&gt;fire text&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-maker/vinyl-spinning-gif-maker" rel="noopener noreferrer"&gt;vinyl spin&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  GIF Effects
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.omnigif.com/gif-effects/black-and-white-gif" rel="noopener noreferrer"&gt;B&amp;amp;W&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/add-border" rel="noopener noreferrer"&gt;border&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/confetti-gif-maker" rel="noopener noreferrer"&gt;confetti&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/gif-dither" rel="noopener noreferrer"&gt;dither&lt;/a&gt; (Floyd–Steinberg, Bayer, Atkinson), &lt;a href="https://www.omnigif.com/gif-effects/gif-pixelator" rel="noopener noreferrer"&gt;pixelate&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/gif-fade-effect" rel="noopener noreferrer"&gt;fade&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/gif-speed-changer" rel="noopener noreferrer"&gt;speed&lt;/a&gt;, &lt;a href="https://www.omnigif.com/gif-effects/glitch-gif-maker" rel="noopener noreferrer"&gt;glitch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Full list: &lt;a href="https://www.omnigif.com/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; (machine-readable sitemap for tools).&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why SSG, not SSR?
&lt;/h3&gt;

&lt;p&gt;All parameters are client-side. No need for server rendering of user files. Faster cold loads, simpler deployment on Cloudflare.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Web Workers?
&lt;/h3&gt;

&lt;p&gt;GIF encoding blocks the main thread. gif.js and heavy transforms run off-thread so the UI stays responsive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why per-tool pages instead of one mega-app?
&lt;/h3&gt;

&lt;p&gt;SEO, shareability, and focused UX. Each tool has its own URL, metadata, FAQ, and structured data. Users land on exactly what they need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-time preview on desktop
&lt;/h3&gt;

&lt;p&gt;For tools with tunable parameters (crop, effects, compression), the right panel shows a live preview on PC. Mobile gets before/after after generation — saves battery and screen space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome extension
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.omnigif.com/extension" rel="noopener noreferrer"&gt;OmniGIF extension&lt;/a&gt; bridges web → tool: right-click any image, pick a destination tool, and the image is passed via URL params / clipboard. Offline install package available for environments that need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; — Large GIFs (many frames × high resolution) can OOM on mobile. We enforce file size limits and warn early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GIF color depth&lt;/strong&gt; — GIF is 256 colors max. Converting from APNG/PNG requires palette reduction; dithering quality varies by algorithm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ffmpeg.wasm bundle size&lt;/strong&gt; — First load downloads a sizable Wasm binary. Lazy-loading FFmpeg only on video tools helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-browser APNG&lt;/strong&gt; — APNG support is uneven; we document browser compatibility on converter pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Privacy as a feature, not a footnote
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/Graphics/GIF/spec-gif89a.txt" rel="noopener noreferrer"&gt;W3C GIF89a spec&lt;/a&gt; hasn't changed much in decades, but how we process GIFs can change. Client-side tooling means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No file retention policies to worry about&lt;/li&gt;
&lt;li&gt;No GDPR data-transfer questions for the image itself&lt;/li&gt;
&lt;li&gt;Works offline after first page load (extension + cached assets)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Live at &lt;a href="https://www.omnigif.com" rel="noopener noreferrer"&gt;https://www.omnigif.com&lt;/a&gt; — free, no account.&lt;/p&gt;

&lt;p&gt;More background: &lt;a href="https://www.omnigif.com/about" rel="noopener noreferrer"&gt;About OmniGIF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions: &lt;a href="https://www.omnigif.com/faq" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt; | &lt;a href="https://www.omnigif.com/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built by Calvin Sun as an indie project. Feedback welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
