<?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: Pritam Patra</title>
    <description>The latest articles on DEV Community by Pritam Patra (@pritam_patra_429a25dedae6).</description>
    <link>https://dev.to/pritam_patra_429a25dedae6</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%2F4024964%2F55fd503a-2224-4f8e-8a04-bdce433dddf9.jpg</url>
      <title>DEV Community: Pritam Patra</title>
      <link>https://dev.to/pritam_patra_429a25dedae6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pritam_patra_429a25dedae6"/>
    <language>en</language>
    <item>
      <title>Your Policies Are Out of Date: How I Built a Sanity AI Agent to Catch Fact Drift</title>
      <dc:creator>Pritam Patra</dc:creator>
      <pubDate>Sat, 03 Oct 2026 21:21:17 +0000</pubDate>
      <link>https://dev.to/pritam_patra_429a25dedae6/your-policies-are-out-of-date-how-i-built-a-sanity-ai-agent-to-catch-fact-drift-5bee</link>
      <guid>https://dev.to/pritam_patra_429a25dedae6/your-policies-are-out-of-date-how-i-built-a-sanity-ai-agent-to-catch-fact-drift-5bee</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl9gc4f7b60xx1mcjavzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl9gc4f7b60xx1mcjavzb.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fact Ledger&lt;/strong&gt; — an AI-assisted fact drift detection and remediation engine built on Sanity Content Lake.&lt;/p&gt;

&lt;p&gt;Here's the problem it solves, told as a scene:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A product manager at a SaaS company decides to extend refunds from 30 days to 60 days. She opens the CMS, finds the Refund Policy page, updates it, clicks publish. Done. She sends the Slack message: &lt;em&gt;"Refund window is now 60 days, effective immediately."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What she doesn't know: the Help Center article still says 30. The Pricing FAQ still says 30. The Onboarding Guide, the Terms of Service, the Enterprise SLA page, the Checkout confirmation modal copy — all still say 30. Six weeks later, a customer disputes a charge, screenshots the Pricing FAQ, and sends it to their lawyer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is &lt;strong&gt;Fact Drift&lt;/strong&gt; — and it happens quietly, constantly, in every company with more than a handful of content pages and more than one person editing them. It's not a CMS problem, it's a &lt;em&gt;structural&lt;/em&gt; problem. The number "30" is stored in twenty-three places as dead characters. There's no relationship between them. When one changes, the others don't know.&lt;/p&gt;

&lt;p&gt;Fact Ledger fixes this at the data model level.&lt;/p&gt;

&lt;p&gt;Business values — refund windows, SLA commitments, file size limits, pricing tiers, data retention periods — become &lt;strong&gt;first-class Sanity documents&lt;/strong&gt; called &lt;code&gt;fact&lt;/code&gt;s. Pages don't copy those values; they &lt;em&gt;reference&lt;/em&gt; them via a custom &lt;code&gt;factRef&lt;/code&gt; inline Portable Text annotation. When the fact document changes, every page that uses a &lt;code&gt;factRef&lt;/code&gt; renders the new value instantly, automatically, with zero editor intervention.&lt;/p&gt;

&lt;p&gt;For every page that still has the hardcoded plain text — either because it predates the system or because someone didn't know — a deterministic scanner runs on every webhook event, finds the stale copies, and raises structured &lt;code&gt;finding&lt;/code&gt; documents. An AI agent drafts the exact Sanity patch mutations to fix them. A human reviews the before/after diffs and clicks one button. Everything updates atomically in a single transaction. The audit ledger captures the whole chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The core principle, distilled to four words per step:&lt;/strong&gt; &lt;em&gt;Rules flag. AI drafts. Human approves. Sanity remembers.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fact Edit → Webhook → Deterministic Scanner → Finding Graph
         → AI Mutation Synthesis → Draft Release → Human Approval → Zero Drift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The 5 Scanner Rules (Zero AI Involved)
&lt;/h3&gt;

&lt;p&gt;This is where the design gets interesting. The scanner is &lt;strong&gt;pure deterministic TypeScript&lt;/strong&gt; — no model calls, no embeddings, no fuzzy matching libraries. It runs in milliseconds and produces findings with exact block keys and character offsets.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R1 — Unlinked Match&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plain text &lt;code&gt;"30 days"&lt;/code&gt; where a &lt;code&gt;factRef&lt;/code&gt; should be&lt;/td&gt;
&lt;td&gt;The most common drift; editors hardcode values without realising&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R2 — Contradiction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text says &lt;code&gt;"60 days"&lt;/code&gt; while the canonical fact says &lt;code&gt;30&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Catches the pages that were &lt;em&gt;partially&lt;/em&gt; updated manually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R3 — Deprecated Reference&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;factRef&lt;/code&gt; pointing to a fact marked &lt;code&gt;deprecated&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Catches linked pages when a fact is retired, not just changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R4 — Orphan Fact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An active fact exists in Sanity but zero pages reference it&lt;/td&gt;
&lt;td&gt;Surfaces forgotten policies that have drifted into irrelevance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;R5 — Temporal Violation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;factRef&lt;/code&gt; pointing to a fact outside its &lt;code&gt;effectiveFrom/Until&lt;/code&gt; range&lt;/td&gt;
&lt;td&gt;Catches seasonal pricing or time-bound policies used out of window&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run the benchmark against the seeded dataset of 31 planted violations: &lt;strong&gt;100% precision, 100% recall&lt;/strong&gt;. Every planted issue found, zero false positives. That's not a marketing number — the &lt;code&gt;seed/groundTruth.ts&lt;/code&gt; file has the exact expected findings per page, and the benchmark runner verifies against them on every run.&lt;/p&gt;

&lt;p&gt;The precision comes from a design decision in R2: the &lt;strong&gt;proximity window&lt;/strong&gt;. Instead of flagging any number that contradicts the fact value anywhere on the page (which would catch phone numbers, copyright years, and table row counts), R2 only flags numbers that appear within N characters of the fact's &lt;em&gt;label&lt;/em&gt; in the text. That single detail is the difference between a rule that's useful and one that's noise.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Workflow
&lt;/h3&gt;

&lt;p&gt;Once findings exist in Sanity, the AI remediation agent kicks in. It fetches the page's Portable Text, locates the exact block and character span identified by the scanner, slices out the stale text, and generates a valid Sanity patch mutation — the kind you'd write by hand if you were doing this manually, except it does it for twenty pages in the time it takes to refresh the dashboard.&lt;/p&gt;

&lt;p&gt;All of this goes into a &lt;code&gt;remediation&lt;/code&gt; document: a structured release with a &lt;code&gt;fixes&lt;/code&gt; array where each item carries &lt;code&gt;beforeText&lt;/code&gt;, &lt;code&gt;afterText&lt;/code&gt;, the raw patch JSON, and an &lt;code&gt;approved&lt;/code&gt; boolean the editor can toggle.&lt;/p&gt;

&lt;p&gt;In Sanity Studio, there's a custom &lt;strong&gt;"Apply Fixes &amp;amp; Publish"&lt;/strong&gt; document action on the &lt;code&gt;remediation&lt;/code&gt; type. The editor reads through the diffs. They can approve or reject individual fixes. When they're satisfied, one click fires a &lt;code&gt;client.transaction()&lt;/code&gt; that atomically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;patches every affected page&lt;/li&gt;
&lt;li&gt;flips every linked finding to &lt;code&gt;fixed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;publishes the remediation document itself&lt;/li&gt;
&lt;li&gt;writes an immutable &lt;code&gt;changeEvent&lt;/code&gt; to the audit ledger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No partial states. No "we fixed 17 of 21 pages and then the tab closed." All twenty-one pages update in the same transaction or none of them do. The Drift Score on the dashboard drops back to zero and stays there — until the next fact changes.&lt;/p&gt;


&lt;h3&gt;
  
  
  🌟 Key Features (The App SDK Experience)
&lt;/h3&gt;

&lt;p&gt;The Next.js dashboard at &lt;code&gt;https://fact-ledger.onrender.com/dashboard&lt;/code&gt; is a live, interactive layer on top of Content Lake. It isn't just a read-only frontend — it's a full &lt;strong&gt;App SDK Control Center&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Clause Impact Tree&lt;/strong&gt;&lt;br&gt;
When a policy changes, officials don't just see a list of broken pages. They see a &lt;strong&gt;cascading change graph&lt;/strong&gt;. The Clause Impact Tree visualizes exactly how a single canonical fact ripples outward across the entire corporate document graph, showing nodes turning red in real-time as the webhook fires.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9d761e2148rf8lsykuj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9d761e2148rf8lsykuj3.png" alt=" " width="799" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Live Control Center &amp;amp; Charts&lt;/strong&gt;&lt;br&gt;
Every number and graph on the dashboard comes from a live GROQ query. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The KPI Row:&lt;/strong&gt; Tracks current Drift Score, total facts, scanned pages, and overall health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolution Health Chart:&lt;/strong&gt; A before-vs-after breakdown showing open findings vs fixed findings across releases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Heatmap:&lt;/strong&gt; A CSS grid visualizing Fact × Page drift anomalies. Red for drift, green for clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Triage:&lt;/strong&gt; Editors can approve or dismiss findings directly from the dashboard table. The API route patches the finding document in Sanity immediately, and the table re-queries live without reloading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdrqcnnveovq5bt3vs9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdrqcnnveovq5bt3vs9s.png" alt=" " width="799" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The Employee Voice Portal&lt;/strong&gt;&lt;br&gt;
Policy confusion doesn't only flow downward. Employees and customers notice stale policies first. The &lt;strong&gt;Employee Voice&lt;/strong&gt; portal gives that signal a home. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Employees can submit policy complaints or questions.&lt;/li&gt;
&lt;li&gt;These submissions are stored as &lt;code&gt;complaint&lt;/code&gt; or &lt;code&gt;policyQuestion&lt;/code&gt; documents directly in Sanity.&lt;/li&gt;
&lt;li&gt;Officials see every submission in Sanity Studio right next to the open findings. When an employee asks, &lt;em&gt;"Why does the checkout page say 30 days but I was told 60?"&lt;/em&gt;, the complaint lives right next to the scanner finding that explains exactly why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi635q6h78ah03xe9y4tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi635q6h78ah03xe9y4tn.png" alt=" " width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Z-wFpxGywfg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Live Dashboard:&lt;/strong&gt; &lt;code&gt;https://fact-ledger.onrender.com/dashboard&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Sanity Studio:&lt;/strong&gt; &lt;code&gt;https://pritam.sanity.studio/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  The Full Loop in 60 Seconds
&lt;/h3&gt;

