<?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: Dauren Abasov</title>
    <description>The latest articles on DEV Community by Dauren Abasov (@dadashi44).</description>
    <link>https://dev.to/dadashi44</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%2F4205285%2F02d5dbdc-0b21-4f5f-af6c-e0f692f0cd0b.png</url>
      <title>DEV Community: Dauren Abasov</title>
      <link>https://dev.to/dadashi44</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dadashi44"/>
    <language>en</language>
    <item>
      <title>I built a render debugger for Vue — now it runs on Rust/WASM</title>
      <dc:creator>Dauren Abasov</dc:creator>
      <pubDate>Sat, 24 Oct 2026 18:30:37 +0000</pubDate>
      <link>https://dev.to/dadashi44/i-built-a-render-debugger-for-vue-now-it-runs-on-rustwasm-jhk</link>
      <guid>https://dev.to/dadashi44/i-built-a-render-debugger-for-vue-now-it-runs-on-rustwasm-jhk</guid>
      <description>&lt;p&gt;Vue tells you &lt;em&gt;that&lt;/em&gt; a component re-rendered. It doesn't tell you &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I kept hitting the same problem: a component re-renders, &lt;code&gt;renderTriggered&lt;/code&gt; fires, but the payload says &lt;code&gt;target: Proxy&lt;/code&gt; — useless. Which ref changed? Which prop got a new reference? Was it Pinia? The router? A computed three levels up?&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/dadashi44/vue-why-render" rel="noopener noreferrer"&gt;vue-why-render&lt;/a&gt;&lt;/strong&gt; — a zero-dependency dev tool that names the exact reactive source behind every re-render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&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;scan&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;vue-why-render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every component that re-renders now gets an overlay label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProductCard ×3  1.2 ms
prop badge: {text} → {text} — new reference, same value
💡 Object is rebuilt in parent — hoist to computed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the component name, the render count, the duration, the trigger — and an actionable hint when the render is avoidable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with Vue's built-in hooks
&lt;/h2&gt;

&lt;p&gt;Vue 3 exposes &lt;code&gt;renderTracked&lt;/code&gt; and &lt;code&gt;renderTriggered&lt;/code&gt; on every component. In theory, they tell you what's going on. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;target&lt;/code&gt; is a raw &lt;code&gt;Proxy&lt;/code&gt; — you can't tell which &lt;code&gt;ref&lt;/code&gt; it came from&lt;/li&gt;
&lt;li&gt;There's no diffing — you don't see old vs. new value&lt;/li&gt;
&lt;li&gt;Pinia stores show up as generic reactive objects&lt;/li&gt;
&lt;li&gt;Route changes show the entire &lt;code&gt;$route&lt;/code&gt; object, not which param changed&lt;/li&gt;
&lt;li&gt;There's no way to see the dependency graph across components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;vue-why-render hooks into these same APIs, but resolves the raw proxies back to named sources. When Pinia triggers a render, you see &lt;code&gt;store cart.items: Array(1) → Array(0) · action clear()&lt;/code&gt;. When a route param changes, you see &lt;code&gt;route.params.id: "1" → "2"&lt;/code&gt; — not &lt;code&gt;$route: {...}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the panel
&lt;/h2&gt;

&lt;p&gt;The floating panel has 5 tabs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tab&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Top&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Components sorted by render count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Components sorted by render duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tree&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Component hierarchy with Σ render sums&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chronological log of every render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reactivity graph (new in 0.8)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reactivity graph
&lt;/h2&gt;

&lt;p&gt;The Deps tab is the feature I'm most excited about. It builds a live dependency map: component ↔ reactive source.&lt;/p&gt;

&lt;p&gt;Two views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Components&lt;/strong&gt; → which refs, props, stores, and computeds does this component read?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sources&lt;/strong&gt; → which components depend on this ref?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every edge has a read count and a trigger count. If &lt;code&gt;UserAvatar&lt;/code&gt; reads &lt;code&gt;user.name&lt;/code&gt; 47 times but it only triggered 2 re-renders, you know the other 45 reads were harmless. If &lt;code&gt;CartBadge&lt;/code&gt; gets triggered by &lt;code&gt;cart.items&lt;/code&gt; on every keystroke — that's your problem.&lt;/p&gt;

