How to Monitor a Website for Visual Changes
Price trackers, competitor monitors, status pages, government data feeds — there's a whole class of problems where you care when a web page looks different, not just when a specific element changes. Parsing HTML is brittle. Scraping structured data works until the layout changes. Screenshots capture everything.
Here's a minimal visual change monitor in Node.js: screenshot a URL, diff against the last capture, alert when the difference exceeds a threshold.
The monitor
import fs from 'fs/promises';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
const PAGES = [
{ name: 'competitor-pricing', url: 'https://competitor.com/pricing' },
{ name: 'status-page', url: 'https://status.yourvendor.com' },
];
const THRESHOLD = 0.02; // 2% pixel change triggers alert
const SNAPSHOTS_DIR = './snapshots';
async function capture(url) {
const res = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: { 'x-api-key': process.env.PAGEBOLT_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ url, fullPage: true, blockBanners: true, blockAds: true })
});
return Buffer.from(await res.arrayBuffer());
}
async function check(page) {
await fs.mkdir(SNAPSHOTS_DIR, { recursive: true });
const snapshotPath = `${SNAPSHOTS_DIR}/${page.name}.png`;
const current = await capture(page.url);
let previous;
try { previous = await fs.readFile(snapshotPath); }
catch { await fs.writeFile(snapshotPath, current); console.log(`✓ ${page.name}: baseline saved`); return; }
const img1 = PNG.sync.read(previous);
const img2 = PNG.sync.read(current);
const { width, height } = img1;
const diff = new PNG({ width, height });
const changed = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
const pct = changed / (width * height);
if (pct > THRESHOLD) {
await fs.writeFile(`${SNAPSHOTS_DIR}/${page.name}-diff.png`, PNG.sync.write(diff));
await fs.writeFile(snapshotPath, current); // update baseline
await alert(page, pct);
} else {
console.log(`✓ ${page.name}: no change (${(pct * 100).toFixed(2)}%)`);
}
}
async function alert(page, pct) {
console.error(`⚠ CHANGE DETECTED: ${page.name} — ${(pct * 100).toFixed(1)}% pixels changed`);
// Add your alerting here: Slack webhook, email, PagerDuty, etc.
// Example Slack:
// await fetch(process.env.SLACK_WEBHOOK, {
// method: 'POST',
// body: JSON.stringify({ text: `Visual change on ${page.name}: ${(pct*100).toFixed(1)}% changed` })
// });
}
for (const page of PAGES) await check(page);
Run this on a cron — every hour, every 15 minutes, whatever the use case demands. The first run saves a baseline. Every subsequent run diffs against it and updates the snapshot when a significant change is detected.
Run on a schedule (GitHub Actions)
name: Visual Monitor
on:
schedule:
- cron: '0 * * * *' # every hour
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install pixelmatch pngjs
- run: node monitor.js
env:
PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
Store the baseline PNGs as artifacts or commit them to the repo. The diff PNGs upload automatically when changes are detected.
No server. No Puppeteer process. No Selenium grid. A screenshot API call + a pixel diff library + a cron is all you need.
Free tier: 100 requests/month. → pagebolt.dev
Top comments (0)