&lt;p&gt;Here is how the system actually looks in practice when a fact changes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Trigger&lt;/strong&gt;&lt;br&gt;
An editor changes the &lt;code&gt;Cancellation Policy&lt;/code&gt; fact value from &lt;code&gt;60&lt;/code&gt; to &lt;code&gt;30&lt;/code&gt; in Sanity Studio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7f5f30gy4chwfvwy5ye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw7f5f30gy4chwfvwy5ye.png" alt=" " width="799" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Detection&lt;/strong&gt;&lt;br&gt;
The webhook fires. The scanner runs R1–R5 across all pages. The dashboard Drift Score jumps and the KPI pulses red.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco5pwgbb4mn0pk09fbwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fco5pwgbb4mn0pk09fbwt.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvx2omow2zqkfie6g98i9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvx2omow2zqkfie6g98i9.png" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The Remediation Draft&lt;/strong&gt;&lt;br&gt;
Clicking "Draft AI Remediation" triggers the AI to draft exact before/after patches for all stale spans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj51v75l65imkc3h96it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj51v75l65imkc3h96it.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Human Approval&lt;/strong&gt;&lt;br&gt;
The editor opens the Remediation Release in Studio, reads through the diffs, and clicks "Apply Fixes &amp;amp; Publish".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsbxon4lqht2f1xdc1a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsbxon4lqht2f1xdc1a1.png" alt=" " width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8gmvo7r1ri0lajtri0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8gmvo7r1ri0lajtri0e.png" alt=" " width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The Resolution&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;client.transaction()&lt;/code&gt; commits. All pages are patched atomically, findings flip to fixed, and the Drift Score returns to 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwn5tv2j5jpzy300a33n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwn5tv2j5jpzy300a33n2.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Pritam-mb" rel="noopener noreferrer"&gt;
        Pritam-mb
      &lt;/a&gt; / &lt;a href="https://github.com/Pritam-mb/sanity" rel="noopener noreferrer"&gt;
        sanity
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Pritam-mb/sanity/./docs/Screenshot%202026-10-04%20014137.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPritam-mb%2Fsanity%2FHEAD%2F.%2Fdocs%2FScreenshot%25202026-10-04%2520014137.png" alt="Fact Ledger Hero" width="100%"&gt;&lt;/a&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;p&gt;
    &lt;b&gt;An AI-assisted fact drift detection engine built on Sanity Content Lake.&lt;/b&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a href="https://fact-ledger.onrender.com" rel="nofollow noopener noreferrer"&gt;Live Demo&lt;/a&gt; •
    &lt;a href="https://pritam.sanity.studio" rel="nofollow noopener noreferrer"&gt;Sanity Studio&lt;/a&gt; •
    &lt;a href="https://github.com/Pritam-mb/sanity#setup" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛑 The Problem: Fact Drift&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;A product manager at a SaaS company decides to extend refunds from 30 days to 60 days. She opens the CMS, updates the Refund Policy page, and clicks publish. What she doesn't know: the Help Center article still says 30. The Pricing FAQ still says 30. The Onboarding Guide, the Terms of Service, the Enterprise SLA page, the Checkout confirmation modal copy — &lt;strong&gt;all still say 30.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is Fact Drift.&lt;/strong&gt; The number "30" is stored in twenty-three places as dead characters. There's no relationship between them. When one changes, the others don't know.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;💡 The Solution: Fact Ledger&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Fact Ledger fixes this at the data model level. Business values become &lt;strong&gt;first-class Sanity documents&lt;/strong&gt; (&lt;code&gt;facts&lt;/code&gt;). Pages don't copy those values; they reference them.&lt;/p&gt;

&lt;p&gt;For every…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Pritam-mb/sanity" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fact-ledger/
├── studio/     # Sanity Studio v3 — 13 schema types, custom structure, Document Action
├── web/        # Next.js 16 App Router — Drift Dashboard + API routes
├── scanner/    # Pure TypeScript R1–R5 + Vitest unit tests
├── seed/       # Idempotent seed script (facts, pages, ground truth)
└── bench/      # Benchmark runner — writes benchmarkResult docs to Sanity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Sanity Project ID:&lt;/strong&gt; &lt;code&gt;tmics7hc&lt;/code&gt; (dataset: &lt;code&gt;fact-ledger&lt;/code&gt;)&lt;/p&gt;


&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;I used &lt;strong&gt;Antigravity IDE&lt;/strong&gt; (Google DeepMind) as my primary AI-native IDE, with its browser agent running alongside for live validation and UI feedback during development.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it started
&lt;/h3&gt;

&lt;p&gt;I came into this with a real problem in mind — not a contrived demo. The scenario of "fact changes in one place, stays stale everywhere else" has bitten real teams I've worked with. I wanted to see if a vibe-coded system could actually address it with structural rigor, not just vibes.&lt;/p&gt;

&lt;p&gt;My opening prompt was deliberately ambitious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Build a closed-loop fact drift detection system on Sanity. Facts are first-class documents. Pages reference them with custom Portable Text inline objects. A deterministic scanner runs on webhook triggers and creates finding documents. An AI agent synthesizes patch mutations. A human approves via a custom Studio document action. Everything is audited. The LLM never publishes anything."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single prompt produced the full schema architecture, the &lt;code&gt;factRef&lt;/code&gt; Portable Text annotation type, and an initial scanner skeleton in one pass. What struck me: the model correctly inferred the key constraint on its own — &lt;em&gt;no unsupervised publishing&lt;/em&gt; — and every design decision that followed was shaped by that constraint without me having to repeat it. It understood why the LLM's role should be limited to drafting, not deciding.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prompts that worked
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Schema design:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Design a &lt;code&gt;fact&lt;/code&gt; schema for Sanity with: key (slug), label, value, unit, aliases (array of strings for surface-form matching), owner (reference to person), dependsOn (self-referential array), effectiveFrom/Until (dates), status (active/deprecated), and a highStakes flag."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;aliases&lt;/code&gt; field is what makes the whole system actually useful in practice. Without it, the scanner could only catch exact matches like &lt;code&gt;"30 days"&lt;/code&gt;. With it, each fact document carries its own list of surface forms: &lt;code&gt;["30 days", "thirty (30) days", "one month", "30 calendar days"]&lt;/code&gt;. R1 matches any of them. That's how you catch the policy writer who wrote "one month" in the terms and the marketing person who wrote "thirty days" in the campaign landing page — same fact, same finding, zero NLP required.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;highStakes&lt;/code&gt; flag is a quiet but important detail: facts marked high-stakes require two human approvers in the remediation workflow. One person can't unilaterally ship a fix to the refund policy affecting twenty pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanner architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Write the 5 scanner rules as pure TypeScript functions. Zero dependencies. Zero AI. Take facts and Portable Text blocks as input, return findings with exact block key, child key, start offset, and end offset."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model got genuinely clever here. For R2 (contradiction detection), it implemented a &lt;strong&gt;proximity window&lt;/strong&gt; — a contradictory number only fires as a finding if it appears within N characters of the fact's label in the text body. That design decision alone is what separates a useful rule from a useless one. A naive implementation would flag every number on every page. This one only flags the ones that are contextually near the fact's label, which is where contradictions actually matter.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;npm run bench&lt;/code&gt; against the 31 planted issues: &lt;strong&gt;31 found, 0 false positives&lt;/strong&gt;. That's a real validation, not a demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The custom document action:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Write a Sanity Studio document action for the remediation type. Label: 'Apply Fixes &amp;amp; Publish'. Filter for approved=true fixes, parse each mutation JSON, build a client.transaction() that patches every page, flips findings to fixed, publishes the remediation doc, writes a changeEvent. Atomic commit."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This came out nearly correct on the first pass. The model chose &lt;code&gt;client.transaction()&lt;/code&gt; without prompting — it understood on its own that sequential patches would create a window where some pages are fixed and others aren't. That matters: if the tab closes halfway through twenty patches, you're left with a half-consistent dataset and no way to know which pages were updated. A transaction is all-or-nothing. The model knew that.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prompts that didn't work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sanity Functions as the webhook handler.&lt;/strong&gt; The model scaffolded a genuinely clean Sanity Function — right structure, right exports, correct handler signature. Then I went to deploy it and hit the plan wall: Sanity Functions require a paid upgrade. Beautiful code, zero utility at my tier. The fallback was a Next.js API route, which actually turned out cleaner: scanner and webhook in the same process, easier to debug, no cold start latency.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;em&gt;beautiful code requiring a plan upgrade isn't code you can ship.&lt;/em&gt; We fell back, and the fallback shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-generating seed data.&lt;/strong&gt; My first two attempts at generating realistic policy pages with planted drift violations produced pages where every violation was placed inside a heading element. Headings in Portable Text don't have child span arrays the way body blocks do — so the scanner couldn't see them. Completely valid Sanity documents, completely invisible to R1–R4.&lt;/p&gt;

&lt;p&gt;The fix was to be brutally explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"All planted violations must be inside Portable Text body blocks with at least 2 child spans. Include exact byte offsets in the ground truth file so the benchmark can verify TP/FP/FN."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After that, &lt;code&gt;seed/groundTruth.ts&lt;/code&gt; became the source of truth for the benchmark. Every time you run &lt;code&gt;npm run bench&lt;/code&gt;, it checks the scanner's output against those exact expected findings. If someone breaks a rule, the benchmark fails — not gracefully, loudly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The heatmap.&lt;/strong&gt; Describing the Fact × Page heatmap in prose got me a Recharts cell chart with tiles so small you needed a magnifying glass. The breakthrough was switching from describing &lt;em&gt;what it should look like&lt;/em&gt; to describing &lt;em&gt;how it should be built&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Implement this as a CSS grid. Rows = facts, columns = pages. Each cell: red background if open findings exist for that combo, green if clean. No charting library — plain CSS grid. Title attributes for hover tooltips."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One pass, done. The lesson: when the output is wrong, don't describe the outcome differently — describe the implementation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reaching past the Studio
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflows as data.&lt;/strong&gt; The &lt;code&gt;remediation&lt;/code&gt; schema &lt;em&gt;is&lt;/em&gt; the workflow document. Status transitions: &lt;code&gt;draft → pending_review → approved → published&lt;/code&gt;. Each transition is a Sanity patch. Every state is queryable in GROQ. The AI agent advances the document to &lt;code&gt;pending_review&lt;/code&gt;. A human editor reviews and advances to &lt;code&gt;published&lt;/code&gt; via the custom document action. If they're not ready, the document sits at &lt;code&gt;pending_review&lt;/code&gt; indefinitely — visible in the Studio sidebar under "Remediation Releases" with a running count of unapproved fixes. Nothing gets lost. Nothing gets auto-published.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real-time custom interface.&lt;/strong&gt; The dashboard is not read-only. Editors can approve or dismiss individual findings directly from the &lt;code&gt;/findings&lt;/code&gt; table — the action patches Sanity and the table refreshes with the new state. The Drift Score pulses visually when non-zero. It's a small UX decision, but it matters: you want people to &lt;em&gt;feel&lt;/em&gt; the urgency of a non-zero drift score, not just read a number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Employee Voice portal.&lt;/strong&gt; A late addition in Session 3, prompted into existence in a single session. Policy confusion doesn't only flow downward — employees and customers notice it first and report it through informal channels (Slack, email, support tickets) that never get structured or tracked. The portal gives that signal a home in the same Content Lake. A complaint about a confusing refund promise routes directly into a &lt;code&gt;complaint&lt;/code&gt; document in Sanity; the editor sees it in Studio next to the open findings for that fact. The problem and its reports live in the same place.&lt;/p&gt;

&lt;p&gt;Three schema types, three Studio sidebar sections, five Next.js routes — roughly 45 minutes from prompt to running.&lt;/p&gt;
&lt;h3&gt;
  
  
  What surprised me
&lt;/h3&gt;

&lt;p&gt;The model never once suggested putting the LLM in the approval loop. Even when I described the remediation flow in ways that left room for autonomous action, it consistently added a &lt;code&gt;canPublish&lt;/code&gt; guard, an &lt;code&gt;approved&lt;/code&gt; boolean per fix, and explicit before/after text fields for human review. The safety architecture emerged from the model's understanding of the design, not from me specifying every guardrail.&lt;/p&gt;

&lt;p&gt;The GROQ queries were also clean on first generation — almost no iteration needed. GROQ's declarative shape maps well onto how the model reasons about data: you describe the shape you want, not the traversal you'd write in SQL. When the output is wrong in SQL, you debug joins. When GROQ output is wrong, you usually just re-describe the shape. The model seems to prefer describing shapes.&lt;/p&gt;

&lt;p&gt;The most important moment: the model wrote an elegant Sanity Function. I had to explain that elegance and deployability aren't the same thing. The fallback shipped. The elegant version sits in a comment block as a note for when the plan gets upgraded.&lt;/p&gt;

&lt;p&gt;That's the honest build: seven and a half hours, a real problem, a model that got most of it right, a few walls we had to route around, and a system that actually works.&lt;/p&gt;


