<?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: Allplix</title>
    <description>The latest articles on DEV Community by Allplix (@allplix).</description>
    <link>https://dev.to/allplix</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3817181%2Fd366bba8-ff7c-4357-9c73-524ecf484ae0.png</url>
      <title>DEV Community: Allplix</title>
      <link>https://dev.to/allplix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/allplix"/>
    <language>en</language>
    <item>
      <title>Client-side background removal with ONNX Runtime Web — a few things that tripped me up</title>
      <dc:creator>Allplix</dc:creator>
      <pubDate>Sun, 12 Apr 2026 12:30:53 +0000</pubDate>
      <link>https://dev.to/allplix/client-side-background-removal-with-onnx-runtime-web-a-few-things-that-tripped-me-up-350g</link>
      <guid>https://dev.to/allplix/client-side-background-removal-with-onnx-runtime-web-a-few-things-that-tripped-me-up-350g</guid>
      <description>&lt;p&gt;I built a background remover for my side project that runs the AI model fully client-side — no upload, no account, the&lt;br&gt;
   image never leaves the browser tab. I wanted to write a few notes on the bits that actually gave me trouble, because&lt;br&gt;
  most client-side ML articles I found glossed over the things that break in production.&lt;/p&gt;

&lt;p&gt;The setup&lt;/p&gt;

&lt;p&gt;I'm using &lt;a class="mentioned-user" href="https://dev.to/imgly"&gt;@imgly&lt;/a&gt;/background-removal, which is a nice wrapper around ONNX Runtime Web. It ships two model variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isnet_quint8 — 8-bit quantized, around 44 MB&lt;/li&gt;
&lt;li&gt;isnet_fp16 — half-precision, bigger but slightly cleaner edges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I default to quint8 in a "Fast" mode and fp16 in a "Best quality" mode. Most users never notice the difference.&lt;/p&gt;

&lt;p&gt;The import is lazy so the model only starts downloading when a user drops their first image:&lt;/p&gt;

