Free, open-source, local-first SEO crawler and regression audit tool.
seo-audit crawls server-rendered pages, saves a versioned JSON snapshot,
finds concrete SEO problems, and detects regressions after a deployment. It
needs no account or API key. Page HTML is processed in memory; normalized
results stay in local JSON, checkpoint, CSV, and self-contained HTML files.
Open the interactive example report or view its source file.
- automatic
robots.txt, sitemap, sitemap-index, and gzip sitemap support; - bounded concurrency with a per-origin request delay;
- retries for timeouts,
429, and temporary5xx, includingRetry-After; - redirect-chain and redirect-loop detection;
- 40+ explicit audit and regression rules with stable fingerprints;
- SnapshotV2 with automatic baseline v1 migration;
new,ongoing,resolved, andunchangedissue lifecycle;- resumable scans and a useful report even after interruption;
- local SnapshotV2 history with issue, page-count, sitemap, and crawl-depth trends—without a hosted account;
- English/Ukrainian report localization, filters, CSV export, print layout, and local report branding;
- interactive issue statistics for severity, frequent checks, ownership, and regression lifecycle, with chart-to-table filtering;
- a production-versus-preview release guard with safe environment-based authentication for private deployments;
- configuration, suppressions with expiry, severity overrides, and budgets;
- a self-contained GitHub Action that runs inside the GitHub runner;
- typed engine API with injectable
fetch, checkpoints, events, logger, andAbortSignal.
The project intentionally does not calculate an opaque overall SEO score. Every finding names the affected URL, rule, risk, evidence, owner, and suggested remediation.
Node.js 20.19 or newer is required.
Run without installing:
npx seo-crawl-audit https://example.com/Or install the CLI globally:
npm install --global seo-crawl-audit
seo-audit --versionFor repository development:
git clone https://github.com/MelnixDev/seo-crawl-audit.git
cd seo-crawl-audit
npm install
npm run build
npm link --workspace seo-crawl-auditQuotes to Scrape is a public educational site made for crawler practice. It is unrelated to this project and keeps the example small.
seo-audit scan https://quotes.toscrape.com/ \
--no-sitemap \
--pages 10 \
--output quotes-baseline.json \
--report quotes-report.htmlRun a comparison later:
seo-audit check https://quotes.toscrape.com/ \
--baseline quotes-baseline.json \
--pages 10 \
--report quotes-changes.htmlShortcut for seo-audit scan <url>.
seo-audit https://example.com/Crawls a site, audits its current state, saves SnapshotV2, and optionally writes an HTML report.
seo-audit scan https://example.com/
seo-audit scan https://example.com/ --pages 250
seo-audit scan https://example.com/ --allWhen a sitemap is found in an interactive terminal, the menu offers the first
100 pages, every sitemap URL, groups of 100 with confirmation, or a custom
number. --pages and --all skip the menu. If no sitemap is found, press Enter
to continue through same-origin internal links, or enter the full sitemap URL.
Crawls again and compares the result with a saved baseline.
seo-audit check
seo-audit check https://preview.example.com/
seo-audit check --baseline production-seo.json --strictWithout url, the saved site URL is used. A different origin supports
production-to-preview comparison while keeping paths comparable. New
error-level findings return exit code 1; --strict also blocks on warnings.
If scan receives SIGINT or SIGTERM, it flushes completed pages, writes the
partial snapshot/report, keeps the checkpoint, and exits with code 130.
Scans production, requests the same paths from a preview deployment, and reports only SEO regressions introduced by the preview:
seo-audit compare \
--production https://example.com/ \
--preview https://preview-123.example.dev/ \
--pages 100 \
--report preview-seo-report.htmlFor a protected deployment, put request headers in an environment variable as a JSON object and pass only its name on the command line:
export SEO_AUDIT_PREVIEW_HEADERS='{"Authorization":"Bearer …"}'
seo-audit compare \
--production https://example.com/ \
--preview https://preview-123.example.dev/ \
--preview-headers-env SEO_AUDIT_PREVIEW_HEADERSHeader values are used only by the injected request adapter. They are never
written to snapshots, reports, checkpoints, or JSON output. Errors block the
release with exit code 1; --strict also blocks on warnings.
Regenerates HTML from a saved snapshot without network requests.
seo-audit report
seo-audit report quotes-baseline.json --report quotes-report.htmlLists locally saved runs and creates an HTML report with issue trends:
seo-audit history https://example.com/
seo-audit history --history-dir ./audit-history --report history.htmlCompare any two saved runs without crawling:
seo-audit history \
--from .seo-audit/history/older.snapshot.json \
--to .seo-audit/history/newer.snapshot.json \
--report selected-runs.html--baseline <file> Baseline input for check (default: .seo-audit.json)
--config <file> Config file (default: seo-audit.config.json)
--output <file> Snapshot output for scan (default: .seo-audit.json)
--report <file> HTML report output (default: seo-audit-report.html)
--no-report Disable automatic HTML report generation
--no-cache Disable checkpoint caching and resume
--pages <number> Scan an exact number of pages
--all Scan every URL found in the sitemap
--max-pages <number> Deprecated alias for --pages
--concurrency <number> Concurrent requests (default: 5)
--delay <ms> Delay between starts per origin (default: 100)
--timeout <ms> Per-request timeout (default: 10000)
--sitemap <url> Use a specific sitemap or sitemap index
--no-sitemap Skip sitemap discovery and crawl internal links
--include-query Treat query-string URLs as separate pages
--ignore-robots Ignore robots.txt disallow rules
--strict Fail check on warnings as well as errors
--production <url> Production URL for compare
--preview <url> Preview deployment URL for compare
--production-headers-env <name>
Read production headers from a JSON environment variable
--preview-headers-env <name>
Read preview headers from a JSON environment variable
--history-dir <path> Local snapshot history directory
--no-history Do not save this scan to local history
--from <snapshot> Older snapshot for an explicit history comparison
--to <snapshot> Newer snapshot for an explicit history comparison
--json Print machine-readable output
--help Show help
--version Show the installed version
The repository includes seo-audit.config.json and a
JSON Schema. CLI flags take precedence over
the config file, which takes precedence over the saved baseline, followed by
safe defaults.
{
"$schema": "./packages/core/config.schema.json",
"url": "https://example.com/",
"sitemap": "auto",
"maxPages": 100,
"concurrency": 5,
"delay": 100,
"timeout": 10000,
"respectRobots": true,
"includeQuery": false,
"enabledRules": null,
"severityOverrides": {
"low-word-count": "info"
},
"suppressions": [
{
"rule": "missing-description",
"urlPattern": "/legal/**",
"reason": "Description intentionally omitted",
"expiresAt": "2027-01-01"
}
],
"regressionBudgets": {
"error": 0,
"warning": 3
},
"report": {
"agencyName": "Example Agency",
"logo": "https://example.com/logo.png",
"primaryColor": "#3157d5"
}
}Expired suppressions stop matching automatically, so the issue appears again.
urlPattern accepts * within one path segment and ** across segments.
The rule registry covers access and HTTP failures, meta and header noindex,
robots blocking, canonical validity and targets, redirects, sitemap consistency,
titles, descriptions, headings, internal links, orphan pages, hreflang,
structured data syntax, language declarations, exact duplicate content, social
metadata, image alt attributes, and low word count.
Regression-only checks cover newly introduced indexing blocks, status changes, metadata removal or edits, redirect changes, robots changes, and sharp sitemap URL loss.
Read the rule-by-rule documentation.
SnapshotV2 records schema, engine and rule-set versions, generated time, config hash, robots and sitemap state, normalized page data, link-graph summary, and crawl statistics. Baseline schema v1 is upgraded automatically when read.
Issues use a stable 24-character fingerprint. A comparison separates:
newIssues— absent from the previous snapshot;ongoingIssues— same issue identity with changed evidence;resolvedIssues— present before but absent now;unchangedIssues— same identity and evidence.
DiffResult.complete is false for partial or truncated comparisons. Findings
on checked pages are still reported, while unchecked pages are not falsely
classified as missing or resolved.
During scan, completed normalized page results are appended to
.seo-audit.checkpoint.ndjson. Full HTML is not cached. Running the same
compatible command resumes without requesting saved pages again. The checkpoint
is removed after completion and retained after interruption. Successful pages
are reused; transient failures and HTTP 5xx results are refreshed. Only an
unfinished final NDJSON record is recoverable—earlier corruption is reported.
Every completed scan and check also saves a full SnapshotV2 beside the selected
baseline under .seo-audit/history/. Use --history-dir to move it or
--no-history for an ephemeral run. History is read directly from disk; no URL,
snapshot, or metric is uploaded. The HTML trend view shows errors, warnings,
informational findings, new/resolved issues, page count, sitemap count, and
maximum crawl depth.
The portable HTML file contains no external script, font, or tracking request. It includes:
- an
English / Українськаlanguage selector that translates the complete interface, rule names, findings, remediation, lifecycle labels, and CSV; - dependency-free interactive charts for severity, most frequent checks, inferred page templates, and owner or regression-lifecycle distribution; selecting a chart item filters the issue table and selecting it again clears that filter;
- summary cards and partial-scan state;
- current and lifecycle tabs;
- severity, rule, inferred template, owner, and URL/text filters;
- evidence, before/after values, remediation, and fingerprint;
- engine and rule-set versions;
- client-side CSV export;
- print-friendly PDF layout;
- optional local agency name, logo, and primary color;
- clear clean-report and no-filter-match states.
Template grouping turns repeated findings such as /products/red-shoe and
/products/blue-shirt into /products/:slug. Numeric IDs, UUIDs, dates, and
hashes have stable placeholders. Grouping changes only presentation: rule IDs,
evidence, snapshots, and issue fingerprints remain unchanged.
Writes are atomic. Interactive scans refresh a partial report while results are being checkpointed, so interruption does not discard already completed work.
The Action crawls entirely inside the GitHub runner. It produces exit status, JSON summary, HTML report, job summary, and annotations for error-level regressions.
name: SEO regression check
on: [pull_request]
jobs:
seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: MelnixDev/seo-crawl-audit@v0
id: seo
with:
url: https://preview.example.com/
baseline: .seo-audit.json
config: seo-audit.config.json
fail-on: error
report: seo-audit-report.html
- uses: actions/upload-artifact@v4
with:
name: seo-crawl-audit-report
path: |
${{ steps.seo.outputs.report }}
${{ steps.seo.outputs.summary }}Read all Action inputs and outputs.
The npm workspace separates reusable concerns:
packages/core @seo-crawl-audit/core — scan, audit, diff, snapshots, reports
packages/cli seo-crawl-audit — terminal UX and npm executable
packages/action @seo-crawl-audit/action — GitHub runner entry point
import { audit, planScan, scan } from "@seo-crawl-audit/core";
import { createFileCheckpointStore } from "@seo-crawl-audit/core/node";
const plan = await planScan({ url: "https://example.com/", maxPages: 100 });
const result = await scan(plan, {
signal: controller.signal,
checkpointStore: createFileCheckpointStore(".seo-audit.checkpoint.ndjson"),
onEvent(event) {
if (event.type === "progress") console.log(event.completed, event.total);
},
});
const issues = audit(result.snapshot);Read the typed public API guide and the architecture notes.
- delay:
100 msbetween request starts per origin; - concurrency:
5; - timeout:
10 s; robots.txtrespected;- same-origin page discovery;
- query strings excluded;
- HTML limited to
5 MiBand robots to512 KiB; - retries are bounded and use exponential backoff with jitter;
Retry-Afteris honored up to 30 seconds;- redirects are limited to 10.
Use a longer delay for a small site or a server you do not control:
seo-audit https://example.com/ --delay 500Use --delay 0 only for local or explicitly controlled fixtures.
- server-rendered HTML; JavaScript rendering is not included;
- same-origin crawl discovery;
- sitemap and internal-link seeding;
- non-HTML responses are recorded but not parsed for page metadata;
- authenticated pages are not supported;
- results are normalized and ordered for reproducible output.
npm run build
npm test
npm run checkCI covers Node.js 20, 22, and 24. Tests use local HTTP fixtures for robots, sitemaps, gzip, retries, redirects, cancellation, checkpoint resume, migration, rules, lifecycle, HTML escaping, npm packaging, and the GitHub Action.
MIT