On this page
Running a technical SEO audit manually on a 500-page site takes 6-8 hours. Checking meta tags, validating heading hierarchy, finding broken links, analyzing content quality -- it is tedious, error-prone work that most teams do quarterly at best (Google's own Lighthouse audit tool helps, but does not scale to whole-site crawls). By the time you finish, half the issues have already cost you rankings.
CrawlForge turns a full-day SEO audit into a 15-minute automated workflow. This guide shows you how to build a complete SEO audit pipeline that checks every page on your site for 20+ ranking factors and generates a prioritized action report.
Table of Contents
- What This SEO Audit Covers
- Architecture Overview
- Step 1: Crawl and Map Your Site
- Step 2: Extract and Validate Metadata
- Step 3: Analyze Content Quality
- Step 4: Check Links and Site Structure
- Step 5: Generate the Audit Report
- Credit Cost Analysis
- Results and Benefits
- Frequently Asked Questions
What This SEO Audit Covers
This automated audit checks for the most impactful technical SEO factors:
| Category | Checks | Impact |
|---|---|---|
| Meta tags | Title length, description, Open Graph, canonical | High |
| Content | Word count, heading hierarchy, keyword density | High |
| Links | Broken links, orphan pages, redirect chains | Medium |
| Structure | Sitemap coverage, crawl depth, URL patterns | Medium |
| Performance | Page size, response time, render-blocking resources | Medium |
What is a technical SEO audit? It is a systematic review of a website's technical health factors that affect search engine crawling, indexing, and ranking. Unlike content audits that focus on what you write, technical audits focus on how search engines discover and process your pages -- including signals like Core Web Vitals and proper robots.txt configuration.
Architecture Overview
The audit pipeline uses five CrawlForge tools:
| Step | Tool | Credits | Purpose |
|---|---|---|---|
| Site discovery | map_site | 3 | Generate complete URL inventory |
| Deep crawl | crawl_deep | 5 | Traverse site structure and find issues |
| Metadata check | extract_metadata | 1 | Validate meta tags per page |
| Content analysis | analyze_content | 3 | Score content quality and readability |
| Link audit | extract_links | 1 | Find broken and orphan links |
Step 1: Crawl and Map Your Site
Start with a complete inventory of every page on your site.
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const client = new Client({
name: 'seo-auditor',
version: '1.0.0',
});
interface SiteAuditConfig {
url: string;
maxPages: number;
maxDepth: number;
}
async function crawlSite(config: SiteAuditConfig) {
// First, get the sitemap for a complete URL list
const siteMap = await client.callTool({
name: 'map_site',
arguments: {
url: config.url,
max_urls: config.maxPages,
include_sitemap: true,
include_metadata: true,
group_by_path: true,
},
});
const mapResult = JSON.parse(siteMap.content[0].text);
// Then crawl to discover pages not in the sitemap
const crawlResult = await client.callTool({
name: 'crawl_deep',
arguments: {
url: config.url,
max_pages: config.maxPages,
max_depth: config.maxDepth,
extract_content: true,
respect_robots: true,
},
});
const crawled = JSON.parse(crawlResult.content[0].text);
// Combine and deduplicate
const allUrls = new Set([
...mapResult.urls,
...crawled.pages.map((p: { url: string }) => p.url),
]);
// Flag pages in crawl but missing from sitemap
const missingFromSitemap = crawled.pages
.filter((p: { url: string }) => !mapResult.urls.includes(p.url))
.map((p: { url: string }) => p.url);
return {
totalPages: allUrls.size,
urls: Array.from(allUrls),
missingFromSitemap,
crawlDepthDistribution: crawled.depthDistribution,
};
}This step identifies orphan pages (crawlable but not in your sitemap) and gives you a depth distribution showing how many clicks it takes to reach each page.
Step 2: Extract and Validate Metadata
Check every page for meta tag issues that hurt rankings.
interface MetaIssue {
url: string;
type: 'error' | 'warning';
message: string;
}
async function auditMetadata(urls: string[]): Promise<MetaIssue[]> {
const issues: MetaIssue[] = [];
// Batch process metadata extraction
const batchResult = await client.callTool({
name: 'batch_scrape',
arguments: {
urls: urls.map(url => ({ url })),
includeMetadata: true,
maxConcurrency: 15,
},
});
const results = JSON.parse(batchResult.content[0].text);
for (const page of results.results) {
const meta = page.metadata;
const url = page.url;
// Title checks
if (!meta?.title) {
issues.push({ url, type: 'error', message: 'Missing title tag' });
} else if (meta.title.length > 60) {
issues.push({ url, type: 'warning', message: `Title too long (${meta.title.length} chars, max 60)` });
} else if (meta.title.length < 30) {
issues.push({ url, type: 'warning', message: `Title too short (${meta.title.length} chars, min 30)` });
}
// Description checks
if (!meta?.description) {
issues.push({ url, type: 'error', message: 'Missing meta description' });
} else if (meta.description.length > 155) {
issues.push({ url, type: 'warning', message: `Description too long (${meta.description.length} chars)` });
}
// Open Graph checks
if (!meta?.ogTitle) {
issues.push({ url, type: 'warning', message: 'Missing og:title' });
}
if (!meta?.ogImage) {
issues.push({ url, type: 'warning', message: 'Missing og:image' });
}
// Canonical check
if (!meta?.canonical) {
issues.push({ url, type: 'warning', message: 'Missing canonical URL' });
}
}
return issues;
}Step 3: Analyze Content Quality
Use analyze_content to score readability, keyword presence, and content depth for your most important pages.
interface ContentScore {
url: string;
wordCount: number;
readabilityScore: number;
topTopics: string[];
sentiment: string;
issues: string[];
}
async function analyzePageContent(url: string): Promise<ContentScore> {
// First extract clean content
const content = await client.callTool({
name: 'extract_content',
arguments: { url },
});
const extracted = JSON.parse(content.content[0].text);
// Then analyze it
const analysis = await client.callTool({
name: 'analyze_content',
arguments: {
text: extracted.content,
},
});
const result = JSON.parse(analysis.content[0].text);
const issues: string[] = [];
// Flag thin content (under 300 words)
if (result.wordCount < 300) {
issues.push(`Thin content: only ${result.wordCount} words (recommend 800+)`);
}
// Flag low readability
if (result.readabilityScore < 40) {
issues.push('Content may be too complex for general audience');
}
return {
url,
wordCount: result.wordCount,
readabilityScore: result.readabilityScore,
topTopics: result.topics || [],
sentiment: result.sentiment || 'neutral',
issues,
};
}Step 4: Check Links and Site Structure
Broken links hurt both user experience and SEO. Extract all links and verify they resolve correctly.
interface LinkIssue {
sourceUrl: string;
targetUrl: string;
type: 'broken' | 'redirect' | 'external-nofollow';
statusCode?: number;
}
async function auditLinks(urls: string[]): Promise<LinkIssue[]> {
const issues: LinkIssue[] = [];
for (const url of urls) {
const linksResult = await client.callTool({
name: 'extract_links',
arguments: {
url,
base_url: urls[0], // Use site root as base
},
});
const links = JSON.parse(linksResult.content[0].text);
// Check each internal link
for (const link of links.internal || []) {
try {
const check = await client.callTool({
name: 'fetch_url',
arguments: { url: link.href, timeout: 5000 },
});
const response = JSON.parse(check.content[0].text);
if (response.statusCode >= 400) {
issues.push({
sourceUrl: url,
targetUrl: link.href,
type: 'broken',
statusCode: response.statusCode,
});
} else if (response.statusCode >= 300) {
issues.push({
sourceUrl: url,
targetUrl: link.href,
type: 'redirect',
statusCode: response.statusCode,
});
}
} catch {
issues.push({
sourceUrl: url,
targetUrl: link.href,
type: 'broken',
});
}
}
}
return issues;
}Step 5: Generate the Audit Report
Combine all findings into a prioritized report.
interface SEOAuditReport {
siteUrl: string;
auditDate: string;
totalPages: number;
overallScore: number;
criticalIssues: number;
warnings: number;
sections: {
metadata: MetaIssue[];
content: ContentScore[];
links: LinkIssue[];
sitemapGaps: string[];
};
}
async function generateReport(siteUrl: string): Promise<SEOAuditReport> {
// Run all audit steps
const site = await crawlSite({
url: siteUrl,
maxPages: 200,
maxDepth: 4,
});
const metaIssues = await auditMetadata(site.urls);
const linkIssues = await auditLinks(site.urls.slice(0, 50)); // Top 50 pages
// Analyze top pages by importance
const topPages = site.urls.slice(0, 20);
const contentScores: ContentScore[] = [];
for (const url of topPages) {
contentScores.push(await analyzePageContent(url));
}
const criticalCount = metaIssues.filter(i => i.type === 'error').length
+ linkIssues.filter(i => i.type === 'broken').length;
const warningCount = metaIssues.filter(i => i.type === 'warning').length
+ contentScores.reduce((sum, s) => sum + s.issues.length, 0);
// Score: start at 100, deduct for issues
const score = Math.max(0, 100 - (criticalCount * 5) - (warningCount * 2));
return {
siteUrl,
auditDate: new Date().toISOString(),
totalPages: site.totalPages,
overallScore: score,
criticalIssues: criticalCount,
warnings: warningCount,
sections: {
metadata: metaIssues,
content: contentScores,
links: linkIssues,
sitemapGaps: site.missingFromSitemap,
},
};
}Credit Cost Analysis
For a 200-page site audit:
| Operation | Tool | Credits | Quantity | Subtotal |
|---|---|---|---|---|
| Site mapping | map_site | 3 | 1 | 3 |
| Deep crawl | crawl_deep | 5 | 1 | 5 |
| Metadata (batch) | batch_scrape | 5 | 4 batches | 20 |
| Content analysis | analyze_content | 3 | 20 pages | 60 |
| Content extraction | extract_content | 2 | 20 pages | 40 |
| Link extraction | extract_links | 1 | 50 pages | 50 |
| Link verification | fetch_url | 1 | 200 checks | 200 |
| Total per audit | 378 credits |
Running monthly audits costs about 378 credits -- well within the Hobby plan's 3,000 monthly credits. The Free tier (1,000 credits) covers 2-3 full audits.
Results and Benefits
Automated SEO audits with CrawlForge deliver:
- Speed: Complete a 200-page audit in 15 minutes instead of 8 hours
- Consistency: Same checks every time, no human oversight gaps
- Frequency: Move from quarterly to weekly audits without added cost
- Prioritization: Issues ranked by severity so you fix what matters first
Teams using automated SEO auditing typically see a 15-25% improvement in technical SEO scores within 3 months, according to Ahrefs' research on technical SEO impact.
Frequently Asked Questions
How does this compare to paid SEO audit tools like Screaming Frog?
CrawlForge's audit pipeline is more flexible because you control exactly what gets checked and how issues are scored. Tools like Screaming Frog cost $259/year for a desktop license, while CrawlForge's credit model lets you run audits on-demand. CrawlForge is best for teams that want programmable, AI-integrated SEO workflows rather than a GUI tool.
Can I schedule audits to run automatically?
Yes. Wrap the generateReport function in a cron job or serverless function (Vercel Cron, AWS Lambda) and trigger it on your preferred schedule. Weekly audits are the sweet spot for most sites.
What about JavaScript-rendered pages?
CrawlForge handles JavaScript rendering automatically. For pages that require interaction (clicking tabs, expanding sections), use scrape_with_actions to render the full content before analysis.
Start auditing your site today. Get 1,000 free credits -- enough for 2-3 complete site audits. No credit card required.
Related resources:
Try this yourself — no signup needed
Run any of CrawlForge's 27 scraping and extraction tools in the playground, then start free with 1,000 credits.
1,000 free credits • Refills monthly • No credit card required
Tags
About the Author
Stay updated with the latest insights
Get tutorials, product updates, and web scraping tips delivered to your inbox.
No spam. Unsubscribe anytime.