We built a scheduled Cloudflare Worker that runs weekly SEO audits across 450+ AiSalonHub comparison pages, catching broken links, missing metadata, and stale pricing automatically, then reports the health score straight to Telegram. Within the first week it surfaced 34 broken internal links, 47 pages with missing meta descriptions, and 12 pricing tables that referenced deprecated plans — problems silently degrading our crawl budget and conversion rates for months.
The Problem — 450+ Comparison Pages Means SEO Issues Multiply Silently
AiSalonHub hosts comparison pages for every major AI tool. With 450+ pages, each contains meta tags, internal link structures, pricing tables, and structured product metadata. When you have a dozen pages, you manually check them. When you have 450+, problems creep in silently:
| Issue Type | Impact | How It Hides |
|---|---|---|
| Missing meta description | Google auto-generates a bad snippet, lowering CTR | No console error — page still renders |
| Broken internal link | 404 for users, wasted crawl budget | Only surfaces in crawl logs weeks later |
| Stale pricing | Shows outdated plan names | No freshness alert in CMS |
| Thin content | Under 300 words on a page competing for top-3 | No red flag until rankings drop |
**The crawl budget multiplier** — every broken link causes Googlebot to waste a crawl on a dead URL instead of discovering new content. With 450+ pages, a 5% broken-link rate means hundreds of wasted crawls per cycle. **The compounding discovery problem** — a broken link on a top-trafficked page like "Jasper vs Copy.ai" means Googlebot never finds the 5 newer pages that should link from it.
The Solution — Cron-Scheduled SEO Audit Worker
We built a Cloudflare Worker running on a cron schedule (`0 8 * * 1` — every Monday at 8 AM UTC) that performs a full SEO health scan across every published comparison page.
**What it checks, page by page:**
1. **Meta description** — present, between 120-158 characters, unique across the site
2. **Title tag** — present, under 60 characters, contains primary keyword
3. **Internal outbound links** — target resolves to 200, page exists in CMS
4. **Inbound link count** — how many other pages link to this comparison
5. **Content length** — body word count, flag if under 300
6. **Pricing freshness** — last updated; flag if >90 days stale
7. **Schema.org markup** — presence of `Product` or `FAQPage` structured data
8. **Open Graph / Twitter card tags** — present and pointing to valid images
Failures write to a D1 database table (`seo_audit_results`) with severity levels (critical, warning, info). Pages with 2+ critical failures get flagged for immediate review. The weekly health score is a weighted average of all sub-scores (0-100). Anything below 70 triggers a Telegram alert.
Architecture — Workers Cron + D1 Queries + Telegram Webhooks
The full pipeline has three stages, all on Cloudflare's edge:
```
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Cron Trigger │ → │ Audit Worker │ → │ D1 Database │
│ (every Mon 8AM) │ │ (reads CMS, │ │ (seo_audit_results)│
└─────────────────┘ │ checks each page) │ └────────┬─────────┘
└──────────────────┘ │
▼
┌──────────────────┐
│ Telegram Bot │
│ (summary report) │
└──────────────────┘
```
**Stage 1 — CMS Enumeration** queries the CMS API for all published comparison slugs. **Stage 2 — Parallel Audit** fetches each page's HTML in parallel via `Promise.allSettled`, runs 8 checks, and batch-writes to D1. **Stage 3 — Telegram Notification** computes the aggregate health score and sends a structured message with emoji indicators and a top-issues breakdown.
Implementation — Audit Checks Code and Reporting Format
Here is the core audit logic, reusable for any content-heavy site:
```javascript
function runAllChecks(html, slug) {
const checks = [];
// 1. Meta description
const metaDesc = html.match(/<meta name="description" content="([^"]+)"/i);
const descLen = metaDesc ? metaDesc[1].length : 0;
checks.push({
check: "meta_description",
status: (metaDesc && descLen >= 120 && descLen <= 158) ? "pass" : "fail",
severity: descLen === 0 ? "critical" : "warning",
detail: metaDesc ? `${descLen} chars` : "missing"
});
// 2. Content length
const bodyText = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
const wordCount = bodyText.split(" ").length;
checks.push({
check: "content_length",
status: wordCount >= 300 ? "pass" : "fail",
severity: wordCount < 200 ? "critical" : "warning",
detail: `${wordCount} words`
});
// 3. Pricing freshness (from CMS metadata in a script tag)
const pricingMatch = html.match(/"lastPricingUpdate":"([^"]+)"/);
if (pricingMatch) {
const updated = new Date(pricingMatch[1]);
const daysStale = Math.floor((Date.now() - updated) / 86400000);
checks.push({
check: "pricing_freshness",
status: daysStale <= 90 ? "pass" : "fail",
severity: daysStale > 180 ? "critical" : "warning",
detail: `${daysStale} days stale`
});
}
return checks;
}
```
The Worker's `wrangler.toml` is minimal — just bindings for D1, the cron trigger, and three environment variables (Telegram bot token, chat ID, CMS API token). The weekly report is also stored as a JSON blob in D1 for historical trend queries.
Results — Real Issues Caught in First Week, Improved Crawl Rate
The first Monday audit (Week 18, 2026) of 456 comparison pages produced sobering numbers:
| Metric | Value |
|---|---|
| Pages audited | 456 |
| Pages with at least one failure | 144 (31.6%) |
| Critical failures total | 67 |
| Warning-level issues | 139 |
| Average health score | 72/100 |
| Lowest single page score | 31/100 |
**The worst offender** was a "ChatGPT vs Claude" comparison with no meta description, 2 broken internal links, a 187-word body, and pricing data last updated 14 months ago — scoring 31/100. After rewrites, its SERP position improved from #9 to #4 within 2 weeks.
**Crawl rate improvement** was observable within 3 weeks. Google Search Console showed a 23% increase in crawled pages per day on comparison URLs after fixing all 34 broken links. The fix cleared crawl path blockages that had been silently accumulating.
**Pricing staleness** affected 12 pages referencing deprecated plans (e.g., Jasper's old "Boss Mode" now called "Creator Pro"). These pages were actively misleading visitors and likely hurting conversion rates. Updating them cost about 2 hours of editorial work but recovered an estimated 5-8% conversion lift on those pages.
**Telegram alerts worked** — within 30 minutes of the first audit completing, a content editor had already claimed the top-5 critical pages and started fixes. The zero-friction notification loop (no login, no dashboard, just a Telegram message) was the difference between auditing theoretically and auditing effectively.
Key Takeaways
- **380% more issues than expected** — manual spot-checking suggested maybe 5-10 problem pages. The automated audit found 144. You cannot audit content at scale without automation.
- **Crawl budget is a hidden cost** — 34 broken links were silently wasting Googlebot capacity. Fixing them gained a 23% crawl rate improvement with zero new content.
- **Pricing staleness is existential** — showing obsolete pricing on comparison pages destroys trust and conversions. A 90-day freshness threshold caught 12 pages needing updates.
- **Telegram as alert infrastructure** — the simplest notification channel won because editors already live there. No dashboard fatigue, no ignored emails. The alert format drove immediate action.
- **Start with a cron Worker, not a platform** — we shipped a Worker in 3 days covering 90% of what a $500/month tool would do, costing ~$2/month in Cloudflare Workers usage.