<?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: Ahmed Errami</title>
    <description>The latest articles on DEV Community by Ahmed Errami (@erramix01).</description>
    <link>https://dev.to/erramix01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898000%2F70f63d4d-cb0f-4673-9d0a-a2905e295357.jpg</url>
      <title>DEV Community: Ahmed Errami</title>
      <link>https://dev.to/erramix01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erramix01"/>
    <language>en</language>
    <item>
      <title>I built a competitor pricing monitor in 3 days, here's how it actually works</title>
      <dc:creator>Ahmed Errami</dc:creator>
      <pubDate>Sat, 25 Apr 2026 20:53:56 +0000</pubDate>
      <link>https://dev.to/erramix01/i-built-a-competitor-pricing-monitor-in-3-days-heres-how-it-actually-works-1mg7</link>
      <guid>https://dev.to/erramix01/i-built-a-competitor-pricing-monitor-in-3-days-heres-how-it-actually-works-1mg7</guid>
      <description>&lt;p&gt;I lost a deal last month.&lt;/p&gt;

&lt;p&gt;Prospect told me my competitor had dropped &lt;br&gt;
their pricing two weeks earlier. Found out &lt;br&gt;
during a live demo. Looked completely &lt;br&gt;
unprepared.&lt;/p&gt;

&lt;p&gt;Started looking for a tool to automate this. &lt;br&gt;
Found Visualping, alerts on every pixel &lt;br&gt;
change, completely noisy. Found enterprise &lt;br&gt;
tools at $500+/year. Nothing in between.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 16 (App Router)&lt;/li&gt;
&lt;li&gt;Playwright for scraping&lt;/li&gt;
&lt;li&gt;Supabase for storage&lt;/li&gt;
&lt;li&gt;Resend for emails&lt;/li&gt;
&lt;li&gt;vercel for hosting and cron worker&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;The hardest part: signal vs noise&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generic page monitoring is easy. Meaningful &lt;br&gt;
change detection is hard.&lt;/p&gt;

&lt;p&gt;A pricing page changes constantly, rotating &lt;br&gt;
banners, timestamps, cookie notices, ad content. &lt;br&gt;
If you alert on every pixel diff you're just &lt;br&gt;
building a noise machine.&lt;/p&gt;

&lt;p&gt;I built a classification engine that categorizes &lt;br&gt;
changes before deciding whether to alert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRICE_CHANGE — dollar amounts moving&lt;/li&gt;
&lt;li&gt;PLAN_CHANGE — plans appearing or disappearing&lt;/li&gt;
&lt;li&gt;FEATURE_CHANGE — features shifting between tiers&lt;/li&gt;
&lt;li&gt;COSMETIC — everything else (ignored)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: normalize the text before diffing. Strip dates, times, navigation text, cookie notices, social handles. Then run a line-by-line diff on what's left.&lt;/p&gt;

&lt;p&gt;Only PRICE_CHANGE, PLAN_CHANGE, and FEATURE_CHANGE trigger an email.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Playwright over Puppeteer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pricing pages are almost always JS-heavy SPAs. Puppeteer struggles with modern React apps,content loads after the initial HTML response.&lt;/p&gt;

&lt;p&gt;Playwright with waitUntil: 'networkidle' plus a 2 second additional wait handles even the most aggressive lazy-loading. Worth the slightly heavier dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cron worker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Runs at 9am daily. Processes monitors sequentially with a 3 second delay between each — not parallel. Parallel scraping gets you rate-limited and blocked fast.&lt;/p&gt;

&lt;p&gt;On first run: saves a baseline snapshot. On subsequent runs: diffs against the previous snapshot, classifies the change, sends alert if significant&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd do differently&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The diff algorithm is the weakest part right now. Line-by-line text diff catches most changes but misses subtle restructuring, when a competitor moves a feature to a different plan section without changing the text, the diff doesn't catch it cleanly.&lt;/p&gt;

&lt;p&gt;Next step is extracting structured data (plan name → price → features[]) and diffing the structure instead of raw text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's live and free&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://priceblind.vercel.app/" rel="noopener noreferrer"&gt;https://priceblind.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No account, no credit card. Paste a URL, get emailed when pricing changes.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any part of the implementation, the scraping, the diff engine, or the email delivery.&lt;/p&gt;

&lt;p&gt;What would you build differently?&lt;/p&gt;

</description>
      <category>automation</category>
      <category>nextjs</category>
      <category>showdev</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
