DEV Community

Boehner
Boehner

Posted on

I Built a Zero-Dependency Website Change Detector in Node.js (Open Source)

I needed to know when a competitor changed their pricing page. I didn't want to set up Puppeteer, a headless browser, or a paid monitoring tool. So I built a 50-line Node.js script using the SnapAPI analyze endpoint.

Here's how it works — and it's fully open source.

The core pattern

Instead of scraping raw HTML and trying to parse it, SnapAPI's /v1/analyze returns structured data about a page: the primary CTA text, word count, detected tech stack, navigation items. The script saves a snapshot of this data after each run, then diffs it against the previous snapshot on the next run.

Here's the key logic:

async function analyzeUrl(url) {
  return new Promise((resolve, reject) => {
    const endpoint =
      `https://api.snapapi.tech/v1/analyze?url=${encodeURIComponent(url)}&api_key=${API_KEY}`;
    const req = https.get(endpoint, (res) => {
      let body = '';
      res.on('data', chunk => { body += chunk; });
      res.on('end', () => {
        if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
        resolve(JSON.parse(body));
      });
    });
    req.on('error', reject);
  });
}

function detectChanges(prev, curr) {
  const changes = [];
  const prevCta = prev.cta.join(' | ');
  const currCta = curr.cta.join(' | ');
  if (prevCta !== currCta) {
    changes.push(`CTA changed: "${prevCta}" → "${currCta}"`);
  }
  if (prev.word_count > 0) {
    const delta = Math.abs(curr.word_count - prev.word_count) / prev.word_count;
    if (delta > 0.10) {
      changes.push(`Word count: ${prev.word_count}${curr.word_count} (${(delta * 100).toFixed(0)}% change)`);
    }
  }
  return changes;
}
Enter fullscreen mode Exit fullscreen mode

After each check, the snapshot is written to state/<slug>.json. Next run, it loads that file and compares. If anything changed beyond a 10% word count threshold or any CTA shift, it prints [ALERT]. Otherwise, [OK].

How to use it

git clone https://github.com/Boehner/automation-health-monitor
cd automation-health-monitor
export SNAPAPI_KEY=your_key_here
Enter fullscreen mode Exit fullscreen mode

Edit config.json with the URLs you want to watch:

[
  { "name": "Stripe Pricing",  "url": "https://stripe.com/pricing" },
  { "name": "Notion Pricing",  "url": "https://notion.so/pricing" }
]
Enter fullscreen mode Exit fullscreen mode

Run it:

node index.js
Enter fullscreen mode Exit fullscreen mode

Sample output:

[2026-03-18T09:00:00.000Z] automation-health-monitor starting — 2 URLs

Checking: Stripe Pricing (https://stripe.com/pricing) ... [OK] no changes detected
Checking: Notion Pricing (https://notion.so/pricing) ...
[ALERT] Notion Pricing: page changed
        • CTA changed: "Get Notion free" → "Try Notion for free"
        • Word count: 1204 → 1587 (32% change)
Enter fullscreen mode Exit fullscreen mode

For continuous monitoring, one cron line:

0 */6 * * * SNAPAPI_KEY=your_key node /path/to/automation-health-monitor/index.js >> /var/log/monitor.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Why SnapAPI analyze instead of raw HTTP

The key insight: I'm not diffing raw HTML. I'm diffing structured page intelligence: CTA text, word count, detected tech stack. A visual redesign that doesn't change the CTA won't trigger a false positive. A competitor quietly rewriting their pricing copy will.

Raw HTML diffing produces too much noise — minification changes, CDN cache headers, timestamp strings in the page. Structured data diffing is signal, not noise.

The repo

github.com/Boehner/automation-health-monitor

Zero npm dependencies. Node.js 18+ only. MIT licensed.

Free API key

The SnapAPI free tier is 100 calls/month — enough to monitor 3 URLs every 8 hours, indefinitely, at no cost.

snapapi.tech — key active in about 30 seconds, no credit card.

Top comments (0)