<?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>Forem: alphadisjunkt</title>
    <description>The latest articles on Forem by alphadisjunkt (@alphadisjunkt).</description>
    <link>https://forem.com/alphadisjunkt</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%2F3796491%2F80a8c06a-02f9-4055-98a9-3f787daf6b8b.png</url>
      <title>Forem: alphadisjunkt</title>
      <link>https://forem.com/alphadisjunkt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alphadisjunkt"/>
    <language>en</language>
    <item>
      <title>I Built AI Face Analysis That Runs Entirely in the Browser — Here's How (Zero Server Costs)</title>
      <dc:creator>alphadisjunkt</dc:creator>
      <pubDate>Fri, 27 Feb 2026 13:13:15 +0000</pubDate>
      <link>https://forem.com/alphadisjunkt/i-built-ai-face-analysis-that-runs-entirely-in-the-browser-heres-how-zero-server-costs-11ni</link>
      <guid>https://forem.com/alphadisjunkt/i-built-ai-face-analysis-that-runs-entirely-in-the-browser-heres-how-zero-server-costs-11ni</guid>
      <description>&lt;h1&gt;
  
  
  How I Built Browser-Based Face Analysis With Zero Server Costs
&lt;/h1&gt;

&lt;p&gt;Every face analysis tool on the internet works the same way: upload your photo to a server, wait for results, hope they don't store your image.&lt;/p&gt;

&lt;p&gt;I wanted to build something different — an AI face analyzer that runs &lt;strong&gt;entirely in the browser&lt;/strong&gt;. No server processing, no cloud uploads, no data collection. Your photos never leave your device.&lt;/p&gt;

&lt;p&gt;Here's how I built &lt;a href="https://realsmile.online?ref=devto" rel="noopener noreferrer"&gt;RealSmile&lt;/a&gt;, what I learned, and why browser-based AI is the future for privacy-sensitive tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Server-Side Face Analysis
&lt;/h2&gt;

&lt;p&gt;Most face analysis apps follow this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User uploads photo&lt;/li&gt;
&lt;li&gt;Photo sent to server&lt;/li&gt;
&lt;li&gt;Server runs ML model (TensorFlow, PyTorch, etc.)&lt;/li&gt;
&lt;li&gt;Results sent back&lt;/li&gt;
&lt;li&gt;Photo sits on server... forever?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates three problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt; — users don't know what happens to their photos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt; — GPU instances are expensive ($50-500/mo depending on traffic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt; — upload time + processing time + download time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Browser-Based Alternative
&lt;/h2&gt;

&lt;p&gt;What if the AI model ran on the user's device instead?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User uploads photo → Browser loads ML model → Processing on user's CPU/GPU → Results displayed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No round trip. No server. No privacy concerns. And crucially — &lt;strong&gt;no compute costs&lt;/strong&gt; for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;Here's what powers RealSmile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;face-api.js&lt;/strong&gt; (@vladmandic/face-api) — Pre-trained models for face detection, landmark detection, and expression recognition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; — React framework for the app shell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;68-point facial landmark detection&lt;/strong&gt; — Maps facial geometry for proportion analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TinyFaceDetector&lt;/strong&gt; — Lightweight model optimized for browser performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: &lt;code&gt;face-api.js&lt;/code&gt; models are ~5MB total and load from a CDN (jsdelivr). Once loaded, all processing happens in the browser's JavaScript engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading Models Efficiently
&lt;/h2&gt;

&lt;p&gt;The biggest UX challenge is model loading. 5MB is nothing for a server, but noticeable for a first-time user. Here's how I handle it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;modelsLoaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;modelsLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureModels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelsLoaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;// Already loaded, instant&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelsLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Another call is loading, wait for it&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelsLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;modelsLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;faceapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tinyFaceDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MODEL_URL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;faceapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faceLandmark68Net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MODEL_URL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;faceapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faceExpressionNet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MODEL_URL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="nx"&gt;modelsLoaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;modelsLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lazy loading&lt;/strong&gt; — models only load when the user actually uploads a photo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton pattern&lt;/strong&gt; — prevents duplicate loading if multiple components request models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN delivery&lt;/strong&gt; — jsdelivr caches globally, so load times are fast worldwide&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Smile Detection: The Duchenne Method
&lt;/h2&gt;

&lt;p&gt;One of RealSmile's features is detecting whether a smile is genuine. This is based on the Duchenne smile — a 150-year-old discovery that genuine smiles activate muscles around the eyes (orbicularis oculi), not just the mouth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;faceapi&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectSingleFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;faceapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TinyFaceDetectorOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;inputSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scoreThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withFaceLandmarks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withFaceExpressions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expressions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;detection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expressions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;happyScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expressions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;happy&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isGenuine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;happyScore&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expression model returns probability scores for 7 emotions. A high "happy" score combined with landmark analysis around the eye region gives a reasonable approximation of Duchenne smile detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Golden Ratio Face Analysis
&lt;/h2&gt;

