Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

Monthly SEO Change Monitoring: Get a 'What Changed or Broke' Report From a Scheduled Audit

Monthly SEO Change Monitoring: "What Changed or Broke Since Last Month?" A one-off SEO audit tells you what is wrong today. What agencies and site owners usually need is different: what changed since last time? Did a d

Monthly SEO Change Monitoring: "What Changed or Broke Since Last Month?"

A one-off SEO audit tells you what is wrong today. What agencies and site owners usually need is different: what changed since last time? Did a deploy break pages? Did someone rewrite a title? Did a fix actually stick?

This guide shows how to get exactly that from a scheduled audit with the SEO Audit Toolkit on Apify, using its free monitoring mode. There is also a ready-made Store example: Monitor website SEO changes.

How it works

Turn on compareWithPrevious and the Actor saves a snapshot of each audit in your own Apify account.

  • First run: saved as the baseline.
  • Every later run: adds a type: "changes" record to the dataset (also stored as CHANGES in the key-value store, plus a readable Markdown report as CHANGES_REPORT).

The changes record lists:

  • newly broken pages (new 404s/5xx or unreachable URLs), recovered pages and every status code change
  • new and fixed broken internal links
  • new and fixed issues per page and site-wide, and the issue count change per severity
  • title, meta description, noindex and canonical changes
  • site score and page score changes, new pages and pages not seen this run
  • new accessibility rule violations and ones no longer found (only when the accessibility scan is on in both runs, see limits below)

Monitoring costs nothing extra. You pay the normal per-page audit fee (see the Store page for current pricing).

Step 1: API token

Create an account at apify.com and copy your token from Settings β†’ API & Integrations:

export APIFY_TOKEN=your_token

Step 2: Run with monitoring on (JavaScript)

npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('batuweb/seo-audit-toolkit').call({
  startUrls: ['https://example.com'],
  crawlMode: 'sitemap',
  maxPages: 50,
  checkBrokenLinks: true,
  compareWithPrevious: true,
  monitoringKey: 'client-a', // optional: separate history per client/site
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const changes = items.find((i) => i.type === 'changes');

if (changes?.status === 'baseline') {
  console.log('Baseline saved. Run again later to see changes.');
} else if (changes) {
  console.log(changes.headline);
  for (const p of changes.newlyBrokenPages) console.log('BROKEN', p.url, p.before, 'β†’', p.after);
  for (const i of changes.newIssues) console.log('NEW', i.code, 'on', i.url);
  if (changes.checksNotCompared?.length) console.log('Not compared:', changes.checksNotCompared);
}

Step 3: Python

pip install apify-client
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("batuweb/seo-audit-toolkit").call(run_input={
    "startUrls": ["https://example.com"],
    "maxPages": 50,
    "compareWithPrevious": True,
    "monitoringKey": "client-a",
})

items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
changes = next((i for i in items if i.get("type") == "changes"), None)

if changes and changes["status"] == "compared":
    print(changes["headline"])
    for name, c in changes["counts"].items():
        if c["totalCount"]:
            print(f"{name}: {c['totalCount']} (listed {c['listedCount']}, omitted {c['omittedCount']})")

Step 4: curl

curl -X POST \
  "https://api.apify.com/v2/acts/batuweb~seo-audit-toolkit/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":["https://example.com"],"maxPages":50,"compareWithPrevious":true,"monitoringKey":"client-a"}'

Step 5: Make it monthly with an Apify Schedule

The option itself does not schedule anything. To get a monthly report:

  1. In Apify Console, open the Actor, enter the input above and save it as a task (one task per client or site works well).
  2. Open Schedules β†’ Create new, pick a monthly interval and add the task.
  3. On the task, add an integration (email, Slack or a webhook) so you're notified when a run finishes. Point it at the CHANGES_REPORT Markdown if you want something human-readable to forward.

Keep the same start URLs and a similar maxPages each month, so the comparison is like for like.

What a changes record looks like

Shape of the record (values illustrative):

{
  "type": "changes",
  "status": "compared",
  "monitoringKey": "client-a",
  "previousRunAt": "…",
  "currentRunAt": "…",
  "headline": "Score 92 β†’ 89 (-3); 1 newly broken page(s), 2 new broken link(s), 3 new / 1 fixed issue(s), 1 title and 0 meta description change(s), 2 new page(s), 0 page(s) not seen this run.",
  "newlyBrokenPages": [{ "url": "https://example.com/pricing/", "before": 200, "after": 404 }],
  "newIssues": [{ "url": "https://example.com/blog/post", "code": "TITLE_TOO_SHORT" }],
  "titleChanges": [{ "url": "https://example.com/", "before": "Old title", "after": "New title" }],
  "checksNotCompared": [],
  "counts": { "newIssues": { "totalCount": 3, "listedCount": 3, "omittedCount": 0 } }
}

Honest limits

  • A check is compared only if it ran in both runs. If checkBrokenLinks was on last month and off now, broken links are listed under checksNotCompared and never reported as "fixed". The same applies to PageSpeed results.
  • Accessibility changes appear only when checkAccessibility is on in both runs. A violation counts as "no longer found" only when the page had a complete scan both times; partial or skipped scans are listed as not compared. The Store example doesn't enable the accessibility scan, so turn it on yourself if you want it. An automated scan finds only part of accessibility barriers and says nothing about legal conformance.
  • Pages not audited this run (outside the page limit, or failed) never count as fixed.
  • Long lists are capped at 100 items in the record and 50 per section in the Markdown report, but the headline and counts always show true totals, with "…and N more" in the report.
  • It reads server-rendered HTML; content rendered only by client-side JavaScript may be missed by the SEO checks.
  • Snapshots live in a named key-value store seo-audit-monitoring in your own account. Use monitoringKey to keep separate histories.
  • Only audit sites you own or have permission to audit.

Related

Disclosure: I built the SEO Audit Toolkit Actor.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.