&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;tmics7hc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;fact-ledger&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema types:&lt;/strong&gt; &lt;code&gt;fact&lt;/code&gt;, &lt;code&gt;factRef&lt;/code&gt; (custom Portable Text annotation), &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;finding&lt;/code&gt;, &lt;code&gt;scanRun&lt;/code&gt;, &lt;code&gt;remediation&lt;/code&gt;, &lt;code&gt;changeEvent&lt;/code&gt;, &lt;code&gt;benchmarkResult&lt;/code&gt;, &lt;code&gt;complaint&lt;/code&gt;, &lt;code&gt;policyQuestion&lt;/code&gt;, &lt;code&gt;policyUpdate&lt;/code&gt;, &lt;code&gt;person&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Studio features:&lt;/strong&gt; custom desk structure (Facts / Pages / Open Findings / Remediation Releases / Employee Voice / Audit Log), custom document action "Apply Fixes &amp;amp; Publish" (&lt;code&gt;PublishRemediationAction.ts&lt;/code&gt;), visionTool for GROQ exploration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks:&lt;/strong&gt; fact and page publish events trigger &lt;code&gt;/api/webhook&lt;/code&gt; → scanner → finding creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions:&lt;/strong&gt; remediation publishing uses &lt;code&gt;client.transaction()&lt;/code&gt; for atomic multi-document patches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeGen:&lt;/strong&gt; &lt;code&gt;sanity typegen generate&lt;/code&gt; in &lt;code&gt;studio/&lt;/code&gt; — all GROQ queries are fully typed&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb5rrbuvyyjjj9mexhfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb5rrbuvyyjjj9mexhfo.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;Built with &lt;strong&gt;Antigravity IDE&lt;/strong&gt; (Google DeepMind) over three sessions, roughly 7.5 hours total.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session 1&lt;/strong&gt; (~2.5 hrs): schema design, &lt;code&gt;factRef&lt;/code&gt; annotation, scanner R1–R5, seed script&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session 2&lt;/strong&gt; (~3 hrs): dashboard panels P1–P6, API routes, webhook handler, benchmark runner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session 3&lt;/strong&gt; (~2 hrs): Studio structure, "Apply Fixes &amp;amp; Publish" action, Employee Voice portal, audit ledger, polish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model wrote the large majority of the TypeScript. My role was architect, reviewer, and the person who hit pricing walls so you don't have to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvm9fvmw3y10d9wuaiiy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvm9fvmw3y10d9wuaiiy.png" alt=" " width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Pritam-mb" rel="noopener noreferrer"&gt;
        Pritam-mb
      &lt;/a&gt; / &lt;a href="https://github.com/Pritam-mb/sanity" rel="noopener noreferrer"&gt;
        sanity
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Pritam-mb/sanity/./docs/Screenshot%202026-10-04%20014137.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPritam-mb%2Fsanity%2FHEAD%2F.%2Fdocs%2FScreenshot%25202026-10-04%2520014137.png" alt="Fact Ledger Hero" width="100%"&gt;&lt;/a&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;p&gt;
    &lt;b&gt;An AI-assisted fact drift detection engine built on Sanity Content Lake.&lt;/b&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a href="https://fact-ledger.onrender.com" rel="nofollow noopener noreferrer"&gt;Live Demo&lt;/a&gt; •
    &lt;a href="https://pritam.sanity.studio" rel="nofollow noopener noreferrer"&gt;Sanity Studio&lt;/a&gt; •
    &lt;a href="https://github.com/Pritam-mb/sanity#setup" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛑 The Problem: Fact Drift&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;A product manager at a SaaS company decides to extend refunds from 30 days to 60 days. She opens the CMS, updates the Refund Policy page, and clicks publish. What she doesn't know: the Help Center article still says 30. The Pricing FAQ still says 30. The Onboarding Guide, the Terms of Service, the Enterprise SLA page, the Checkout confirmation modal copy — &lt;strong&gt;all still say 30.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is Fact Drift.&lt;/strong&gt; The number "30" is stored in twenty-three places as dead characters. There's no relationship between them. When one changes, the others don't know.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;💡 The Solution: Fact Ledger&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Fact Ledger fixes this at the data model level. Business values become &lt;strong&gt;first-class Sanity documents&lt;/strong&gt; (&lt;code&gt;facts&lt;/code&gt;). Pages don't copy those values; they reference them.&lt;/p&gt;

&lt;p&gt;For every…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Pritam-mb/sanity" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  👥 The Team
&lt;/h2&gt;

&lt;p&gt;Our team came together for the International Charity Day DEV Challenge to build NeedFeed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to"&gt;@pritam_patra_429a25dedae6&lt;/a&gt; (Lead Developer)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/RexBytes" rel="noopener noreferrer"&gt;@t-rexbytes&lt;/a&gt; (Collaborator)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>OmniML: We Built an AutoML System That Refuses to Be a Black Box</title>
      <dc:creator>Pritam Patra</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:17:49 +0000</pubDate>
      <link>https://dev.to/pritam_patra_429a25dedae6/omniml-human-in-the-loop-automl-with-langgraph-groq-and-chainlit-3g36</link>
      <guid>https://dev.to/pritam_patra_429a25dedae6/omniml-human-in-the-loop-automl-with-langgraph-groq-and-chainlit-3g36</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/mlh-hackathon"&gt;MLH x DEV Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  OmniML: An AutoML Pipeline That Actually Asks Before It Decides
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most AutoML tools operate as complete black boxes. You feed in a dataset, a brute-force search algorithm churns away behind closed doors, and out comes a model with zero explanation, no room for expert intervention, and no consideration for regulatory standards like the EU AI Act or FDA Software-as-a-Medical-Device (SaMD) guidelines. That opacity is exactly what my team set out to fix.&lt;/p&gt;

&lt;p&gt;At HackTropica, we built &lt;strong&gt;OmniML&lt;/strong&gt; — an open-source, full-stack, autonomous machine learning system that keeps a human explicitly in the loop at every critical decision point. Orchestrated with LangGraph, powered by Groq's &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt; for fast reasoning, and wrapped in an interactive Chainlit UI, OmniML turns a plain-English request into a fully trained, compliance-checked, and deployment-ready ML model, sourcing real datasets from Kaggle along the way.&lt;/p&gt;

&lt;p&gt;The pipeline runs through a chain of dedicated stages, and at five separate checkpoints it pauses and hands control back to the user before proceeding. The architect agent first proposes a baseline model graph from the problem statement, which the user can then visually edit through an embedded React Flow canvas before anything is compiled. From there, the system searches, ranks, and downloads a real tabular dataset via the Kaggle API, profiles it for feature distributions and data quality, and surfaces that analysis for review. The user then configures hyperparameters, fairness settings, and compliance mode, and makes an explicit choice between local and cloud compute before training is ever allowed to start. Once training resumes, an engineer and debugger agent pair generates deterministic training code, validates it, and executes it in a sandboxed environment while streaming real-time logs and metrics back to the UI. The final stage produces SHAP and LIME explainability outputs, fairness benchmarks, and a full compliance report formatted against EU AI Act and FDA SaMD guidance, alongside a production-ready deployment bundle.&lt;/p&gt;

&lt;p&gt;One design decision I'm particularly proud of is supporting two distinct training paths depending on the problem. Path B, which is the default, uses scikit-learn for fast tabular, text, or image training via grid search over a featurized CSV. Path A compiles the user-approved React Flow graph directly into a custom &lt;code&gt;OmniMLNet&lt;/code&gt; PyTorch module and trains it with real epoch-level metrics, which is useful when someone actually wants to reason about network architecture rather than just get a fast baseline. OmniML also grounds its architecture decisions in real research: before training begins, an agent pulls relevant literature and produces a gap-analysis comparing the current dataset, task, and proposed architecture against published methodologies, so the user can see exactly where their setup diverges from established approaches in the field. Every run is also fully reproducible: each execution persists to &lt;code&gt;runs/&amp;lt;run_id&amp;gt;/manifest.json&lt;/code&gt; as a complete evidence bundle covering artifacts, plots, logs, and reports, all downloadable and normalized for later benchmarking.&lt;/p&gt;

&lt;p&gt;Here's the high-level flow end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
  └─► Architecture generation
        └─► HITL architecture editor (React Flow)
              └─► Dataset sourcing and ranking (Kaggle)
                    └─► HITL dataset selection
                          └─► Dataset download + validation
                                └─► Drift sentry
                                      └─► EDA profiling
                                            └─► HITL training configuration
                                                  └─► HITL compute strategy
                                                        └─► Hyperparameter tuning + deterministic code generation
                                                              └─► Code validation &amp;amp; self-healing
                                                                    └─► Execution sandbox
                                                                          └─► XAI / benchmark / fairness / compliance
                                                                                └─► Deployment dashboard + downloadable reports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I learned building this had less to do with the ML itself and more to do with orchestration. Getting an LLM-driven agent graph to pause reliably at a UI checkpoint, hold state correctly, and resume exactly where it left off — without losing context or re-triggering earlier stages — turned out to be the hardest engineering problem in the whole project, harder than anything on the modeling side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's the full walkthrough of OmniML running end to end, including the interactive HITL graph editor, live dataset profiling, and compliance report generation:&lt;/p&gt;

&lt;h2&gt;
  
  
  🎬 Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/dNsuma_PQ9g" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
&lt;strong&gt;🔗 GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/hacktropica" rel="noopener noreferrer"&gt;https://github.com/Pritam-mb/hacktropica&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Architecture Editor (HITL Checkpoint)
&lt;/h3&gt;

&lt;p&gt;The embedded React Flow visual editor inside Chainlit lets you visually refine a proposed neural network topology before it's compiled into a trainable model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bkl533mx9kkdgos1ugu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bkl533mx9kkdgos1ugu.png" alt=" " width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline Walkthrough in Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. EDA Profiling Dashboard.&lt;/strong&gt; After a dataset is sourced from Kaggle, OmniML runs it through an automated profiling stage that surfaces missing values, feature distributions with skewness stats, and categorical feature breakdowns — all before the user is asked to approve moving forward:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuo3v7tclhdjizluajito.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuo3v7tclhdjizluajito.png" alt=" " width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Outlier Detection, Correlation Analysis &amp;amp; AI Data Insights.&lt;/strong&gt; The same EDA stage flags outliers using IQR fencing, ranks the strongest feature-to-feature correlations, renders a full correlation heatmap, and closes with a natural-language summary generated by Groq — explaining data quality issues and how the dataset maps to its likely source (in this run, correctly identifying it as the Wisconsin Breast Cancer Diagnostic dataset):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzppkglbe0vlb9d56vj4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzppkglbe0vlb9d56vj4x.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Training Configuration (HITL Checkpoint).&lt;/strong&gt; Before any training runs, the user is handed an interactive configuration panel to set epochs, test split, batch size, learning rate, optimizer, and regularization settings — with quick presets for fast debug runs versus deep, balanced, or overfit-test configurations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh14hen5rmbcs163twfqg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh14hen5rmbcs163twfqg.png" alt=" " width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Live Training Progress.&lt;/strong&gt; Once training is launched, OmniML streams real epoch metrics back into the chat as live loss and accuracy curves, alongside a running Groq-generated observation of what's happening in the training dynamics — in this case flagging a plateau in validation loss and suggesting the learning rate may be too high for further fine-tuning:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpt192ae4o23mztjbamg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpt192ae4o23mztjbamg4.png" alt=" " width="800" height="921"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Literature Comparison &amp;amp; Gap Analysis.&lt;/strong&gt; Before the pipeline finalizes an architecture, an agent cross-references the task and dataset against relevant published research, summarizing methodologies from comparable papers and calling out where the current setup diverges from established approaches in the literature:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0laemdrxmj5gmuct64h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0laemdrxmj5gmuct64h.png" alt=" " width="800" height="1007"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Try It Yourself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Pritam-mb/hacktropica.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hacktropica

python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate   &lt;span class="c"&gt;# Windows: venv\Scripts\activate&lt;/span&gt;

pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env       &lt;span class="c"&gt;# then fill in GROQ_API_KEY, KAGGLE_USERNAME, KAGGLE_KEY, CHAINLIT_AUTH_SECRET&lt;/span&gt;