&lt;p&gt;It's like X-ray for Vue's reactivity system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust/WASM
&lt;/h2&gt;

&lt;p&gt;A real Vue app can have hundreds or thousands of components. Building the dependency tree, deduplicating render causes, and diffing reports across time — on every frame — adds up.&lt;/p&gt;

&lt;p&gt;I rewrote the heavy algorithms in Rust and compiled them to WebAssembly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tree construction&lt;/li&gt;
&lt;li&gt;Cause deduplication&lt;/li&gt;
&lt;li&gt;Report diffing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The WASM module is lazy-loaded as a separate chunk — it doesn't bloat your app bundle. If WASM isn't available (SSR, jsdom, strict CSP), a JS fallback kicks in automatically. No configuration needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  VS Code extension
&lt;/h2&gt;

&lt;p&gt;Browser devtools are great, but I kept switching between Chrome and VS Code. So I built a WebSocket bridge.&lt;/p&gt;

&lt;p&gt;The VS Code extension connects to the scanner running in the browser and shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component tree&lt;/strong&gt; in the sidebar — sorted by renders, with flame icons for hot components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deps tree&lt;/strong&gt; — same dependency graph, but in the editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeLens&lt;/strong&gt; on &lt;code&gt;.vue&lt;/code&gt; files — render count and average duration, right on the component definition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status bar&lt;/strong&gt; — live render counter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt; — pause, resume, reset from the command palette&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No browser tab switching. The data is right where you write code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing renders
&lt;/h2&gt;

&lt;p&gt;The scariest thing about render optimization is regression. You fix a wasted render today, someone adds a prop next week, and it's back.&lt;/p&gt;

&lt;p&gt;vue-why-render ships test utilities:&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;expectRenders&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;vue-why-render/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ProductCard does not re-render on unrelated state change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... mount component, trigger action&lt;/span&gt;
  &lt;span class="nf"&gt;expectRenders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProductCard&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adding to cart&lt;/span&gt;&lt;span class="dl"&gt;'&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;This runs in Vitest / Jest. If &lt;code&gt;ProductCard&lt;/code&gt; re-renders when it shouldn't, the test fails — with the exact trigger in the error message. The fix stays fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render budgets
&lt;/h2&gt;

&lt;p&gt;For runtime monitoring, you can set budgets:&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="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxRenders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// per 5-second window&lt;/span&gt;
  &lt;span class="na"&gt;maxDuration&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;// ms per update&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Violations show up in the console, the panel, and the reports. Useful for catching regressions in staging before they hit production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reports
&lt;/h2&gt;

&lt;p&gt;Export render data as JSON or Markdown:&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;report&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;vue-why-render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// structured data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// ready for GitHub issues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can diff two reports to catch regressions between builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt support
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&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;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modules&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;vue-why-render/nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&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;Listed on &lt;a href="https://nuxt.com/modules/vue-why-render" rel="noopener noreferrer"&gt;nuxt.com/modules&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo&lt;/strong&gt;: &lt;a href="https://dadashi44.github.io/vue-why-render/" rel="noopener noreferrer"&gt;dadashi44.github.io/vue-why-render&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/dadashi44/vue-why-render" rel="noopener noreferrer"&gt;github.com/dadashi44/vue-why-render&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install&lt;/strong&gt;: &lt;code&gt;npm i vue-why-render&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero runtime dependencies. MIT license. Vue 3.3+.&lt;/p&gt;

&lt;p&gt;If you find it useful — a ⭐ on GitHub genuinely helps with discoverability. And if you hit a bug or want a feature, &lt;a href="https://github.com/dadashi44/vue-why-render/issues" rel="noopener noreferrer"&gt;issues are open&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>webassembly</category>
      <category>rust</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