&lt;p&gt;The more interesting technical challenge was measuring facial proportions against the golden ratio (φ = 1.618).&lt;/p&gt;

&lt;p&gt;With 68 facial landmarks, you can calculate ratios between key measurements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PHI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.618&lt;/span&gt;

&lt;span class="c1"&gt;// Key landmark points&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faceTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;// Between eyebrows&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;// Bottom of chin&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leftFace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;// Left jawline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rightFace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;// Right jawline&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faceHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;faceTop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faceWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leftFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rightFace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// How close is this ratio to the golden ratio?&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faceHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;faceWidth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;PHI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;PHI&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I measure 6 ratios total: face height/width, eye spacing, mouth/nose ratio, nose width, eye width, and nose shape. The overall score is an average of how close each ratio is to its ideal phi-based value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making It Embeddable
&lt;/h2&gt;

&lt;p&gt;The architecture that makes this free and private also makes it &lt;strong&gt;embeddable&lt;/strong&gt;. Since there's no server dependency, anyone can add the analyzer to their site with one line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://realsmile.online/embed/smile"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"380"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border:none;border-radius:16px;"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The iframe loads a compact version of the analyzer. Models load from jsdelivr's CDN (not my server). Processing happens in the visitor's browser. My server just serves a static page.&lt;/p&gt;

&lt;p&gt;This means even if 10,000 sites embed the widget, my hosting costs stay near zero.&lt;/p&gt;

&lt;p&gt;Check out all 4 embeddable widgets at &lt;a href="https://realsmile.online/widget?ref=devto" rel="noopener noreferrer"&gt;realsmile.online/widget&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Numbers
&lt;/h2&gt;

&lt;p&gt;On a modern laptop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model load&lt;/strong&gt;: ~2-3 seconds (first time), instant after (cached)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Face detection&lt;/strong&gt;: ~33ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full analysis&lt;/strong&gt; (landmarks + expressions): ~80ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total from upload to results&lt;/strong&gt;: &amp;lt; 1 second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On mobile (iPhone 13):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model load&lt;/strong&gt;: ~4-5 seconds first time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full analysis&lt;/strong&gt;: ~150ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfectly usable on both platforms without any server infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;Here's what it costs to run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vercel hosting (static pages)&lt;/td&gt;
&lt;td&gt;$0 (free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jsdelivr CDN (model files)&lt;/td&gt;
&lt;td&gt;$0 (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI processing&lt;/td&gt;
&lt;td&gt;$0 (runs on user's device)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;~$12/year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$1/month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compare that to running TensorFlow Serving on a GPU instance: $50-500/month depending on traffic. The browser-based approach is essentially free at any scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs
&lt;/h2&gt;

&lt;p&gt;It's not all sunshine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model size&lt;/strong&gt; — 5MB is acceptable but not tiny. Users on slow connections notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device dependent&lt;/strong&gt; — Old phones or low-end laptops will be slower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model limitations&lt;/strong&gt; — Browser-compatible models are smaller and less accurate than server-side alternatives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No training&lt;/strong&gt; — You can't collect data to improve models (which is the privacy point, but it limits improvement).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this use case — entertainment-grade face analysis — the tradeoffs are worth it. For medical imaging or security applications, you'd want server-side processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;WebAssembly&lt;/strong&gt; — ONNX Runtime Web or TFLite WASM would be faster than pure JS inference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebGPU&lt;/strong&gt; — When browser support improves, GPU acceleration will close the gap with server-side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive loading&lt;/strong&gt; — Load the smallest model first for instant basic results, then upgrade&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full tools&lt;/strong&gt;: &lt;a href="https://realsmile.online?ref=devto" rel="noopener noreferrer"&gt;realsmile.online&lt;/a&gt; — smile analyzer, golden ratio test, face score, photo ranker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddable widgets&lt;/strong&gt;: &lt;a href="https://realsmile.online/widget?ref=devto" rel="noopener noreferrer"&gt;realsmile.online/widget&lt;/a&gt; — add AI face analysis to your site with one line of code&lt;/li&gt;
&lt;li&gt;All tools are free, no signup required, and 100% private&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're building privacy-sensitive tools, consider browser-based ML. The models are good enough for many use cases, the cost savings are dramatic, and users genuinely appreciate knowing their data stays on their device.&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments!&lt;/p&gt;

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