&lt;p&gt;let _imglyPromise: Promise | null = null;&lt;br&gt;
  function getImgly() {&lt;br&gt;
    if (!_imglyPromise) _imglyPromise = import("&lt;a class="mentioned-user" href="https://dev.to/imgly"&gt;@imgly&lt;/a&gt;/background-removal");&lt;br&gt;
    return _imglyPromise;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;Simple enough. Now the parts that broke.&lt;/p&gt;

&lt;p&gt;CSP wants two unsafe-eval directives, not one&lt;/p&gt;

&lt;p&gt;My site has a strict Content-Security-Policy. First time I pushed the background remover to production, it crashed&lt;br&gt;
  with:&lt;/p&gt;

&lt;p&gt;Refused to compile WebAssembly module because 'wasm-eval' is not an allowed&lt;br&gt;
  source of script in the following Content Security Policy directive&lt;/p&gt;

&lt;p&gt;ONNX Runtime Web loads its WebAssembly through paths that trigger both the JS eval guard AND the WASM eval guard. You&lt;br&gt;
  need BOTH of these in your script-src:&lt;/p&gt;

&lt;p&gt;'unsafe-eval' 'wasm-unsafe-eval'&lt;/p&gt;

&lt;p&gt;I don't love it. If anyone has found a way to load ONNX models without 'unsafe-eval', I'd genuinely like to hear it.&lt;br&gt;
  For now, wasm-unsafe-eval alone is not enough.&lt;/p&gt;

&lt;p&gt;COOP/COEP for threaded inference&lt;/p&gt;

&lt;p&gt;ONNX Runtime Web can run multi-threaded via Web Workers, and threaded inference needs SharedArrayBuffer. Chrome only&lt;br&gt;
  exposes SharedArrayBuffer on pages that are cross-origin isolated, which needs two response headers:&lt;/p&gt;

&lt;p&gt;{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },&lt;br&gt;
  { key: "Cross-Origin-Embedder-Policy", value: "credentialless" },&lt;/p&gt;

&lt;p&gt;The credentialless value is the one you want. The stricter require-corp blocks every third-party image embed on the&lt;br&gt;
  page unless the remote server sends Cross-Origin-Resource-Policy back — obviously not something you can guarantee on a&lt;br&gt;
   tool where users drop in arbitrary images from anywhere.&lt;/p&gt;

&lt;p&gt;Heads up: these headers can break Google Fonts if you apply them too broadly. I ended up scoping them to specific tool&lt;br&gt;
   routes via Next.js header config so the rest of the site isn't affected.&lt;/p&gt;

&lt;p&gt;The hydration bug that took me a week to find&lt;/p&gt;

&lt;p&gt;Not ML-specific but it's the one I'm happiest to have caught. I had a free-tier usage counter for AI-generated&lt;br&gt;
  backgrounds (3 per day) and wrote this:&lt;/p&gt;

&lt;p&gt;const [remaining, setRemaining] = useState(() =&amp;gt; {&lt;br&gt;
    if (typeof window === "undefined") return 3;&lt;br&gt;
    try {&lt;br&gt;
      const count = localStorage.getItem("ai-bg-count");&lt;br&gt;
      // ... compute from stored date&lt;br&gt;
      return Math.max(0, 3 - parseInt(count || "0", 10));&lt;br&gt;
    } catch { return 3; }&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;Looks fine. The typeof window check handles SSR, right? Ship to prod. Users occasionally report weird flickering on&lt;br&gt;
  the tool page.&lt;/p&gt;

&lt;p&gt;Here's the bug: on the server, the initializer returns 3 (no window, fall through). On the client, during hydration,&lt;br&gt;
  the initializer runs AGAIN and reads localStorage, so it may return 2. React compares the server HTML to the client&lt;br&gt;
  render, sees the mismatch, and throws a hydration error — which tears down the tree and rebuilds it from scratch. Any&lt;br&gt;
  tool state the user had at that moment gets reset.&lt;/p&gt;

&lt;p&gt;The fix is what every React doc tells you to do and I had ignored:&lt;/p&gt;

&lt;p&gt;const [remaining, setRemaining] = useState(3); // stable on server AND first client render&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
    try {&lt;br&gt;
      const count = localStorage.getItem("ai-bg-count");&lt;br&gt;
      setRemaining(Math.max(0, 3 - parseInt(count || "0", 10)));&lt;br&gt;
    } catch {}&lt;br&gt;
  }, []);&lt;/p&gt;

&lt;p&gt;Initial render is now identical on server and client. The real value lands after hydration via the effect. No&lt;br&gt;
  mismatch.&lt;/p&gt;

&lt;p&gt;My rule now: if a useState initializer contains typeof window, it's a hydration bug waiting to surface. Move it to a&lt;br&gt;
  useEffect.&lt;/p&gt;

&lt;p&gt;Mobile OOMs on big images&lt;/p&gt;

&lt;p&gt;I only caught this one because Sentry started reporting "tab crashed" events from Android Chrome. A modern phone photo&lt;br&gt;
   is easily 48 megapixels, which decodes to roughly 192 MB of raw ImageData per copy. The pipeline allocates multiple&lt;br&gt;
  copies (original + mask + result) and, on a 4 GB Android, you OOM before the model even starts. Safari on iOS silently&lt;br&gt;
   kills the tab instead of crashing.&lt;/p&gt;

&lt;p&gt;The cheap fix is to probe the image with a lightweight Image element before any real pipeline work:&lt;/p&gt;

&lt;p&gt;const probe = new Image();&lt;br&gt;
  probe.onload = () =&amp;gt; {&lt;br&gt;
    if (probe.naturalWidth &amp;gt; 8192 || probe.naturalHeight &amp;gt; 8192) {&lt;br&gt;
      URL.revokeObjectURL(url);&lt;br&gt;
      toast.error("Image too large — max 8192 × 8192");&lt;br&gt;
      return;&lt;br&gt;
    }&lt;br&gt;
    // Safe to proceed with the real pipeline&lt;br&gt;
  };&lt;br&gt;
  probe.onerror = () =&amp;gt; {&lt;br&gt;
    URL.revokeObjectURL(url);&lt;br&gt;
    toast.error("Could not decode image");&lt;br&gt;
  };&lt;br&gt;
  probe.src = url;&lt;/p&gt;

&lt;p&gt;Two things this also gave me for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An onerror path, so corrupt files no longer silently stall the UI in a loading state forever.&lt;/li&gt;
&lt;li&gt;A matching revokeObjectURL on both code paths, which fixed a slow leak where rejected images were leaving blob URLs
pinned in tab memory for the rest of the session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more small thing: when you do end up reading pixels from a canvas (for fine edge work), pass willReadFrequently:&lt;br&gt;
  true when you create the 2D context:&lt;/p&gt;

&lt;p&gt;const ctx = canvas.getContext("2d", { willReadFrequently: true });&lt;/p&gt;

&lt;p&gt;It hints to the browser to keep the bitmap CPU-side instead of flushing the GPU pipeline on every getImageData().&lt;br&gt;
  Meaningful speedup on mobile.&lt;/p&gt;

&lt;p&gt;That's it&lt;/p&gt;

&lt;p&gt;If you want to try the result, I have a live version at &lt;a href="https://www.allplix.com/en/background-remover" rel="noopener noreferrer"&gt;https://www.allplix.com/en/background-remover&lt;/a&gt;. Drop an image,&lt;br&gt;
  open DevTools → Network, verify nothing uploads.&lt;/p&gt;

&lt;p&gt;Questions or better solutions to any of these, drop a comment.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built 54 free browser tools as a solo dev — everything runs client-side</title>
      <dc:creator>Allplix</dc:creator>
      <pubDate>Tue, 10 Mar 2026 15:34:08 +0000</pubDate>
      <link>https://dev.to/allplix/i-built-55-free-browser-tools-as-a-solo-dev-everything-runs-client-side-di3</link>
      <guid>https://dev.to/allplix/i-built-55-free-browser-tools-as-a-solo-dev-everything-runs-client-side-di3</guid>
      <description>&lt;p&gt;Hey dev community 👋&lt;br&gt;
I've been building Allplix (&lt;a href="https://www.allplix.com" rel="noopener noreferrer"&gt;https://www.allplix.com&lt;/a&gt;) for the past few months. It's a collection of 55 free online tools that all run 100% in the browser — no server uploads, no signup, no limits.&lt;br&gt;
What's inside&lt;br&gt;
Image &amp;amp; Media tools:&lt;/p&gt;

&lt;p&gt;AI Background Remover (uses MediaPipe ML in the browser)&lt;br&gt;
Image compressor, resizer, enhancer&lt;br&gt;
AI Video Editor with Auto-Reframe (YouTube → TikTok)&lt;/p&gt;

&lt;p&gt;Developer tools:&lt;/p&gt;

&lt;p&gt;JSON Formatter &amp;amp; Validator&lt;br&gt;
Regex Tester with real-time highlighting&lt;br&gt;
UUID Generator&lt;br&gt;
CSS Gradient Generator&lt;br&gt;
Color Converter (HEX/RGB/HSL/CMYK)&lt;/p&gt;

&lt;p&gt;File converters (18 formats):&lt;/p&gt;

&lt;p&gt;JPG ↔ PNG ↔ WebP ↔ PDF&lt;br&gt;
DOCX → PDF/TXT/HTML&lt;br&gt;
CSV ↔ JSON ↔ XLSX&lt;br&gt;
HEIC → JPG/PNG&lt;/p&gt;

&lt;p&gt;AI-powered tools:&lt;/p&gt;

&lt;p&gt;AI Writing Assistant (rewrite, summarize, translate)&lt;br&gt;
AI Spell Checker&lt;br&gt;
AI Marketing Studio&lt;br&gt;
AI Resume Builder with ATS scoring&lt;br&gt;
Voice-to-Text (12 languages)&lt;/p&gt;

&lt;p&gt;Productivity:&lt;/p&gt;

&lt;p&gt;Pomodoro Timer, Todo List, Notes&lt;br&gt;
Markdown Editor with live preview&lt;br&gt;
Calculators (percentage, VAT, salary, loan)&lt;/p&gt;

&lt;p&gt;The tech&lt;/p&gt;

&lt;p&gt;Next.js 16 with App Router + Turbopack&lt;br&gt;
React 19 with Server Components&lt;br&gt;
Tailwind CSS 4&lt;br&gt;
12 languages (FR, EN, ES, DE, PT, IT, AR, ZH, JA, HI, PL, RU)&lt;br&gt;
936 pages in the sitemap&lt;br&gt;
Client-side processing using Canvas API, WebAssembly, Web Audio API, MediaPipe ML&lt;/p&gt;

&lt;p&gt;Why client-side?&lt;br&gt;
Privacy. Your files never leave your device. No server costs either — everything runs on Vercel's free tier. The AI text tools (writing assistant, spell checker) use API calls to Groq's free tier, but media files (images, videos, audio) are processed 100% locally.&lt;br&gt;
Lighthouse scores&lt;/p&gt;

&lt;p&gt;Performance: 94-99&lt;br&gt;
Accessibility: 96-100&lt;br&gt;
Best Practices: 100&lt;br&gt;
SEO: 100&lt;/p&gt;

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

&lt;p&gt;SEO is harder than coding. I had 4 critical SEO bugs that kept 632 pages from being indexed. JSON-LD hidden in client components, middleware redirecting everything to /en/, missing canonical tags... Took weeks to find and fix.&lt;br&gt;
i18n is a massive multiplier. 55 tools × 12 languages = 936 pages. Each page is a door for Google traffic.&lt;br&gt;
Marketing is the real challenge. Building the tools was the easy part. Getting people to know they exist is 10x harder.&lt;/p&gt;

&lt;p&gt;The site is called Allplix — check it out at &lt;a href="https://www.allplix.com" rel="noopener noreferrer"&gt;https://www.allplix.com&lt;/a&gt;&lt;br&gt;
I'd love to hear your feedback. What tools would you want to see added?&lt;/p&gt;

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