python start.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:8001&lt;/code&gt; and try a prompt like &lt;em&gt;"Diagnose breast cancer from biopsy records"&lt;/em&gt; or &lt;em&gt;"Detect credit card fraud from transaction telemetry."&lt;/em&gt; Runtime diagnostics — Groq auth status, Kaggle CLI resolution, credential checks — are available at &lt;code&gt;http://localhost:8001/runtime-diagnostics&lt;/code&gt; if anything needs debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partner Technologies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Groq (&lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;)&lt;/strong&gt; is the reasoning engine behind almost every intelligent decision in OmniML: generating the initial model architecture from a natural-language prompt, ranking candidate Kaggle datasets, writing deterministic training code, self-healing that code when validation fails, and generating live natural-language commentary during training (as shown in the screenshots above). What made Groq stand out for this specific project was inference latency. Because OmniML's architecture involves an LLM call at nearly every stage transition in a multi-agent LangGraph state machine, using a slower inference provider would have made the human-in-the-loop experience feel sluggish and would have broken the sense that you're interacting with a live system rather than waiting on a batch job. Groq's speed meant the architect agent could regenerate a model graph, or the debugger agent could rewrite a failing script, fast enough that the checkpoint-and-resume UX in Chainlit felt responsive rather than like a loading screen. Swapping in the &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt; model specifically gave us a good balance between reasoning quality for code generation tasks and the raw throughput Groq's hardware provides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chainlit&lt;/strong&gt; is the backbone of the entire user-facing experience. Rather than building a custom frontend from scratch, we used Chainlit's message and element system to embed custom React components — the React Flow architecture editor, the EDA profiling dashboard, the training console, and the deployment dashboard — directly inside a chat interface. The interesting implementation challenge was making Chainlit's async message flow pause execution and wait on user interaction from an embedded custom element, rather than just displaying static content. We used Chainlit's action and custom-element callback system to bridge state from the frontend graph editor back into the LangGraph execution state, so a user's edits to the visual canvas actually mutate the architecture object that gets compiled downstream. Working with Chainlit's extensibility for custom JS/React elements alongside a Python-native backend was one of the more rewarding parts of the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kaggle API&lt;/strong&gt; powers structured dataset discovery, evaluation, and automated ingest — letting the agent search, rank, and validate real-world tabular datasets against the user's problem statement without any manual dataset hunting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PyTorch, Optuna &amp;amp; Scikit-Learn&lt;/strong&gt; drive graph compilation, hyperparameter search, and model execution across both the sklearn (Path B) and PyTorch (Path A) training routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hackathon Experience
&lt;/h2&gt;

&lt;p&gt;We attended &lt;strong&gt;HackTropica&lt;/strong&gt; on &lt;strong&gt;5th April 2026&lt;/strong&gt; at &lt;strong&gt;Asansol Engineering College&lt;/strong&gt;. The energy in the hall was electric from the moment we walked in—hundreds of developers, whiteboards filled with architecture diagrams, and a clear challenge in the air to build something truly transformative.&lt;/p&gt;

&lt;p&gt;Building &lt;strong&gt;AxisVision OmniML&lt;/strong&gt; under a tight deadline was both thrilling and intense. A pivotal part of our journey was the guidance we received from &lt;strong&gt;Prof. Tridib Chakraborty&lt;/strong&gt;. His mentorship helped us sharpen our system design, pushing us to ensure our Human-in-the-Loop (HITL) workflow was seamless, robust, and genuinely useful for real-world application.&lt;/p&gt;

&lt;p&gt;The most memorable moment was our final pipeline test. We had spent hours resolving a late-stage bug where the LLM-generated graph layout refused to bind cleanly with the Kaggle dataset streaming pipeline. After painstaking step-by-step debugging, we hit "Run"—and watched in real time as the prompt generated a complete 7-layer neural network, downloaded live data, rendered EDA metrics, and executed training end-to-end without breaking.&lt;/p&gt;

&lt;p&gt;What I'll remember most about this event is the sheer thrill of turning a complex, multi-module AI system into a working reality in a room full of passionate builders. HackTropica was a powerful reminder of what good teamwork and dedicated mentorship can achieve under pressure.&lt;/p&gt;

&lt;p&gt;If you find OmniML valuable, consider giving it a ⭐ on &lt;a href="https://github.com/Pritam-mb/hacktropica" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — and if you build something with it or have feedback, I'd love to hear about it.&lt;/p&gt;

</description>
      <category>mlhacks</category>
      <category>devchallenge</category>
      <category>hackathon</category>
      <category>ai</category>
    </item>
    <item>
      <title>NeedFeed 🤝 — Safe, In-Kind Mutual Aid Without Cash or Tagging</title>
      <dc:creator>Pritam Patra</dc:creator>
      <pubDate>Mon, 07 Sep 2026 04:14:30 +0000</pubDate>
      <link>https://dev.to/pritam_patra_429a25dedae6/needfeed-safe-in-kind-mutual-aid-without-cash-or-tagging-1166</link>
      <guid>https://dev.to/pritam_patra_429a25dedae6/needfeed-safe-in-kind-mutual-aid-without-cash-or-tagging-1166</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;Weekend Challenge: Generosity Edition&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
Prize categories: &lt;strong&gt;Best Use of Snowflake&lt;/strong&gt; · &lt;strong&gt;Best Use of Google AI&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem I couldn't unsee
&lt;/h2&gt;

&lt;p&gt;Most charity tech solves the wrong problem.&lt;/p&gt;

&lt;p&gt;It makes it easier to &lt;strong&gt;move money&lt;/strong&gt; — when the actual friction is moving &lt;em&gt;things&lt;/em&gt;. It makes it easier to &lt;strong&gt;identify people in need&lt;/strong&gt; — when the actual requirement is &lt;em&gt;protecting&lt;/em&gt; them. It makes it easier to &lt;strong&gt;claim generosity&lt;/strong&gt; — when the actual value is &lt;em&gt;proving&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;Three failure modes, and they all start before a single dollar changes hands:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cash is a liability, not a feature.&lt;/strong&gt; When the need is "10 kg of rice" but the mechanism is "send ₹500," you've introduced an unnecessary conversion step — and with it every failure mode: fee skimming, diversion, a middleman buying the wrong thing entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tagging is surveillance in a helpful mask.&lt;/strong&gt; "Tag someone who needs this!" sounds generous. It publicly identifies a vulnerable person to a stranger, with no consent, no accountability, and no recourse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-reported badges are meaningless.&lt;/strong&gt; Most platforms award "generous donor" status when someone &lt;em&gt;says&lt;/em&gt; they gave. No one checks. The badge becomes a participation trophy, not a trust signal.&lt;/p&gt;

&lt;p&gt;NeedFeed exists because charity does not have to mean money — and almost everyone has &lt;em&gt;something&lt;/em&gt; they can give.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NeedFeed&lt;/strong&gt; is a hyperlocal, in-kind mutual aid platform. Neighbors coordinate real, physical help — food, clothing, medical supplies, school materials, volunteer time — without ever touching cash, without publicly tagging vulnerable people, and without asking anyone to just trust the process.&lt;/p&gt;

&lt;p&gt;The unit of generosity is a &lt;strong&gt;physical item&lt;/strong&gt;: a warm sweater, a walking cane, a bag of rice.&lt;br&gt;
The unit of trust is a &lt;strong&gt;verified handoff&lt;/strong&gt; — not a payment receipt, not a checkbox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30yrwdz9icqn7p9ngnmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30yrwdz9icqn7p9ngnmz.png" alt=" " width="800" height="708"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Four hard rules — in the data model, not the guidelines
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Strictly in-kind, zero cash, ever.&lt;/strong&gt;&lt;br&gt;
Food, clothing, medicine, mobility aids, school supplies, shelter materials, hygiene kits, and volunteer time only. There is no "donate cash" button, and there never will be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Open pledge pool, no direct tagging.&lt;/strong&gt;&lt;br&gt;
A posted need enters a public pledge pool. Any neighbor can pledge to fulfill it, but no one gets a private link, a DM, or a direct connection to the beneficiary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Steward representation, with recorded consent.&lt;/strong&gt;&lt;br&gt;
People who can't represent themselves online — no phone, no literacy, no connectivity — are represented by a verified local steward who opens a persistent Case Page on their behalf, &lt;em&gt;only after a recorded verbal consent clip&lt;/em&gt;, not a checkbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Two-party verification cycle.&lt;/strong&gt;&lt;br&gt;
A giver pledges, marks the item "Handed Off." Only after the steward independently confirms receipt with the beneficiary does the giver earn their Honor Badge. A mismatch is flagged and auditable.&lt;/p&gt;

&lt;p&gt;The badge means something because it costs something to earn.&lt;/p&gt;


&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HCPxF1s6CnM" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run locally — zero API keys required:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Pritam-mb/charity.git
&lt;span class="nb"&gt;cd &lt;/span&gt;charity
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;span class="c"&gt;# → http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The local auto-tagger and local metric engine stand in for Gemini and Snowflake respectively. To connect the real services, add keys to &lt;code&gt;.env.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Snowflake Data Cloud
SNOWFLAKE_ACCOUNT=&amp;lt;account&amp;gt;.&amp;lt;region&amp;gt;
SNOWFLAKE_USERNAME=&amp;lt;username&amp;gt;
SNOWFLAKE_PASSWORD=&amp;lt;password&amp;gt;
SNOWFLAKE_DATABASE=CHARITY
SNOWFLAKE_SCHEMA=PUBLIC
SNOWFLAKE_WAREHOUSE=COMPUTE_WH

# Google Gemini AI
GOOGLE_AI_API_KEY=AIzaSy...
GOOGLE_AI_MODEL=gemini-1.5-flash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Delete &lt;code&gt;data/store.json&lt;/code&gt; and restart the dev server to reseed with fresh, realistic demo data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub →&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/charity" rel="noopener noreferrer"&gt;Pritam-mb/charity&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The walkthrough
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The feed
&lt;/h3&gt;

&lt;p&gt;The home feed is designed for Gen Z: visual, fast, and community-driven. It's a dark-mode, Reddit-style three-column layout — a collapsible left sidebar for navigation, a center feed with segmented sorting (&lt;strong&gt;New / Hot / Top Urgent&lt;/strong&gt;) and category/neighborhood filters, and a right rail with a Local Area Explorer and community rules.&lt;/p&gt;

&lt;p&gt;You can browse new, hot, or urgent needs, filter by category and neighborhood, and see exactly what is needed — with a real image, urgency flair, and steward-beneficiary attribution (&lt;em&gt;"Posted by Rashid on behalf of Arjun"&lt;/em&gt;). Every need card has a four-action bar: Support, Comments, Share, and Pledge Help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2avs425p05ua1xsanxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2avs425p05ua1xsanxv.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Opening a need
&lt;/h3&gt;

&lt;p&gt;Open a need and you get the full context — item, urgency, quantity, neighborhood, and the Case Page it belongs to. A verified local handoff point appears on the Leaflet map, so help stays simple and local. No stranger-to-stranger coordinates. No private DMs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Making a pledge
&lt;/h3&gt;

&lt;p&gt;Choose &lt;strong&gt;Pledge Help&lt;/strong&gt;, select what you can provide, and complete the handoff. But a pledge is not impact.&lt;/p&gt;

&lt;p&gt;You mark it &lt;strong&gt;Handed Off&lt;/strong&gt;. Then the verified steward confirms delivery with the beneficiary. Only when &lt;em&gt;both sides confirm&lt;/em&gt; does the system record a verified contribution and award the Honor Badge. That two-step is not a formality — it's the whole point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F550qr460yvpmsdfwdiv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F550qr460yvpmsdfwdiv1.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Posting a need
&lt;/h3&gt;

&lt;p&gt;Posting only requires a plain-language caption and an optional photo or video. Gemini AI analyzes the description and proposes category, item type, quantity, and urgency — all fully editable before publishing. AI proposes; a human confirms. Nothing auto-publishes.&lt;/p&gt;

