Cron-Driven SEO Audits in Next.js: The System We Built
When you're shipping 16 industry-specific CRMs—from HotelDesk to LegalEase to PharmaCare—keeping tabs on SEO metadata, Core Web Vitals, and canonical tags across hundreds of pages becomes impossible to do manually. Missing a meta description on a pricing page or letting a 404 slip through for three weeks kills conversions silently.
We needed a system that ran weekly audits, flagged regressions, and sent actionable reports to Slack. No SaaS subscription. No third-party dependency that could change pricing or shut down. Just a cron job, Next.js API routes, and Lighthouse CI running in a GitHub Action.
Here's how we built it, the tradeoffs we made, and what we'd do differently.
Why We Didn't Use Off-the-Shelf Tools
We evaluated Ahrefs, Screaming Frog, and SEMrush. All solid. But:
- Cost: $200-500/month per project doesn't scale when you're maintaining 16 CRMs plus an ERP.
- Custom checks: We needed domain-specific validations—like ensuring every legal case page in LegalEase has structured data for FAQPage, or that EventPro's venue pages include LocalBusiness schema.
- Integration: We wanted audit results to flow directly into our issue tracker and Slack channels, not sit in a dashboard someone has to remember to check.
The trade-off? We own the maintenance. When Lighthouse updates or Next.js changes routing conventions, we adapt. For a remote team building custom software full-time, that's a fair exchange.
The Architecture
The system has three layers:
- Scheduled trigger (GitHub Actions cron)
- Audit runner (Next.js API route)
- Reporter (Slack webhook + optional issue creation)
Layer 1: GitHub Actions Cron
Every Sunday at 3 AM UTC, a GitHub Action fires:
name: Weekly SEO Audit
on:
schedule:
- cron: '0 3 * * 0'
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Lighthouse CI
run: |
npm ci
npm run build
npx @lhci/cli@latest autorun
- name: Trigger audit API
run: |
curl -X POST https://hoteldesk.technova.team/api/seo-audit \
-H "Authorization: Bearer ${{ secrets.AUDIT_TOKEN }}"
We use workflow_dispatch so any team member can trigger an audit manually when deploying big changes.
Layer 2: Next.js API Route
The /api/seo-audit endpoint does the heavy lifting:
- Fetches a sitemap (we generate this dynamically in
/api/sitemap.xmlusing Next.js rewrites) - Loops through priority pages (pricing, landing pages, top 20 blog posts by traffic)
- Runs checks:
- Meta tags: Title length (50-60 chars), description (120-160 chars), Open Graph tags
- Headers: Exactly one H1, logical H2-H6 hierarchy
- Performance: LCP < 2.5s, CLS < 0.1 (parsed from Lighthouse JSON)
- Structured data: Validates JSON-LD against schema.org types
- Canonical tags: No self-referencing loops, HTTPS enforcement
- Image alt text: Every
<img>must have non-emptyalt
Here's a simplified snippet:
import { NextApiRequest, NextApiResponse } from 'next';
import Anthropic from '@anthropic-ai/sdk';
import axios from 'axios';
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.headers.authorization !== `Bearer ${process.env.AUDIT_TOKEN}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
const sitemap = await axios.get('https://hoteldesk.technova.team/api/sitemap.xml');
const urls = parseSitemap(sitemap.data); // extract <loc> tags
const results = [];
for (const url of urls.slice(0, 50)) { // limit to top 50 pages
const html = await axios.get(url);
const issues = await runChecks(html.data, url);
if (issues.length > 0) results.push({ url, issues });
}
await sendToSlack(results);
res.status(200).json({ audited: urls.length, issues: results.length });
}
function runChecks(html: string, url: string): string[] {
const issues = [];
const titleMatch = html.match(/<title>(.*?)<\/title>/);
if (!titleMatch || titleMatch[1].length < 30) {
issues.push('Title tag missing or too short');
}
// ... 15 more checks
return issues;
}
For complex checks—like analyzing whether a blog post's keyword density is natural or spammy—we pipe the HTML into Claude via the Anthropic API. This is overkill for most audits, but for our AI agent specialties landing pages, where we need nuanced copywriting feedback, it's worth the token cost.
Layer 3: Slack Reporting
When issues are found, we post to a dedicated #seo-alerts channel:
🚨 SEO Audit – 12 issues found
❌ /pricing – Missing meta description
❌ /blog/custom-crm-development – LCP 3.2s (target: <2.5s)
⚠️ /products/legalease – H1 appears twice
✅ 47 pages passed
Full report: https://github.com/technova/hoteldesk/actions/runs/123456
For critical issues (missing canonicals, broken structured data), we auto-create GitHub issues with the seo label.
What We Learned
1. Start with the 20% that matters
We initially tried to audit everything—internal link graph, keyword cannibalization, backlink profiles. It took 40 minutes to run and produced 200-line Slack messages no one read.
Now we focus on:
- Metadata completeness (title, description, OG tags)
- Core Web Vitals (LCP, CLS, FID)
- Structured data validity
- Mobile usability (viewport tag, font sizes)
These four categories catch 80% of real SEO problems.
2. Lighthouse in CI is flaky
Running Lighthouse in GitHub Actions gives inconsistent scores because:
- Shared runners have variable CPU
- Network conditions fluctuate
- Cold starts on serverless functions skew metrics
We run each audit three times and take the median. It adds 90 seconds but eliminates false positives.
3. Alerting fatigue is real
In the first month, we got Slack pings for every warning. Team ignored them within a week.
Now we only alert on:
- New issues (regression since last audit)
- Critical severity (missing title tag, 404 errors)
- Trends (LCP degrading for 3 consecutive weeks)
Everything else goes to a weekly digest.
When This Approach Makes Sense
This system works for us because:
- We already have Next.js infrastructure across all projects
- We ship features weekly and need continuous validation
- Our team has bandwidth to maintain custom tooling
- We're building products where SEO directly impacts user acquisition (HotelDesk gets 60% of signups from organic search)
If you're a solo founder or early-stage startup, start with a SaaS tool. The opportunity cost of building this is real. But if you're at the scale where you're managing multiple domains, running experiments, or need compliance-specific checks (HIPAA for PharmaCare, GDPR for EU deployments), owning your audit pipeline pays off.
Next Steps
We're adding:
- Competitor tracking: Scrape top 3 SERP competitors weekly, flag when they outrank us on target keywords
- Content freshness: Auto-flag blog posts older than 12 months with declining traffic
- International SEO: Validate
hreflangtags across our 6 regional deployments
The codebase is internal, but if you're building something similar and want to compare notes, we're on X @TechNovaTeam.
SEO at scale isn't about chasing algorithm updates. It's about catching the unglamorous mistakes—the missing alt tag, the slow API call, the duplicate canonical—before they compound. Automate the boring stuff so you can focus on writing content that actually helps users.
That's the real competitive advantage.