<?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: Van1s1mys</title>
    <description>The latest articles on DEV Community by Van1s1mys (@ivanmalks).</description>
    <link>https://dev.to/ivanmalks</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%2F3838033%2F3e621376-3cdc-468d-aae0-8d7f88926aa6.jpeg</url>
      <title>DEV Community: Van1s1mys</title>
      <link>https://dev.to/ivanmalks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivanmalks"/>
    <language>en</language>
    <item>
      <title>I Built an AI-Powered Router That Understands What Your Users Mean (Not What They Type)</title>
      <dc:creator>Van1s1mys</dc:creator>
      <pubDate>Sun, 22 Mar 2026 07:00:18 +0000</pubDate>
      <link>https://dev.to/ivanmalks/i-built-an-ai-powered-router-that-understands-what-your-users-mean-not-what-they-type-5ecp</link>
      <guid>https://dev.to/ivanmalks/i-built-an-ai-powered-router-that-understands-what-your-users-mean-not-what-they-type-5ecp</guid>
      <description>&lt;p&gt;What if your search bar could understand "how do I reach support?" and route to &lt;code&gt;/contact&lt;/code&gt; - without a single keyword rule?&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;AI Router&lt;/strong&gt; - an open-source lib that runs a HuggingFace embedding model &lt;strong&gt;inside a Web Worker&lt;/strong&gt; in the browser. No API keys, no backend, no latency. Just semantic understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo&lt;/strong&gt;: &lt;a href="https://ai-router-search.vercel.app" rel="noopener noreferrer"&gt;ai-router-search.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/IvanMalkS/ai-router" rel="noopener noreferrer"&gt;IvanMalkS/ai-router&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Classic search in SPAs is keyword-based. User types "pricing", you match it to &lt;code&gt;/pricing&lt;/code&gt;. Easy.&lt;/p&gt;

&lt;p&gt;But what happens when they type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"how much does it cost?"&lt;/li&gt;
&lt;li&gt;"can I get a free trial?"&lt;/li&gt;
&lt;li&gt;"tariffs and plans"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these have the word "pricing" in them. Keyword search returns nothing. User bounces.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: Embeddings in the Browser
&lt;/h2&gt;

&lt;p&gt;AI Router turns every query into a vector embedding - basically a numerical represantation of its &lt;strong&gt;meaning&lt;/strong&gt;. Then it compares that meaning against your routes using cosine similarity.&lt;/p&gt;

&lt;p&gt;The cool part - the model runs entirely client-side in a Web Worker. Main thread stays free, zero server cost, and it works offline after the first model download (~22 MB, cached).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SmartRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@van1s1mys/ai-router&lt;/span&gt;&lt;span class="dl"&gt;'&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SmartRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Pricing', description: &lt;/span&gt;&lt;span class="dl"&gt;"'&lt;/span&gt;&lt;span class="s1"&gt;cost, plans, subscription&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Contact', description: &lt;/span&gt;&lt;span class="dl"&gt;"'&lt;/span&gt;&lt;span class="s1"&gt;support, phone, address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Docs',    description: &lt;/span&gt;&lt;span class="dl"&gt;"'&lt;/span&gt;&lt;span class="s1"&gt;documentation, API, guides&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// model loads once, cached after&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;how much does it cost?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { path: '/pricing', score: 0.87 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thats it. Five lines of config and your search understands natural language.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Even Handles Typos
&lt;/h2&gt;

&lt;p&gt;Type "pricng" or "contcat" - the router still finds the right page. Embeddings capture meaning at a deeper level than characters, so misspellings dont break anything.&lt;/p&gt;

&lt;p&gt;Try it yourself in the &lt;a href="https://ai-router-search.vercel.app" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; - intentionally make typos and watch it still work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User query
    |
    v
[Web Worker] --&amp;gt; HuggingFace Transformers --&amp;gt; embedding vector
    |
    v
[Orama] --&amp;gt; hybrid search (text + vector) --&amp;gt; ranked results
    |
    v
Best match above threshold --&amp;gt; { path, score }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SmartRouter&lt;/strong&gt; spawns a Web Worker on init&lt;/li&gt;
&lt;li&gt;The worker loads an ONNX model via &lt;code&gt;@huggingface/transformers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your routes get indexed in &lt;a href="https://orama.com" rel="noopener noreferrer"&gt;Orama&lt;/a&gt; - both as text and as vectors&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;search()&lt;/code&gt;, the query gets embedded and compared against all routes&lt;/li&gt;
&lt;li&gt;Best match above threshold is returned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything runs in the worker thread. Main thread only sends and recieves messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive Model Loading
&lt;/h2&gt;

&lt;p&gt;Start fast, get better in the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SmartRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Xenova/all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Xenova/multilingual-e5-small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;onModelUpgrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Upgraded to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// first model ready - search works immediately&lt;/span&gt;
&lt;span class="c1"&gt;// second model loads in the background, seamlessly upgrades&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First model is small and fast. Second one is more accurate. Users get instant results while the better model loads behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Plugins
&lt;/h2&gt;

&lt;p&gt;Dont want to list routes manually? Plugins auto-scan your pages directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Vite / SvelteKit / Vue&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @van1s1mys/ai-router-plugin-vite

&lt;span class="c"&gt;# Next.js&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @van1s1mys/ai-router-plugin-next

&lt;span class="c"&gt;# Webpack / CRA&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @van1s1mys/ai-router-plugin-webpack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;aiRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@van1s1mys/ai-router-plugin-vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;aiRouter&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routes are generated at build time from your file structure. Zero config.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR? Covered
&lt;/h2&gt;

&lt;p&gt;AI Router is SSR-safe out of the box. On the server &lt;code&gt;ready&lt;/code&gt; resolves immediately and &lt;code&gt;search()&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt;. Web Worker only spawns in the browser. Works with Next.js, Nuxt, Remix - whatever you use.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;@huggingface/transformers&lt;/strong&gt; - model inference in the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orama&lt;/strong&gt; - hybrid text + vector search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Workers&lt;/strong&gt; - off-main-thread execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ONNX Runtime&lt;/strong&gt; - model execution engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; - full type safety&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tsup&lt;/strong&gt; - bundling&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @van1s1mys/ai-router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://ai-router-search.vercel.app" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt; | &lt;a href="https://github.com/IvanMalkS/ai-router" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.npmjs.com/package/@van1s1mys/ai-router" rel="noopener noreferrer"&gt;npm&lt;/a&gt; | &lt;a href="https://ivanmalks.github.io/ai-router/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you ever wished your SPA search was smarter - give it a shot. Stars on GitHub are always welcome, and id love to hear feedback or feature ideas in the issues.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Whats your approach to search in SPAs? Ever tried client-side AI? Let me know in the comments&lt;/em&gt;&lt;/p&gt;

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