&lt;p&gt;When Gemini isn't configured, a local keyword-and-regex inference engine (&lt;code&gt;lib/auto-tag.ts&lt;/code&gt;) handles the same job. The workflow never breaks because of a missing API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fccaxa0xdmx4to68lsq0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fccaxa0xdmx4to68lsq0x.png" alt=" " width="1582" height="808"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Case Pages
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8npifoffbqpmviqm8c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8npifoffbqpmviqm8c0.png" alt=" " width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Case Pages (&lt;code&gt;/cases/[id]&lt;/code&gt;) are dedicated, persistent profiles for recurring or high-vulnerability beneficiaries. Instead of isolated posts, a Case Page becomes a &lt;em&gt;living record&lt;/em&gt; of the community helping one person over time.&lt;/p&gt;

&lt;p&gt;Each page has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A letter avatar and alias name (never a legal name)&lt;/li&gt;
&lt;li&gt;A verified-consent badge&lt;/li&gt;
&lt;li&gt;An append-only steward update timeline with photo evidence&lt;/li&gt;
&lt;li&gt;All associated needs with their fulfillment status&lt;/li&gt;
&lt;li&gt;A defined handoff location — a known local anchor point, not a stranger's address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The community extends beyond individual needs too — people can offer items, volunteer their time, connect with organisations, and follow cases they care about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffas1rjkmj3bu5mglhb4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffas1rjkmj3bu5mglhb4e.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Account settings define a person's platform role (volunteer, steward, NGO leader), primary neighborhood for anchor-point matching, and contact info — which is only ever shared with stewards at the point of a pledge or handoff, never made public.&lt;/p&gt;
&lt;h3&gt;
  
  
  Achievements
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5p041han8af36kevgu9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5p041han8af36kevgu9p.png" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Confirmed, fulfilled needs become a quiet public record — not a leaderboard of who gave the most, but a feed of what actually changed for someone, with a link back to their Case Page. Verified contributions, not participation trophies.&lt;/p&gt;
&lt;h3&gt;
  
  
  Snowflake HQ — the transparency layer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhq6emcdwxj95eulxrgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhq6emcdwxj95eulxrgu.png" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then comes &lt;strong&gt;Snowflake HQ&lt;/strong&gt; — what I call the GlassPocket layer, because every transaction is visible through the glass.&lt;/p&gt;

&lt;p&gt;A one-click &lt;code&gt;POST /api/sync&lt;/code&gt; pushes the entire local transactional state to Snowflake. Dashboard metrics are then computed via Snowflake SQL when available, or locally from &lt;code&gt;store.json&lt;/code&gt; when not — the platform is always operational either way.&lt;/p&gt;

