A comprehensive SEO audit command-line tool with 251 audit rules across 20 categories. Analyze any website for SEO best practices, Core Web Vitals, security headers, structured data, accessibility, JavaScript rendering, redirect chains, and more.
Prefer a web interface? Try our Free SEO Audit Tool for a visual, browser-based SEO analysis.
- 251 SEO Audit Rules across 20 categories
- Single Page & Crawl Mode - Audit one page or crawl entire sites
- Core Web Vitals - LCP, CLS, FCP, TTFB, INP measurement via Playwright
- JavaScript Rendering Analysis - Compare raw vs rendered DOM for SPA/CSR sites
- 5 Output Formats - Console, JSON, HTML, Markdown, and LLM-optimized XML
- AI/GEO Readiness - Check semantic HTML, AI bot access, and llms.txt
- Redirect Chain Detection - Loops, broken redirects, meta/JS redirects
- HTML Validation - Doctype, charset, head structure, lorem ipsum detection
- Cross-Page Analysis - Duplicate content detection, orphan pages, pagination
- Concurrent Crawling - Fast multi-page audits with configurable concurrency
- SQLite Storage - Persistent crawl data with compression and audit history
- CI/CD Ready - Exit codes, JSON output, GitHub Actions & GitLab CI examples
- TOML Configuration - Project-level settings with presets and inheritance
# Install globally
npm install -g @seomator/seo-audit
# Run audit
seomator audit https://example.comNote: The CLI automatically uses your system Chrome, Chromium, or Edge browser for Core Web Vitals measurement. No additional browser installation is required if you have Chrome installed.
git clone https://github.com/seo-skills/seo-audit-skill.git
cd seo-audit-skill
npm install
npm run build
# Run directly
./dist/cli.js audit https://example.com
# Or link globally
npm link
seomator audit https://example.com# Basic audit
seomator audit https://example.com
# Skip Core Web Vitals (faster)
seomator audit https://example.com --no-cwv
# Audit specific categories
seomator audit https://example.com -c core,security,perf
# JSON output (for CI/CD or parsing)
seomator audit https://example.com --format json
# HTML report
seomator audit https://example.com --format html -o report.html
# LLM-optimized output (pipe to Claude)
seomator audit https://example.com --format llm --no-cwv | claude "analyze and prioritize fixes"
# Crawl multiple pages
seomator audit https://example.com --crawl --max-pages 20
# Full options
seomator audit https://example.com --crawl -m 50 --concurrency 5 --timeout 60000 --format json -o results.jsonRun SEO audit on a URL.
| Option | Alias | Description | Default |
|---|---|---|---|
--format <type> |
-f |
Output format: console, json, html, markdown, llm | console |
--output <path> |
-o |
Output file path | - |
--categories <list> |
-c |
Comma-separated categories to audit | All |
--json |
-j |
Output as JSON (deprecated, use --format json) | false |
--crawl |
- | Enable crawl mode for multiple pages | false |
--max-pages <n> |
-m |
Maximum pages to crawl | 10 |
--concurrency <n> |
- | Concurrent requests | 3 |
--timeout <ms> |
- | Request timeout in milliseconds | 30000 |
--no-cwv |
- | Skip Core Web Vitals measurement | false |
--verbose |
-v |
Show progress | false |
--refresh |
-r |
Ignore cache, fetch fresh | false |
--resume |
- | Resume interrupted crawl | false |
--config <path> |
- | Config file path | - |
--save |
- | Save report to .seomator/reports/ | false |
Create a seomator.toml config file.
seomator init # Interactive setup
seomator init -y # Use defaults
seomator init --preset blog # Blog preset
seomator init --preset ecommerce # E-commerce preset
seomator init --preset ci # Minimal CI configCrawl website without running analysis. Saves data for later analysis with seomator analyze.
seomator crawl https://example.com -m 20
seomator crawl https://example.com --refresh
seomator crawl https://example.com --resumeRun rules on stored crawl data.
seomator analyze # Analyze latest crawl
seomator analyze --latest --save # Analyze and save
seomator analyze 2026-01-23-abc123 # Specific crawlView and query past reports.
seomator report --list # List all reports
seomator report --project mysite # Filter by projectView or modify configuration.
seomator config --list # Show all config
seomator config crawler.max_pages 50 # Set value
seomator config validate # Validate configDatabase management.
seomator db migrate # Migrate JSON to SQLite
seomator db migrate --dry-run # Preview migration
seomator db stats -v # Database statistics
seomator db restore # Rollback migrationCheck system setup and dependencies.
seomator self doctor -v # Verbose diagnostics| Code | Meaning |
|---|---|
| 0 | Audit passed (score >= 70) |
| 1 | Audit failed (score < 70) |
| 2 | Error occurred |
| Rule | Description |
|---|---|
core-title-present |
<title> tag exists |
core-title-length |
Title should be 30-60 characters |
core-description-present |
Meta description exists |
core-description-length |
Description should be 120-160 characters |
core-canonical-present |
Canonical URL exists |
core-canonical-valid |
Canonical URL is valid absolute URL |
core-viewport-present |
Viewport meta tag exists |
core-favicon-present |
Favicon link exists |
core-h1-present |
At least one H1 exists |
core-h1-single |
Only one H1 exists |
core-canonical-header |
HTML canonical and Link header match |
core-nosnippet |
Detects nosnippet/max-snippet:0 directives |
core-robots-meta |
Checks noindex/nofollow directives |
core-title-unique |
Titles should be unique site-wide |
core-canonical-conflicting |
HTML and header canonicals should not conflict |
core-canonical-to-homepage |
Canonical should not always point to homepage |
core-canonical-http-mismatch |
Canonical protocol should match page protocol |
core-canonical-loop |
Detects circular canonical chains |
core-canonical-to-noindex |
Canonical should not point to noindexed page |
| Rule | Description |
|---|---|
cwv-lcp |
Largest Contentful Paint (<2.5s pass, >4s fail) |
cwv-cls |
Cumulative Layout Shift (<0.1 pass, >0.25 fail) |
cwv-inp |
Interaction to Next Paint (<200ms pass, >500ms fail) |
cwv-ttfb |
Time to First Byte (<800ms pass, >1800ms fail) |
cwv-fcp |
First Contentful Paint (<1.8s pass, >3s fail) |
perf-dom-size |
DOM should have <1500 nodes |
perf-css-file-size |
CSS files should be reasonably sized |
perf-font-loading |
Font-display: swap should be used |
perf-preconnect |
Preconnect hints for third-party origins |
perf-render-blocking |
Scripts should use async/defer |
perf-lazy-above-fold |
Above-fold images should not be lazy-loaded |
perf-lcp-hints |
LCP element should be preloaded |
perf-text-compression |
Responses should use gzip/Brotli compression |
perf-brotli |
Prefer Brotli over gzip for better compression |
perf-cache-policy |
Static assets should have cache headers |
perf-minify-css |
CSS should be minified |
perf-minify-js |
JavaScript should be minified |
perf-response-time |
Server response time should be <200ms |
perf-http2 |
Site should serve over HTTP/2 |
perf-page-weight |
Total page size should be <3MB |
perf-js-file-size |
Individual JS files should be <500KB |
perf-video-for-animations |
Use <video> instead of animated GIFs |
| Rule | Description |
|---|---|
links-broken-internal |
Internal links should return 200 |
links-external-valid |
External links should be reachable |
links-internal-present |
Page should have internal links |
links-nofollow-appropriate |
nofollow used appropriately |
links-anchor-text |
Anchor text should be descriptive |
links-depth |
Page depth should be ≤3 from homepage |
links-dead-end-pages |
Pages should have outgoing internal links |
links-https-downgrade |
HTTPS pages should not link to HTTP |
links-external-count |
Warn if >100 external links |
links-invalid-links |
No empty, javascript:, or malformed hrefs |
links-tel-mailto |
Valid tel: and mailto: link formats |
links-redirect-chains |
Links should not go through redirects |
links-orphan-pages |
Pages should have incoming links |
links-localhost |
No localhost/127.0.0.1 URLs in production |
links-local-file |
No file:// protocol links |
links-broken-fragment |
Fragment links should match element IDs |
links-excessive |
Limit internal links per page |
links-onclick |
No onclick-based navigation instead of <a> tags |
links-whitespace-href |
No whitespace in href attributes |
| Rule | Description |
|---|---|
images-alt-present |
All images should have alt attribute |
images-alt-quality |
Alt text should be descriptive |
images-dimensions |
Images should have width/height |
images-lazy-loading |
Below-fold images should use lazy loading |
images-modern-format |
Use WebP/AVIF formats |
images-size |
Images should be <200KB |
images-responsive |
Use srcset for responsive images |
images-broken |
Images should not return 404 |
images-figure-captions |
Figure elements should have figcaption |
images-filename-quality |
Use descriptive filenames |
images-inline-svg-size |
Inline SVGs should be <5KB |
images-picture-element |
Picture elements must have img fallback |
images-alt-length |
Alt text should be under 125 characters |
images-background-seo |
Content images should use <img>, not CSS background |
| Rule | Description |
|---|---|
security-https |
Site should use HTTPS |
security-https-redirect |
HTTP should redirect to HTTPS |
security-hsts |
Strict-Transport-Security header |
security-csp |
Content-Security-Policy header |
security-x-frame |
X-Frame-Options header |
security-x-content-type |
X-Content-Type-Options: nosniff |
security-external-links |
External target="_blank" links have noopener/noreferrer |
security-form-https |
Form actions use HTTPS |
security-mixed-content |
No HTTP resources on HTTPS pages |
security-permissions-policy |
Permissions-Policy header present |
security-referrer-policy |
Referrer-Policy header present |
security-leaked-secrets |
No exposed API keys or credentials |
security-password-http |
Login forms served over HTTPS only |
security-protocol-relative |
No protocol-relative URLs (//example.com) |
security-ssl-expiry |
SSL certificate not near expiration |
security-ssl-protocol |
TLS 1.2+ required; no TLS 1.0/1.1 |
| Rule | Description |
|---|---|
technical-robots-txt-exists |
robots.txt should return 200 |
technical-robots-txt-valid |
robots.txt should have valid syntax |
technical-sitemap-exists |
sitemap.xml should exist |
technical-sitemap-valid |
Sitemap should have valid XML structure |
technical-url-structure |
URL should use hyphens, lowercase |
technical-trailing-slash |
Consistent trailing slash usage |
technical-www-redirect |
www/non-www should redirect to one version |
technical-404-page |
Custom 404 page should exist |
technical-soft-404 |
Soft 404 pages should return proper 404 status |
technical-server-error |
No 5xx server errors |
technical-4xx-non-404 |
No 400, 403, 410 client errors |
technical-timeout |
Pages should respond within timeout |
technical-bad-content-type |
Content-Type header matches actual content |
| Rule | Description |
|---|---|
crawl-schema-noindex-conflict |
Schema.org and noindex should not conflict |
crawl-pagination-canonical |
Paginated pages should self-canonicalize |
crawl-sitemap-domain |
Sitemap URLs should match host domain |
crawl-noindex-in-sitemap |
Noindexed pages should not be in sitemap |
crawl-indexability-conflict |
robots.txt and noindex should not both block |
crawl-canonical-redirect |
Canonical should not point through redirects |
crawl-sitemap-url-limit |
Sitemap should have <50,000 URLs |
crawl-sitemap-size-limit |
Sitemap should be <50MB |
crawl-sitemap-duplicate-urls |
No duplicate URLs in sitemap |
crawl-sitemap-orphan-urls |
Sitemap URLs should be linked internally |
crawl-blocked-resources |
Critical resources not blocked by robots.txt |
crawl-crawl-delay |
Excessive Crawl-delay slows indexing |
crawl-sitemap-in-robotstxt |
robots.txt should reference sitemap |
crawl-pagination-broken |
Pagination links should not be broken |
crawl-pagination-loop |
No circular pagination chains |
crawl-pagination-sequence |
No gaps in pagination sequence |
crawl-pagination-noindex |
Paginated pages should not be noindexed |
crawl-pagination-orphaned |
Paginated pages should be linked from content |
| Rule | Description |
|---|---|
schema-present |
JSON-LD or microdata should exist |
schema-valid |
JSON-LD should be valid JSON |
schema-type |
@type field should be present |
schema-required-fields |
Required fields for schema type |
schema-article |
Validates Article schema properties |
schema-breadcrumb |
Checks BreadcrumbList on non-homepage |
schema-faq |
Validates FAQPage schema structure |
schema-local-business |
Validates LocalBusiness for local SEO |
schema-organization |
Validates Organization schema |
schema-product |
Validates Product schema for e-commerce |
schema-review |
Validates Review/AggregateRating schema |
schema-video |
Validates VideoObject schema |
schema-website-search |
Checks WebSite sitelinks searchbox |
| Rule | Description |
|---|---|
js-rendered-title |
Title present in rendered DOM |
js-rendered-description |
Meta description present in rendered DOM |
js-rendered-h1 |
H1 present in rendered DOM |
js-rendered-canonical |
Canonical present in rendered DOM |
js-canonical-mismatch |
Canonical matches between raw and rendered HTML |
js-noindex-mismatch |
Noindex consistent between raw and rendered HTML |
js-title-modified |
Title not changed by JavaScript |
js-description-modified |
Description not changed by JavaScript |
js-h1-modified |
H1 not changed by JavaScript |
js-rendered-content |
Main content present without JavaScript dependency |
js-rendered-links |
Navigation links present without JavaScript |
js-blocked-resources |
Critical JS not blocked by robots.txt |
js-ssr-check |
Server-side rendering detected |
| Rule | Description |
|---|---|
a11y-aria-labels |
Interactive elements have accessible names |
a11y-color-contrast |
Color contrast issues detected |
a11y-focus-visible |
Focus indicator styles present |
a11y-form-labels |
Form inputs have associated labels |
a11y-heading-order |
Heading levels don't skip |
a11y-landmark-regions |
Proper landmark regions (main, nav, footer) |
a11y-link-text |
Descriptive link text (no "click here") |
a11y-skip-link |
Skip-to-content link for keyboard navigation |
a11y-table-headers |
Data tables have proper headers |
a11y-touch-targets |
Minimum 44x44px touch target sizing |
a11y-video-captions |
Videos have captions or transcripts |
a11y-zoom-disabled |
Viewport doesn't disable user zoom |
| Rule | Description |
|---|---|
content-word-count |
Page should have 300+ words |
content-reading-level |
Flesch-Kincaid reading level check |
content-keyword-stuffing |
Detects excessive keyword repetition |
content-article-links |
Checks link-to-content ratio |
content-broken-html |
Detects malformed HTML structure |
content-meta-in-body |
Meta tags should be in head |
content-mime-type |
Validates Content-Type header |
content-duplicate-description |
Descriptions should be unique site-wide |
content-heading-hierarchy |
Proper heading hierarchy (H1>H2>H3) |
content-heading-length |
Headings should be 10-70 characters |
content-heading-unique |
Headings should be unique |
content-text-html-ratio |
Text-to-HTML ratio should be >10% |
content-title-same-as-h1 |
Title and H1 should differ |
content-title-pixel-width |
Title pixel width for SERP display (<580px) |
content-description-pixel-width |
Description pixel width for SERP (<920px) |
content-duplicate-exact |
Detects exact duplicate content across pages |
content-duplicate-near |
Detects near-duplicate content via simhash |
| Rule | Description |
|---|---|
social-og-title |
og:title meta tag |
social-og-description |
og:description meta tag |
social-og-image |
og:image with valid URL |
social-og-image-size |
og:image dimensions (1200x630) |
social-twitter-card |
twitter:card meta tag |
social-og-url |
og:url meta tag |
social-og-url-canonical |
og:url matches canonical |
social-share-buttons |
Social share buttons present |
social-social-profiles |
Social profile links present |
| Rule | Description |
|---|---|
eeat-about-page |
About page exists |
eeat-affiliate-disclosure |
Affiliate links have disclosure |
eeat-author-byline |
Author attribution present |
eeat-author-expertise |
Author credentials/bio present |
eeat-citations |
Links to authoritative sources |
eeat-contact-page |
Contact page exists |
eeat-content-dates |
Publication/modification dates present |
eeat-disclaimers |
YMYL content has disclaimers |
eeat-editorial-policy |
Editorial policy page exists |
eeat-physical-address |
Business address present |
eeat-privacy-policy |
Privacy policy link present |
eeat-terms-of-service |
Terms of service link present |
eeat-trust-signals |
Trust badges, reviews, certifications |
eeat-ymyl-detection |
YMYL content detection |
| Rule | Description |
|---|---|
url-slug-keywords |
URL slug contains keywords |
url-stop-words |
URL should not have stop words |
url-uppercase |
URLs should be lowercase |
url-underscores |
Use hyphens, not underscores |
url-double-slash |
No consecutive slashes in path |
url-spaces |
No spaces in URL |
url-non-ascii |
No non-ASCII characters in URL |
url-length |
URL should be under 2048 characters |
url-repetitive-path |
No repetitive path segments |
url-parameters |
Excessive query parameters |
url-session-ids |
No session IDs in URL |
url-tracking-params |
Tracking parameters should use canonical |
url-internal-search |
Internal search URLs should be noindexed |
url-http-https-duplicate |
HTTP/HTTPS versions should canonicalize |
| Rule | Description |
|---|---|
redirect-meta-refresh |
No <meta http-equiv="refresh"> redirects |
redirect-javascript |
No JavaScript-based redirects |
redirect-http-refresh |
No HTTP Refresh header redirects |
redirect-loop |
No circular redirect chains |
redirect-type |
Prefer 301 over 302 for permanent moves |
redirect-broken |
Redirects should not lead to errors |
redirect-resource |
No redirects on CSS/JS/image resources |
redirect-case-normalization |
Redirect uppercase URLs to lowercase |
| Rule | Description |
|---|---|
mobile-font-size |
Minimum 16px body text |
mobile-horizontal-scroll |
No horizontal scrolling |
mobile-interstitials |
No intrusive interstitials |
mobile-viewport-width |
No fixed viewport width |
mobile-multiple-viewports |
Single viewport meta tag |
| Rule | Description |
|---|---|
i18n-lang-attribute |
HTML lang attribute present |
i18n-hreflang |
Hreflang tags for multilingual sites |
i18n-hreflang-return-links |
Hreflang targets link back to source |
i18n-hreflang-to-noindex |
Hreflang should not point to noindexed pages |
i18n-hreflang-to-non-canonical |
Hreflang should point to canonical URLs |
i18n-hreflang-to-broken |
Hreflang should not point to broken URLs |
i18n-hreflang-to-redirect |
Hreflang should not point through redirects |
i18n-hreflang-conflicting |
No duplicate hreflang for same language |
i18n-hreflang-lang-mismatch |
Page language matches hreflang code |
i18n-hreflang-multiple-methods |
Use single hreflang method |
| Rule | Description |
|---|---|
htmlval-missing-doctype |
<!DOCTYPE html> must be present |
htmlval-missing-charset |
<meta charset> must be in head |
htmlval-invalid-head |
Only metadata elements in <head> |
htmlval-noscript-in-head |
<noscript> should be in body |
htmlval-multiple-heads |
Single <head> element only |
htmlval-size-limit |
HTML should be under 5MB |
htmlval-lorem-ipsum |
No placeholder lorem ipsum text |
htmlval-multiple-titles |
Single <title> tag only |
htmlval-multiple-descriptions |
Single meta description only |
| Rule | Description |
|---|---|
geo-semantic-html |
Uses semantic HTML elements |
geo-content-structure |
Proper heading hierarchy and lists |
geo-ai-bot-access |
AI crawlers (GPTBot, ClaudeBot) not blocked |
geo-llms-txt |
/llms.txt file for AI discovery |
geo-schema-drift |
JSON-LD matches visible content |
| Rule | Description |
|---|---|
legal-cookie-consent |
Cookie consent banner present |
Create a seomator.toml config file with seomator init:
[project]
name = "my-website"
domains = ["example.com", "www.example.com"]
[crawler]
max_pages = 100
concurrency = 3
timeout_ms = 30000
respect_robots = true
delay_ms = 100
include = []
exclude = ["/admin/**", "/api/**"]
drop_query_prefixes = ["utm_", "gclid", "fbclid"]
[rules]
enable = ["*"]
disable = ["perf-inp"] # Supports wildcards: "core-*"
[output]
format = "console" # console, json, html, markdown, llmConfig priority (highest to lowest):
- CLI arguments
- Local
./seomator.toml - Parent directory configs
- Global
~/.seomator/config.toml - Built-in defaults
| Format | Flag | Best For |
|---|---|---|
| Console | --format console |
Human terminal output (default) |
| JSON | --format json |
CI/CD, programmatic processing |
| HTML | --format html |
Standalone reports, sharing |
| Markdown | --format markdown |
Documentation, GitHub |
| LLM | --format llm |
AI agents, piping to Claude |
╔══════════════════════════════════════════════════════════════╗
║ SEOmator Audit Report ║
╚══════════════════════════════════════════════════════════════╝
URL: https://example.com
Score: 88/100 [A]
┌──────────────────────────┬───────┬────────┬──────────┬────────┐
│ Category │ Score │ Passed │ Warnings │ Failed │
├──────────────────────────┼───────┼────────┼──────────┼────────┤
│ Core │ 97 │ 18 │ 1 │ 0 │
│ Performance │ 85 │ 18 │ 3 │ 1 │
│ JavaScript Rendering │ 100 │ 13 │ 0 │ 0 │
│ ... │ │ │ │ │
└──────────────────────────┴───────┴────────┴──────────┴────────┘
{
"url": "https://example.com",
"overallScore": 88,
"categoryResults": [
{
"categoryId": "core",
"score": 97,
"passCount": 18,
"warnCount": 1,
"failCount": 0,
"results": [...]
}
],
"timestamp": "2026-01-23T16:00:00.000Z",
"crawledPages": 1
}name: SEO Audit
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install SEOmator
run: npm install -g @seomator/seo-audit
- name: Install Playwright browsers
run: npx playwright install chromium
- name: Run SEO Audit
run: seomator audit https://your-staging-url.com --format json -o seo-report.json
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: seo-report
path: seo-report.jsonseo-audit:
image: node:20
script:
- npm install -g @seomator/seo-audit
- npx playwright install chromium
- seomator audit https://your-staging-url.com --format json -o seo-report.json
artifacts:
paths:
- seo-report.jsonimport { Auditor, createAuditor } from '@seomator/seo-audit';
const auditor = createAuditor({
categories: ['core', 'security', 'perf'],
measureCwv: true,
onCategoryComplete: (categoryId, name, result) => {
console.log(`${name}: ${result.score}/100`);
}
});
const result = await auditor.audit('https://example.com');
console.log(`Overall Score: ${result.overallScore}`);Use SEOmator directly in Claude Code as an AI skill for automated SEO auditing.
npx skills add seo-skills/seo-audit-skill"Run an SEO audit on https://example.com"
"Audit https://mysite.com and tell me what to fix first"
"Check SEO health of https://example.com with 20-page crawl"
- Node.js 18+ (uses native fetch API)
- Playwright (for Core Web Vitals and JS rendering analysis)
After installing, run npx playwright install chromium to install the browser for CWV measurement.
MIT