&lt;p&gt;Snowflake surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which neighborhoods have the most unfulfilled needs&lt;/li&gt;
&lt;li&gt;Which categories are chronically undersupplied&lt;/li&gt;
&lt;li&gt;Which Case Pages are going stale (steward abandonment detection)&lt;/li&gt;
&lt;li&gt;Whether badge counts actually correspond to real confirmed handoffs&lt;/li&gt;
&lt;li&gt;Which shared needs convert into pledges and which don't&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Top Givers are ranked by &lt;strong&gt;confirmed handoffs&lt;/strong&gt; — not pledges. That single distinction is the difference between a leaderboard and an accountability record.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 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;Tech&lt;/th&gt;
&lt;th&gt;Why&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 (App Router), React 19&lt;/td&gt;
&lt;td&gt;Server Components for the heavy lifting, Client Components for interactive hubs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript (strict mode)&lt;/td&gt;
&lt;td&gt;100% type safety across every API route and component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Vanilla CSS, custom design system&lt;/td&gt;
&lt;td&gt;Reddit dark theme, glassmorphism, responsive grids — no framework overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Local JSON store (&lt;code&gt;data/store.json&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Atomically managed via &lt;code&gt;lib/store.ts&lt;/code&gt;, auto-seeded on first run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intelligence&lt;/td&gt;
&lt;td&gt;Google Gemini AI&lt;/td&gt;
&lt;td&gt;Auto-categorization, item extraction, urgency scoring on upload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;Snowflake Data Cloud&lt;/td&gt;
&lt;td&gt;Relational telemetry with automated local fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapping&lt;/td&gt;
&lt;td&gt;Leaflet + OpenStreetMap&lt;/td&gt;
&lt;td&gt;Hyperlocal anchor point visualization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Repo structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/              Next.js App Router — pages, layouts, API routes
components/       Feed, need cards, pledge pool, steward tools, forms, dashboard widgets
lib/              Types, store, seed data, Gemini client, Snowflake queries, auto-tagger
data/             store.json (auto-generated, gitignored)
scripts/          Snowflake diagnostics and connectivity tests
public/images/    Photorealistic need imagery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Worth reading: &lt;code&gt;plan.md&lt;/code&gt; (product thesis), &lt;code&gt;problems.md&lt;/code&gt; (every risk identified and how it's mitigated), &lt;code&gt;lib/store.ts&lt;/code&gt; (the two-party verification cycle), &lt;code&gt;lib/snowflake.ts&lt;/code&gt; (dual-engine telemetry with automatic fallback).&lt;/p&gt;


&lt;h2&gt;
  
  
  Safety decisions I'm proud of
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Alias names, not legal names.&lt;/strong&gt; Case Pages use pseudonyms and broad neighborhood tags — never exact addresses, never legal identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No solo stewardship.&lt;/strong&gt; One person alone speaking for another is exactly the risk the platform exists to prevent. The system requires co-stewardship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consent isn't a checkbox.&lt;/strong&gt; Verbal recorded consent is a structural requirement — a field in the data model, not a terms-of-service paragraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anchor points over direct meetups.&lt;/strong&gt; Handoffs route through a known local location (a shop, a community hub) rather than a stranger-to-stranger connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staleness detection.&lt;/strong&gt; Snowflake flags Case Pages with no recent updates. An abandoned page that still &lt;em&gt;looks&lt;/em&gt; active is worse than no page at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editable AI tags.&lt;/strong&gt; Gemini proposes; the steward confirms. No auto-publish, no blind trust in classification.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prize categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Snowflake — GlassPocket accountability layer&lt;/strong&gt;&lt;br&gt;
Snowflake is the transparency backbone. It powers the institutional dashboard (unfulfilled needs by category/area, urgency distribution, steward staleness detection, confirmed-handoffs-only leaderboard) with a dual-engine design that falls back to local computation with zero downtime if Snowflake is unreachable. Key design choice: analytics that fail silently are worse than no analytics at all — so the fallback is first-class, not an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Google AI — Gemini auto-categorization&lt;/strong&gt;&lt;br&gt;
Gemini analyzes every posted need (description and/or photo) to propose category, item type, quantity, and urgency. A local keyword-and-regex engine (&lt;code&gt;lib/auto-tag.ts&lt;/code&gt;) provides identical functionality when Gemini isn't configured, so the platform never depends on a live API key to function. AI assists the steward — it does not replace the steward's judgment.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I'd build next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Solana confirmation ledger&lt;/strong&gt; — writing each verified handoff on-chain so badge counts are cryptographically auditable, not just database rows. The &lt;code&gt;ConfirmationRecord&lt;/code&gt; model already has an &lt;code&gt;on_chain_ref&lt;/code&gt; field waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs voice input&lt;/strong&gt; — letting stewards speak updates instead of typing, with auto-narrated Case Page timelines in multiple languages for low-literacy accessibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate detection&lt;/strong&gt; — Gemini visual-similarity + area matching at Case Page creation, to prevent two stewards unknowingly opening pages for the same person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stewardship transfer&lt;/strong&gt; — a formal flow for community members to take over inactive Case Pages after a defined inactivity window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real geospatial infrastructure&lt;/strong&gt; — replacing manual neighborhood tags with location-aware matching and proximity-based feed prioritization.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;Someone posts a need. Someone else offers what they can. The community connects them. The handoff happens. The steward verifies it. The contribution becomes part of a trusted record.&lt;/p&gt;

&lt;p&gt;Not everyone can donate money. But almost everyone has something they can give — a bag of rice, a spare cane, an afternoon.&lt;/p&gt;

&lt;p&gt;NeedFeed turns those small acts into a connected, accountable community. No cash to divert. No vulnerable people to tag. No badges to fake. Just rice, sweaters, wheelchairs, and the receipts to prove they arrived.&lt;/p&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Pritam-mb" rel="noopener noreferrer"&gt;
        Pritam-mb
      &lt;/a&gt; / &lt;a href="https://github.com/Pritam-mb/charity" rel="noopener noreferrer"&gt;
        charity
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;NeedFeed&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Hyperlocal in-kind giving. Post a need, anyone can pledge a piece of it into an open pool
a steward confirms the handoff, and the giver earns a verifiable badge — no money, no tagging
of vulnerable people.&lt;/p&gt;
&lt;p&gt;Product thinking lives in &lt;code&gt;plan.md&lt;/code&gt; and &lt;code&gt;problems.md&lt;/code&gt; (at repo root, &lt;code&gt;..&lt;/code&gt;).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
npm run dev        &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; http://localhost:3000&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The app works with zero API keys. Local auto-tagging stands in for Google AI and the
Snowflake dashboard falls back to computing the same metrics locally.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo script (2–3 min)&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reel&lt;/strong&gt; (&lt;code&gt;/&lt;/code&gt;) — filter by category/area, open a need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case Page&lt;/strong&gt; (&lt;code&gt;/cases/c-arjun&lt;/code&gt;) — multi-entry timeline, recorded-consent badge, steward tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pledge&lt;/strong&gt; — on a need card, pledge a portion → “Mark handed off”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm&lt;/strong&gt; — on the Case Page, steward tools → “Confirm received” → giver badge increments (see &lt;code&gt;/&lt;/code&gt; toolbar is per-user; badge lives in &lt;code&gt;store.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Pritam-mb/charity" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  👥 The Team
&lt;/h2&gt;

&lt;p&gt;Our team came together for the International Charity Day DEV Challenge to build NeedFeed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to"&gt;@pritam_patra_429a25dedae6&lt;/a&gt; (Lead Developer)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/RexBytes" rel="noopener noreferrer"&gt;@t-rexbytes&lt;/a&gt; (Collaborator)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built with Next.js 15 · React 19 · Snowflake Data Cloud · Google Gemini AI · Leaflet · TypeScript&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From 2.0 MB to 471 KB: Debugging Rawbee with Sentry + Gemini</title>
      <dc:creator>Pritam Patra</dc:creator>
      <pubDate>Sun, 16 Aug 2026 21:37:22 +0000</pubDate>
      <link>https://dev.to/pritam_patra_429a25dedae6/from-20-mb-to-471-kb-debugging-rawbee-with-sentry-gemini-363g</link>
      <guid>https://dev.to/pritam_patra_429a25dedae6/from-20-mb-to-471-kb-debugging-rawbee-with-sentry-gemini-363g</guid>
      <description>&lt;h1&gt;
  
  
  I Thought Rawbee Was Slow — Then I Opened the Network Tab
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;Rawbee is a full-stack video-sharing and real-time streaming platform that I built as a YouTube-inspired application.&lt;/p&gt;

&lt;p&gt;It currently includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video uploads&lt;/li&gt;
&lt;li&gt;Video discovery&lt;/li&gt;
&lt;li&gt;Subscriptions&lt;/li&gt;
&lt;li&gt;Watch history&lt;/li&gt;
&lt;li&gt;Live streaming&lt;/li&gt;
&lt;li&gt;WebRTC-based functionality&lt;/li&gt;
&lt;li&gt;Screen sharing&lt;/li&gt;
&lt;li&gt;Socket.IO communication&lt;/li&gt;
&lt;li&gt;Cloudinary media delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React 18, TypeScript, Tailwind CSS, Vite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js, Express&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media:&lt;/strong&gt; Cloudinary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time:&lt;/strong&gt; Socket.IO, WebRTC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Sentry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI debugging:&lt;/strong&gt; Google Gemini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee" rel="noopener noreferrer"&gt;View Rawbee on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug Smash PR:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee/pull/1" rel="noopener noreferrer"&gt;View the Pull Request&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this Bug Smash, I deliberately stopped adding new features and focused on one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where is Rawbee doing unnecessary work, and can I prove that my fixes actually made it better?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Moment I Realized Something Was Wrong
&lt;/h2&gt;

&lt;p&gt;Nothing was obviously broken.&lt;/p&gt;

&lt;p&gt;The homepage loaded. Videos appeared. Search worked.&lt;/p&gt;

&lt;p&gt;But the application felt heavier than it should.&lt;/p&gt;

&lt;p&gt;My first instinct was to blame React.&lt;/p&gt;

&lt;p&gt;When I typed into the search bar, the parent component updated and the video-card tree was being evaluated again.&lt;/p&gt;

&lt;p&gt;So I opened React DevTools and started profiling.&lt;/p&gt;

&lt;p&gt;The profiler showed repeated &lt;code&gt;VideoCard&lt;/code&gt; renders during a simple interaction, even when the underlying video data had not changed.&lt;/p&gt;

&lt;p&gt;That was the first bug.&lt;/p&gt;

&lt;p&gt;Then I opened the Network tab.&lt;/p&gt;

&lt;p&gt;And I found a much bigger problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Network Tab Changed Everything
&lt;/h2&gt;

&lt;p&gt;The production homepage was transferring approximately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.0 MB&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some individual image assets were enormous for their displayed size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;834 KB
433 KB
160 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An 834 KB thumbnail for a relatively small video card immediately looked wrong.&lt;/p&gt;

&lt;p&gt;At that point, the problem stopped being:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The page feels slow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The browser is downloading far more data than this UI should need."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before: Production Baseline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 1 — BEFORE NETWORK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1um3ilrj881amyorbw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1um3ilrj881amyorbw8.png" alt="Chrome DevTools Network panel showing the unoptimized production build with 30 requests, 2.0 MB transferred, 2.3 MB of resources, and a 2.61 second Load time" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Production baseline before optimization. The homepage transferred approximately 2.0 MB, with individual image assets reaching 834 KB, 433 KB, and 160 KB.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The baseline showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;30 requests&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.0 MB transferred&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.3 MB resources&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;593 ms DOMContentLoaded&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.61 s Load&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.82 s Finish&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first time, I had a real baseline instead of a vague feeling that the application was slow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix #1 — Stopping Unnecessary React Renders
&lt;/h1&gt;

&lt;p&gt;The first fix was &lt;code&gt;React.memo()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search state changes
        ↓
Home re-renders
        ↓
VideoCard renders again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if the video's data hasn't changed, that child component doesn't necessarily need to render again.&lt;/p&gt;

&lt;p&gt;I changed the component to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;memo&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="s2"&gt;react&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;formatDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;// ...&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;formatViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;// ...&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;VideoCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;VideoCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;video&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;VideoCardProps&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"group cursor-pointer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Video card UI */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;VideoCard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also moved pure formatting helpers outside the component so their definitions weren't recreated during every component execution.&lt;/p&gt;

&lt;p&gt;The important optimization, however, was &lt;code&gt;React.memo()&lt;/code&gt; combined with stable props.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;video&lt;/code&gt; prop remains referentially equal, React can skip rendering the component.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Profiler — Before
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 2 — REACT PROFILER BEFORE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnywt9afuyjg96g9fekf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnywt9afuyjg96g9fekf.png" alt="React DevTools Profiler showing VideoCard rendering repeatedly during the unoptimized search interaction" width="799" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before memoization: &lt;code&gt;VideoCard&lt;/code&gt; renders repeatedly during the recorded interaction as parent state changes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The profiler gave me a concrete reason to make the optimization instead of blindly applying &lt;code&gt;React.memo()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Profiler — After
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 3 — REACT PROFILER AFTER&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnphg2snr595qiuuhlt5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnphg2snr595qiuuhlt5j.png" alt="React DevTools Profiler showing the memoized VideoCard component being skipped during the optimized interaction" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After memoization: the Profiler explicitly reports "Did not render on the client during this profiling session." The memoized component is skipped when its props remain unchanged.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important result wasn't the raw number of React commits.&lt;/p&gt;

&lt;p&gt;It was seeing React actually skip the component when its props remained unchanged.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix #2 — The 834 KB Thumbnail
&lt;/h1&gt;

&lt;p&gt;The React optimization removed unnecessary rendering work.&lt;/p&gt;

&lt;p&gt;But the Network tab was still pointing at the much larger problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;image delivery.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original application requested full-size images from Cloudinary even when the UI only needed a relatively small thumbnail.&lt;/p&gt;

&lt;p&gt;I created a helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getOptimizedCloudinaryUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudinary.com&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;url&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/upload/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`/upload/w_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/f_auto/q_auto/`&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;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getOptimizedCloudinaryUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;500&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transformation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;w_500&lt;/code&gt; to resize the image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto&lt;/code&gt; to let Cloudinary select an appropriate format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;q_auto&lt;/code&gt; to optimize quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before optimization, some image responses were approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;834 KB
433 KB
160 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After optimization, examples included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7.3 KB
2.0 KB
22.0 KB
2.6 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This became the biggest contributor to the overall network improvement.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.0 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the optimized production build transferred:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;471 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;76.5% reduction in transferred data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  After: Optimized Production Build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 4 — AFTER NETWORK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9lnkkegq42kh1rejyyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9lnkkegq42kh1rejyyj.png" alt="Chrome DevTools Network panel showing the optimized production build with 32 requests, 471 KB transferred, 767 KB resources, and a 1.60 second Load time" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Same page, same throttling, same cache-disabled conditions. Payload dropped from 2.0 MB to 471 KB and Load time from 2.61 s to 1.60 s.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix #3 — Why Load JavaScript for Pages the User Hasn't Opened?
&lt;/h1&gt;

&lt;p&gt;Rawbee contains multiple routes.&lt;/p&gt;

&lt;p&gt;But a user visiting the homepage doesn't need every page's JavaScript immediately.&lt;/p&gt;

&lt;p&gt;So I introduced route-level code splitting using &lt;code&gt;React.lazy()&lt;/code&gt; and &lt;code&gt;Suspense&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;lazy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Suspense&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="s2"&gt;react&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;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pages/Home&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pages/Login&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex h-screen items-center justify-center"&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Loading...
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        }
      &amp;gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{&amp;lt;Login"&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;}
          /&amp;gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{&amp;lt;Home"&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;}
          /&amp;gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Everything loads immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the browser can now load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current route
    ↓
Current route chunk
    ↓
User navigates
    ↓
Additional route chunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the initial JavaScript workload focused on the route the user is actually visiting.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix #4 — Cleaning Up Sentry Configuration
&lt;/h1&gt;

&lt;p&gt;While working on observability, I also found that the Sentry DSN was hardcoded in the source.&lt;/p&gt;

&lt;p&gt;I moved it to environment configuration.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your_dsn_here&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;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_SENTRY_DSN&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VITE_SENTRY_DSN=your_dsn_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the configuration easier to manage across environments and keeps environment-specific configuration outside application source.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fix #5 — When MongoDB Stopped Connecting
&lt;/h1&gt;

&lt;p&gt;While working on the frontend, I encountered a completely different problem.&lt;/p&gt;

&lt;p&gt;MongoDB stopped connecting and the backend reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;querySrv ECONNREFUSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, I treated it as a MongoDB problem.&lt;/p&gt;

&lt;p&gt;But the failure was occurring during DNS/SRV resolution.&lt;/p&gt;

&lt;p&gt;I investigated the DNS behavior using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Resolve-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;mongodb-host&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After confirming the DNS resolution problem in my Windows environment, I changed the MongoDB connection strategy to a direct connection configuration as a workaround.&lt;/p&gt;

&lt;p&gt;The backend then reconnected successfully.&lt;/p&gt;

&lt;h1&gt;
  
  
  Best Use of Sentry
&lt;/h1&gt;

&lt;p&gt;Sentry became part of my debugging workflow rather than something I added only after the application was finished.&lt;/p&gt;

&lt;p&gt;My process became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime problem
      ↓
Sentry captures the event
      ↓
Inspect issue + stack trace + context
      ↓
Locate affected code
      ↓
Fix
      ↓
Verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One concrete example happened during a refactor.&lt;/p&gt;

&lt;p&gt;I accidentally removed an import for &lt;code&gt;VideoSkeleton&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The application could still build successfully.&lt;/p&gt;

&lt;p&gt;But when the loading path executed at runtime, the browser threw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: VideoSkeleton is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sentry captured the runtime failure and provided the stack trace and surrounding debugging context.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Something is broken."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I now had:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This exact runtime path is throwing this exact error."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 5 — SENTRY ISSUE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9hbila26fx96lrq5vwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9hbila26fx96lrq5vwy.png" alt="Sentry issue page showing the ReferenceError VideoSkeleton is not defined with its stack trace and debugging context" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sentry captured the runtime failure with the stack trace and execution context used during debugging.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sentry and React Profiler Answered Different Questions
&lt;/h2&gt;

&lt;p&gt;One of the biggest lessons from the project was that different debugging tools answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sentry&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is failing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;React Profiler&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is rendering?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Chrome Network&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the browser downloading?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using separate tools for separate questions made the investigation much clearer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Best Use of Google AI
&lt;/h1&gt;

&lt;p&gt;I used &lt;strong&gt;Google Gemini 3.1 Pro&lt;/strong&gt; as a debugging partner rather than simply asking it to generate code.&lt;/p&gt;

&lt;p&gt;The MongoDB DNS error was the clearest example.&lt;/p&gt;

&lt;p&gt;I provided Gemini with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;querySrv ECONNREFUSED&lt;/code&gt; error&lt;/li&gt;
&lt;li&gt;My Windows environment&lt;/li&gt;
&lt;li&gt;Node.js information&lt;/li&gt;
&lt;li&gt;The MongoDB connection behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemini helped investigate possible causes involving DNS resolution, MongoDB SRV records, and IPv4/IPv6 behavior.&lt;/p&gt;

&lt;p&gt;One useful diagnostic was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Resolve-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_mongodb._tcp.cluster0.x3qwygg.mongodb.net&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran the command myself.&lt;/p&gt;

&lt;p&gt;That distinction was important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I treated Gemini's response as a hypothesis, not as proof.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I independently verified the behavior before changing the connection configuration.&lt;/p&gt;

&lt;p&gt;The debugging loop became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MongoDB querySrv ECONNREFUSED
        ↓
Gemini analysis
        ↓
DNS / IPv4-IPv6 hypothesis
        ↓
Resolve-DnsName verification
        ↓
Connection configuration workaround
        ↓
Backend restored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📸 SCREENSHOT 6 — GEMINI DEBUGGING&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjiirxdbgbbofewggcow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjiirxdbgbbofewggcow.png" alt="Gemini 3.1 Pro conversation analyzing the MongoDB querySrv ECONNREFUSED DNS and IPv4 IPv6 issue" width="799" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Gemini helped narrow down the MongoDB connection failure; I independently verified the suggested diagnostic path before implementing the workaround.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The useful part of AI debugging wasn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"AI wrote code for me."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"AI helped me turn an unfamiliar error into a testable hypothesis."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  The Final Benchmark
&lt;/h1&gt;

&lt;p&gt;After applying the fixes, I repeated the production benchmark under the same conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome&lt;/li&gt;
&lt;li&gt;Fast 4G throttling&lt;/li&gt;
&lt;li&gt;Browser cache disabled&lt;/li&gt;
&lt;li&gt;Same homepage&lt;/li&gt;
&lt;li&gt;Same machine&lt;/li&gt;
&lt;li&gt;Same production preview workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network transferred&lt;/td&gt;
&lt;td&gt;2.0 MB&lt;/td&gt;
&lt;td&gt;471 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−76.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resources loaded&lt;/td&gt;
&lt;td&gt;2.3 MB&lt;/td&gt;
&lt;td&gt;767 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−66.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load event&lt;/td&gt;
&lt;td&gt;2.61 s&lt;/td&gt;
&lt;td&gt;1.60 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−38.7%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finish&lt;/td&gt;
&lt;td&gt;2.82 s&lt;/td&gt;
&lt;td&gt;1.81 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−35.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOMContentLoaded&lt;/td&gt;
&lt;td&gt;593 ms&lt;/td&gt;
&lt;td&gt;624 ms&lt;/td&gt;
&lt;td&gt;+31 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two numbers I cared about most were:&lt;/p&gt;

&lt;h3&gt;
  
  
  2.0 MB → 471 KB
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;76.5% less network data transferred&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;h3&gt;
  
  
  2.61 s → 1.60 s
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;38.7% faster Load event&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Didn't Improve
&lt;/h2&gt;

&lt;p&gt;Not every metric improved.&lt;/p&gt;

&lt;p&gt;The request count changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 → 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and DOMContentLoaded changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;593 ms → 624 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also had a stale thumbnail returning a 404, which I flagged as follow-up work.&lt;/p&gt;

&lt;p&gt;I don't want to turn a benchmark into:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Everything got better."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;The meaningful result was the large reduction in transferred data and the improvement in the Load event.&lt;/p&gt;

&lt;p&gt;Those are the results I can actually demonstrate.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I'm Most Proud Of
&lt;/h1&gt;

&lt;p&gt;The thing I'm most proud of isn't a particular line of code.&lt;/p&gt;

&lt;p&gt;It's the change in how I approached the problem.&lt;/p&gt;

&lt;p&gt;Before this work, my instinct would have been:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The application feels slow. I should optimize the code."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Observe
   ↓
Measure
   ↓
Hypothesize
   ↓
Test
   ↓
Fix
   ↓
Measure again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought React rendering was the main problem.&lt;/p&gt;

&lt;p&gt;Then the Network tab showed me that an &lt;strong&gt;834 KB thumbnail&lt;/strong&gt; was a much bigger issue.&lt;/p&gt;

&lt;p&gt;That was a useful reminder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The thing that feels slow isn't always the thing that is actually expensive.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Profile before optimizing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;React.memo()&lt;/code&gt; is useful when you can identify unnecessary renders.&lt;/p&gt;

&lt;p&gt;The profiler gave me evidence to make the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network payload matters
&lt;/h3&gt;

&lt;p&gt;A frontend can have clean React code and still feel slow if the browser downloads unnecessary data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability changes debugging
&lt;/h3&gt;

&lt;p&gt;Sentry turned runtime errors into actionable issues instead of vague UI failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI works best with verification
&lt;/h3&gt;

&lt;p&gt;Gemini helped me investigate the MongoDB problem faster, but I still tested the hypothesis myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not every metric needs to improve
&lt;/h3&gt;

&lt;p&gt;A useful benchmark should report the actual result, including the numbers that didn't move in the desired direction.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's Next for Rawbee?
&lt;/h1&gt;

&lt;p&gt;The Bug Smash focused on performance, reliability, and debugging.&lt;/p&gt;

&lt;p&gt;But Rawbee is still evolving.&lt;/p&gt;

&lt;p&gt;The next direction is a more interactive social streaming experience.&lt;/p&gt;

&lt;p&gt;Future work includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Live Rooms&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebRTC video/audio&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screen sharing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time Socket.IO chat&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synchronized watch parties&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-user co-streaming&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host controls&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is eventually to let users watch, communicate, and participate together without needing a separate application for every part of the experience.&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;future roadmap features&lt;/strong&gt; and are not included in the performance measurements above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Result
&lt;/h1&gt;

&lt;p&gt;The most important transformation was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.0 MB → 471 KB&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;strong&gt;76.5% reduction in network transfer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Load event changed from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.61 s → 1.60 s&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;strong&gt;38.7% improvement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Along the way I also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced unnecessary &lt;code&gt;VideoCard&lt;/code&gt; rendering&lt;/li&gt;
&lt;li&gt;Optimized Cloudinary image delivery&lt;/li&gt;
&lt;li&gt;Added route-level code splitting&lt;/li&gt;
&lt;li&gt;Improved Sentry configuration&lt;/li&gt;
&lt;li&gt;Diagnosed a MongoDB DNS/SRV failure&lt;/li&gt;
&lt;li&gt;Used Sentry for runtime observability&lt;/li&gt;
&lt;li&gt;Used Gemini to accelerate debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No rewrite.&lt;/p&gt;

&lt;p&gt;No new framework.&lt;/p&gt;

&lt;p&gt;Just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Measure → Understand → Fix → Verify&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Links
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Rawbee Repository:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Pritam-mb/rawbee" rel="noopener noreferrer"&gt;https://github.com/Pritam-mb/rawbee&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug Smash Pull Request:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Pritam-mb/rawbee/pull/1" rel="noopener noreferrer"&gt;https://github.com/Pritam-mb/rawbee/pull/1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;And thanks to DEV and Sentry for giving me a reason to stop adding features for a moment and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How well does the application I've already built actually work?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Slashing Network Payload by 76%: How I Used Sentry and Gemini to Optimize a Real-Time Streaming Platform</title>
      <dc:creator>Pritam Patra</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:21:13 +0000</pubDate>
      <link>https://dev.to/pritam_patra_429a25dedae6/slashing-network-payload-by-76-how-i-used-sentry-and-gemini-to-optimize-a-real-time-streaming-fnd</link>
      <guid>https://dev.to/pritam_patra_429a25dedae6/slashing-network-payload-by-76-how-i-used-sentry-and-gemini-to-optimize-a-real-time-streaming-fnd</guid>
      <description>&lt;h1&gt;
  
  
  From 20+ Unnecessary Renders to a 76.5% Smaller Network Payload — How I Optimized Rawbee with Sentry + Gemini
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; Rawbee —  Collaborative Real-Time Streaming Platform &lt;br&gt;
&lt;strong&gt;Implementation:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee/pull/1" rel="noopener noreferrer"&gt;View the GitHub Pull Request&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee" rel="noopener noreferrer"&gt;View the GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rawbee is a full-stack video sharing and live streaming platform with traditional VOD features (uploads, subscriptions, watch history) alongside a WebRTC-based collaborative streaming engine for co-streaming, screen sharing, and in-browser voice/video chat.&lt;/p&gt;

&lt;p&gt;The current stack includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React 18, TypeScript, Tailwind CSS, Vite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js, Express&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media:&lt;/strong&gt; Cloudinary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Sentry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI debugging assistant:&lt;/strong&gt; Google Gemini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the Summer Bug Smash, I focused on improving the existing application rather than adding a new feature.&lt;/p&gt;

&lt;p&gt;My goal was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Find real problems → measure them → understand the root cause → fix them → measure again.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;During the Bug Smash, I addressed frontend performance issues, a backend reliability problem, and runtime errors surfaced through Sentry.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;As Rawbee grew, the homepage started doing more work than necessary.&lt;/p&gt;

&lt;p&gt;The first issue appeared while using the search bar. Updating the search state caused the parent component to re-render, and the video-card grid was repeatedly evaluated even when individual video data had not changed. React Profiler confirmed repeated &lt;code&gt;VideoCard&lt;/code&gt; renders during the interaction, which led me to investigate whether those renders were actually necessary.&lt;/p&gt;

&lt;p&gt;The second problem was visible directly in Chrome DevTools. The homepage was transferring a surprisingly large amount of data, with several individual image assets reaching hundreds of kilobytes.&lt;/p&gt;

&lt;p&gt;The original production trace contained images such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;834 KB PNG
433 KB JPEG
160 KB JPEG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also found that the frontend was loading more JavaScript up front than necessary because page-level code was not being split efficiently.&lt;/p&gt;

&lt;p&gt;On the backend, MongoDB was failing to connect in my Windows environment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;querySrv ECONNREFUSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, Sentry was capturing frontend runtime errors that gave me useful visibility into failures occurring inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlled Before/After Benchmark
&lt;/h2&gt;

&lt;p&gt;To make the performance comparison meaningful, I benchmarked the &lt;strong&gt;before and after production builds&lt;/strong&gt; using the same setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome&lt;/li&gt;
&lt;li&gt;Fast 4G throttling&lt;/li&gt;
&lt;li&gt;Browser cache disabled&lt;/li&gt;
&lt;li&gt;Same homepage&lt;/li&gt;
&lt;li&gt;Same machine&lt;/li&gt;
&lt;li&gt;Same production preview workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network transferred&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.0 MB&lt;/td&gt;
&lt;td&gt;471 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;76.5% reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resources loaded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.3 MB&lt;/td&gt;
&lt;td&gt;767 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.7% reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load event&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.61 s&lt;/td&gt;
&lt;td&gt;1.60 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38.7% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Finish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.82 s&lt;/td&gt;
&lt;td&gt;1.81 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35.8% faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;No improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOMContentLoaded&lt;/td&gt;
&lt;td&gt;593 ms&lt;/td&gt;
&lt;td&gt;624 ms&lt;/td&gt;
&lt;td&gt;Slightly higher&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I am deliberately reporting the metrics that actually improved instead of dressing every measurement as a win. Request count went slightly up, partly because of a stale thumbnail returning 404, and DOMContentLoaded shifted marginally within measurement noise.&lt;/p&gt;

&lt;p&gt;The headline result is the &lt;strong&gt;76.5% payload reduction&lt;/strong&gt;.&lt;br&gt;
The biggest measurable gain came from reducing the amount of image data transferred to the browser.&lt;/p&gt;
&lt;h3&gt;
  
  
  Before: Production Benchmark
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1um3ilrj881amyorbw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft1um3ilrj881amyorbw8.png" alt="Chrome DevTools Network panel showing the unoptimized production build with 30 requests, 2.0 MB transferred, 2.3 MB of resources, and a 2.61 second Load time" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Production baseline before optimization. The homepage transferred approximately 2.0 MB, dominated by unoptimized image assets — including an 834 KB PNG, a 433 KB JPG, and a 160 KB JPG.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  After: Production Benchmark
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9lnkkegq42kh1rejyyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9lnkkegq42kh1rejyyj.png" alt="Chrome DevTools Network panel showing the optimized production build with reduced image payload and improved loading performance" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Same page, same throttling, same cache-disabled conditions. Payload dropped from 2.0 MB to 471 KB and Load time from 2.61 s to 1.60 s.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete implementation is available in the &lt;a href="https://github.com/Pritam-mb/rawbee/pull/1" rel="noopener noreferrer"&gt;GitHub Pull Request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main changes covered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React.memo
Cloudinary image transformations
React.lazy / Suspense
MongoDB connection workaround
Sentry configuration improvements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Memoizing &lt;code&gt;VideoCard&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;memo&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="s2"&gt;react&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;formatDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;// ...&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;formatViews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;// ...&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;VideoCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;VideoCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;VideoCardProps&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"group cursor-pointer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Video card UI */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;VideoCard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important optimization here is &lt;code&gt;React.memo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the parent component updates, React can skip rendering the memoized &lt;code&gt;VideoCard&lt;/code&gt; if the &lt;code&gt;video&lt;/code&gt; prop remains referentially equal.&lt;/p&gt;

&lt;p&gt;I also moved pure formatting helpers outside the component so they are not recreated as part of each component execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cloudinary Image Optimization
&lt;/h3&gt;

&lt;p&gt;This was the single biggest performance win in the benchmark. An 834 KB PNG for a card thumbnail was far larger than necessary for the displayed dimensions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getOptimizedCloudinaryUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudinary.com&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;url&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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/upload/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`/upload/w_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/f_auto/q_auto/`&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;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getOptimizedCloudinaryUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The w_500 resizes to display dimensions, f_auto serves modern formats (WebP/AVIF) where supported, and q_auto picks quality intelligently.&lt;/p&gt;

&lt;p&gt;The before trace contained image responses of 834 KB, 433 KB, and 160 KB. The after trace shows the same images at 7.3 KB, 2.0 KB, 22.0 KB, and 2.6 KB. This single change is the primary contributor to the 2.0 MB → 471 KB reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Route-Level Code Splitting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;lazy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Suspense&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="s2"&gt;react&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;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pages/Home&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;Login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pages/Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;
        &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex h-screen items-center justify-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Loading application...
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Login&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;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 moved page-specific JavaScript into route-level chunks instead of requiring every page to be part of the initial route workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sentry Environment Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;// frontend/.env
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ VITE_SENTRY_DSN=your_dsn_here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;// frontend/src/main.tsx
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;Sentry.init({
&lt;/span&gt;&lt;span class="gd"&gt;-  dsn: "your_dsn_here",
&lt;/span&gt;&lt;span class="gi"&gt;+  dsn: import.meta.env.VITE_SENTRY_DSN,
&lt;/span&gt;});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the configuration easier to manage across different environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. MongoDB DNS Failure
&lt;/h3&gt;

&lt;p&gt;The backend was failing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;querySrv ECONNREFUSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I investigated the DNS behavior in the Windows environment and used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Resolve-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;mongodb-host&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to verify the DNS resolution problem.&lt;/p&gt;

&lt;p&gt;I then switched to a direct MongoDB connection configuration as a workaround for the affected environment, restoring database connectivity.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Eliminating unnecessary React work
&lt;/h3&gt;

&lt;p&gt;The first performance problem was component-level rendering.&lt;/p&gt;

&lt;p&gt;A search update causes the &lt;code&gt;Home&lt;/code&gt; component to update, but an individual video card does not necessarily need to update with it.&lt;/p&gt;

&lt;p&gt;Before optimization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search state changes
        ↓
Home updates
        ↓
VideoCard repeatedly renders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After optimization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search state changes
        ↓
Home updates
        ↓
React.memo checks props
        ↓
VideoCard render skipped when props are unchanged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React Profiler Evidence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;React Profiler evidence — before:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fryav79n2bb1ripr0qrds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fryav79n2bb1ripr0qrds.png" alt="React DevTools Profiler showing VideoCard rendering repeatedly during the unoptimized search interaction" width="799" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before memoization: VideoCard renders repeatedly across the profiling session as parent state updates.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React Profiler evidence — after:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9x96aqdpi88xg6nsw6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9x96aqdpi88xg6nsw6c.png" alt="React DevTools Profiler showing VideoCard2 as a memoized component that did not render during the optimized interaction" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After memoization: the Profiler explicitly reports "Did not render on the client during this profiling session." The memoized component is skipped entirely when props are unchanged.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;React Profiler evidence for the memoized component. The optimized &lt;code&gt;VideoCard&lt;/code&gt; was skipped during the recorded interaction when its props remained unchanged.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I am intentionally not presenting the React commit count itself as the number of renders. The important result is that the profiler directly shows the memoized component being skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reducing image payload
&lt;/h3&gt;

&lt;p&gt;The largest measurable performance improvement came from image delivery.&lt;/p&gt;

&lt;p&gt;The original production trace contained image resources such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;834 KB
433 KB
160 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying Cloudinary transformations, the optimized trace contained much smaller responses, including examples around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7.3 KB
2.0 KB
22.0 KB
2.6 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change was the main contributor to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.0 MB → 471 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or a &lt;strong&gt;76.5% reduction in transferred data&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reducing initial JavaScript work
&lt;/h3&gt;

&lt;p&gt;The route-level &lt;code&gt;React.lazy()&lt;/code&gt; change means that the browser no longer needs to load code for every page before the current route can operate.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial application
├── Home
├── Login
├── other route code
└── dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial route
├── shared dependencies
└── current page chunk

Additional page
└── loaded on demand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the initial route focused on the code it actually needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Backend reliability improvement
&lt;/h3&gt;

&lt;p&gt;The MongoDB issue was not treated as a performance metric because it was fundamentally a reliability problem.&lt;/p&gt;

&lt;p&gt;The debugging process was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MongoDB connection fails
        ↓
querySrv ECONNREFUSED
        ↓
DNS investigation
        ↓
Resolve-DnsName verification
        ↓
Direct connection workaround
        ↓
Backend connectivity restored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fix removed a development blocker that was preventing the API from connecting to the database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;p&gt;I am submitting this project for the &lt;strong&gt;Best Use of Sentry&lt;/strong&gt; category.&lt;/p&gt;

&lt;p&gt;Sentry became part of the debugging workflow rather than simply being another dependency.&lt;/p&gt;

&lt;p&gt;The workflow was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime problem
      ↓
Sentry captures the event
      ↓
Inspect issue + stack trace
      ↓
Locate affected code
      ↓
Fix
      ↓
Verify application behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During development, Sentry captured frontend runtime errors including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: VideoSkeleton is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sentry provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issue details&lt;/li&gt;
&lt;li&gt;event information&lt;/li&gt;
&lt;li&gt;stack trace&lt;/li&gt;
&lt;li&gt;browser/environment context&lt;/li&gt;
&lt;li&gt;affected execution path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That helped me move from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Something is broken."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This specific code path is failing for this specific reason."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Sentry Evidence
&lt;/h3&gt;

&lt;p&gt;Sentry became part of my debugging loop, not just a dependency in package.json. The workflow was consistent throughout the Bug Smash:&lt;/p&gt;

&lt;p&gt;Runtime failure → Sentry captures event → inspect issue + stack trace + context → locate affected code → fix → verify.&lt;/p&gt;

&lt;p&gt;One concrete example: during a refactor, I accidentally removed an import for VideoSkeleton. It didn't crash the build — it only threw at runtime when the loading state triggered. Without Sentry I'd have found it eventually when a user reported a blank page. With Sentry, I had it within minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9hbila26fx96lrq5vwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9hbila26fx96lrq5vwy.png" alt="Sentry issue page showing the ReferenceError VideoSkeleton is not defined with its stack trace and debugging context" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry captured the ReferenceError with the full stack trace, release hash, browser and OS context, and event breadcrumbs. This turned "the page is broken sometimes" into a single actionable issue with a direct line to the failing code path.&lt;/p&gt;

&lt;p&gt;What Sentry gave me that my terminal couldn't: the release hash tying the error to a specific build, the browser and OS the user hit it on, the URL and route where it happened, and the trace ID linking to surrounding events. That context is what turned a five-word error into a five-minute fix.&lt;/p&gt;

&lt;p&gt;Importantly, I used Sentry for runtime observability and React Profiler for component rendering analysis — I didn't conflate them. Sentry told me what was failing; the Profiler told me how components were rendering. Keeping those two evidence streams separate gave me a cleaner picture of the application's actual behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;I am also submitting this project for the &lt;strong&gt;Best Use of Google AI&lt;/strong&gt; category.&lt;/p&gt;

&lt;p&gt;I used &lt;strong&gt;Gemini 3.1 Pro&lt;/strong&gt; as a debugging partner during the Bug Smash rather than simply asking it to generate the implementation.&lt;/p&gt;

&lt;p&gt;The clearest example was the MongoDB connection problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Problem
&lt;/h3&gt;

&lt;p&gt;The backend returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;querySrv ECONNREFUSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the application could not establish its database connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Gemini investigation
&lt;/h3&gt;

&lt;p&gt;I provided Gemini with the error and information about my Windows environment.&lt;/p&gt;

&lt;p&gt;Gemini analyzed the failure and suggested investigating DNS and IPv4/IPv6 resolution behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Independent verification
&lt;/h3&gt;

&lt;p&gt;I did not blindly copy the recommendation.&lt;/p&gt;

&lt;p&gt;I ran the diagnostic myself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Resolve-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;mongodb-host&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and verified the DNS resolution problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Implementation
&lt;/h3&gt;

&lt;p&gt;After confirming the issue, I changed the MongoDB connection strategy to use a direct connection configuration that avoided the failing SRV-resolution path.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Result
&lt;/h3&gt;

&lt;p&gt;The backend was able to reconnect to MongoDB and resume normal operation.&lt;/p&gt;

&lt;p&gt;The overall debugging loop became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;querySrv ECONNREFUSED
        ↓
Gemini analysis
        ↓
DNS hypothesis
        ↓
Resolve-DnsName verification
        ↓
Connection workaround
        ↓
Backend restored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I treated Gemini's response as a hypothesis, not as the final answer. I independently verified the DNS behavior before changing the MongoDB connection strategy.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini Evidence
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjiirxdbgbbofewggcow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjiirxdbgbbofewggcow.png" alt="Gemini 3.1 Pro conversation analyzing the MongoDB querySrv ECONNREFUSED DNS and IPv4 IPv6 issue" width="799" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the screenshot showing Gemini analyzing the &lt;code&gt;querySrv ECONNREFUSED&lt;/code&gt; problem and discussing the DNS/connection investigation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suggested caption: Gemini helped narrow down the MongoDB connection failure; I independently verified the suggested diagnostic path before implementing the workaround.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important part for me was not simply generating code faster. It was shortening the path from an unfamiliar error to a testable hypothesis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Impact
&lt;/h2&gt;

&lt;p&gt;The clearest measured result was the reduction in network transfer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.0 MB → 471 KB&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a &lt;strong&gt;76.5% reduction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Load event changed from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.61 s → 1.60 s&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;which is a &lt;strong&gt;38.7% reduction in Load time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The total resource footprint changed from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.3 MB → 767 KB&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;which is a &lt;strong&gt;66.7% reduction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The React Profiler also showed that the optimized &lt;code&gt;VideoCard&lt;/code&gt; could be skipped during the recorded search interaction when its props remained unchanged.&lt;/p&gt;

&lt;p&gt;The MongoDB issue was resolved as a backend reliability fix, while Sentry and Gemini made the debugging workflow more observable and efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Deliberately Did Not Claim
&lt;/h2&gt;

&lt;p&gt;Not every metric improved.&lt;/p&gt;

&lt;p&gt;Request count moved from 30 to 32, partly because a stale thumbnail returned 404. DOMContentLoaded moved from 593 ms to 624 ms, which I consider within the noise of this benchmark.&lt;/p&gt;

&lt;p&gt;The strongest measured result is the reduction in transferred data from 2.0 MB to 471 KB, followed by the Load improvement from 2.61 s to 1.60 s.&lt;/p&gt;

&lt;p&gt;The Sentry runtime event shown in this post was captured in development; validating the same workflow against production traffic is a next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Roadmap for Rawbee
&lt;/h2&gt;

&lt;p&gt;The Bug Smash was focused on performance and reliability, but I also have a larger direction planned for Rawbee.&lt;/p&gt;

&lt;p&gt;The next stage is to turn Rawbee from a traditional video-sharing application into a more interactive, social streaming platform.&lt;/p&gt;

&lt;p&gt;Planned future work includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Rooms&lt;/strong&gt; where users can create and join shared sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebRTC video/audio calling&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screen sharing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time room chat using Socket.io&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synchronized watch experiences&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-user collaborative streaming&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host controls and participant management&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to let users watch and interact together without needing a separate Discord or video-call application.&lt;/p&gt;

&lt;p&gt;These real-time features are &lt;strong&gt;future work&lt;/strong&gt; and are not included in the performance measurements reported in this Bug Smash submission.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This Bug Smash started with a simple observation: Rawbee was getting slower as the application grew.&lt;/p&gt;

&lt;p&gt;Instead of rewriting large parts of the project, I used measurement and debugging tools to identify where the actual problems were.&lt;/p&gt;

&lt;p&gt;The result was a focused set of improvements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React.memo
      ↓
Less unnecessary rendering

Cloudinary transformations
      ↓
Smaller image payloads

React.lazy + Suspense
      ↓
Route-level code splitting

DNS investigation + MongoDB workaround
      ↓
Backend reliability restored

Sentry
      ↓
Runtime visibility

Gemini
      ↓
Faster debugging investigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest measurable performance improvement was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.0 MB → 471 KB transferred&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;with the production Load event improving from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.61 s → 1.60 s&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The main takeaway is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before optimizing code, measure what the application is actually doing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is what I tried to do throughout this Bug Smash: identify the real bottlenecks, fix them deliberately, and verify the results instead of relying on assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Pull Request:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee/pull/1" rel="noopener noreferrer"&gt;View PR #1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Pritam-mb/rawbee" rel="noopener noreferrer"&gt;View Rawbee on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks for checking out my submission!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>ai</category>
      <category>sentry</category>
    </item>
  </channel>
</rss>
