npm.io
1.2.0 • Published 2d agoCLI

seosnap

Licence
MIT
Version
1.2.0
Deps
10
Size
850 kB
Vulns
0
Weekly
0

seosnap

Full-site SEO, Performance, Security & AI-agent-experience auditor for Node.js — run it as a library inside any script, pipeline, or app.

npm install seosnap

251 rules. Zero cloud dependency. No login. Runs entirely in your process.


What it does

Point seosnap at any URL and it crawls the entire site, runs every rule against every page, and hands you back a structured result object with per-page findings, category scores, and a single 0–100 health score — all in one await.

Four score groups roll up into the overall score:

Group What it covers
SEO Crawlability, meta tags, links, content quality, structured data, images, social, accessibility, mobile, URLs, i18n, E-E-A-T, local SEO, video, analytics
Performance Core Web Vitals (LCP/CLS/INP), DOM size, render-blocking resources, unused JS/CSS
Security HTTPS, headers, mixed content, legal compliance, ad-block resilience
Agents AI-agent & LLM discoverability (llms.txt, structured hints, machine-readable metadata)

Quick start

import { runAudit } from "seosnap";

const result = await runAudit({ url: "https://example.com" });

console.log(result.overallScore);   // 0-100
console.log(result.groupScores);    // { seo, performance, security, agents }
console.log(result.topIssues);      // severity-sorted findings across all pages

seosnap is silent by default — no console or stderr output unless you opt in.


Core API

runAudit(options)

The single entry point for a full site audit.

import { runAudit } from "seosnap";

const result = await runAudit({
  url: "https://example.com",

  // Crawl limits
  maxPages: 25,        // default 25
  maxDepth: 3,         // default 3
  concurrency: 5,      // parallel page fetches, default 5
  timeoutMs: 15_000,   // per-page timeout, default 15 000 ms

  // Rendering mode
  full: false,         // true → real Playwright/Chromium for CWV & rendered-DOM rules

  // Crawl behaviour
  respectRobotsTxt: true,   // default true
  ignoreSitemap: false,     // skip sitemap discovery, use link-following only
  userAgent: undefined,     // custom UA string for the static fetcher

  // Config file
  configDir: process.cwd(), // directory that contains seosnap.toml (optional)
});

The full: true mode requires Playwright installed separately — see Browser mode below.

The result object
result.overallScore          // number 0-100
result.groupScores           // { seo, performance, security, agents }
result.categoryScores        // one score per category (crawl, core, a11y, perf, …)
result.topIssues             // AggregatedIssue[] — deduped, severity-sorted, with fix guidance
result.pages                 // PageAuditResult[] — per-page detail
result.pagesAudited          // number of pages crawled
result.rulesRun              // rules executed (skipped/N/A excluded from scoring)
result.durationMs            // total wall-clock time
result.meta.seosnapVersion   // library version that produced this result

Every issue in topIssues includes reason (why it matters) and fix (what to do), not just a pass/fail flag.


Generating reports

Get a formatted string
import { runAudit, renderJsonReport, renderMarkdownReport, renderHtmlReport } from "seosnap";

const result = await runAudit({ url: "https://example.com" });

const html = renderHtmlReport(result);      // self-contained single-file HTML
const md   = renderMarkdownReport(result);  // GitHub-compatible Markdown
const json = renderJsonReport(result);      // pretty-printed JSON string
Write report files directly
import { runAudit, emitReport } from "seosnap";

const result = await runAudit({ url: "https://example.com" });

const { writtenFiles } = await emitReport(result, {
  format: "all",          // "terminal" | "json" | "md" | "html" | "all"
  outDir: "./reports",
});

console.log(writtenFiles); // ["./reports/report.json", "./reports/report.md", …]

Enabling logging

import { runAudit, setLogLevel } from "seosnap";

setLogLevel("info");      // progress messages to stderr
// setLogLevel("verbose") // debug-level detail
// setLogLevel("silent")  // default — no output at all

await runAudit({ url: "https://example.com" });

Browser mode — Core Web Vitals

Static mode (the default) fetches raw HTML and runs ~230 rules without any browser overhead. Browser mode adds a real Playwright/Chromium pass for Core Web Vitals (LCP, CLS, INP), DOM node count, unused JS/CSS coverage, and render-blocking resource detection.

# install Playwright and its Chromium binary once
npm install playwright
npx playwright install chromium
const result = await runAudit({
  url: "https://example.com",
  full: true,   // enable browser mode
});

// Browser-only fields now populated on each page:
result.pages[0]?.browserMetrics?.lcpMs
result.pages[0]?.browserMetrics?.cls
result.pages[0]?.browserMetrics?.inpMs
result.pages[0]?.browserMetrics?.domNodes
result.pages[0]?.browserMetrics?.jsCoverageUnusedBytes

Playwright is an optional peer dependency — seosnap installs and runs fine without it as long as you don't pass full: true.


Configuration — seosnap.toml

Drop a seosnap.toml in your project root (or point configDir at a directory) to enable/disable rules and tweak per-rule options without touching code.

[rules]
enable  = ["*"]          # all rules — the default
disable = [
  "security",            # bare category name = "security/*"
  "content/word-count",  # or a specific rule id
]

[crawl]
max_pages   = 50
max_depth   = 2
concurrency = 8

# Override a rule's default thresholds
[rules."core/meta-title"]
min_length = 30
max_length = 75

Advanced — custom pipelines

Every layer is independently exported. You can crawl without scoring, score your own rule results, or run a hand-picked rule set against a page you fetched yourself.

Run a subset of rules
import { crawlSite, runRulesForPage, getRulesByCategory, buildAuditResult } from "seosnap";

const { pages } = await crawlSite("https://example.com", {
  maxPages: 10, maxDepth: 2, full: false, concurrency: 5,
});

const securityRules = getRulesByCategory("security");

for (const page of pages) {
  const results = await runRulesForPage(page, securityRules, { fullMode: false });
  // results is RuleResult[] — do whatever you need
}
Inspect the rule registry
import { ALL_RULES, getRuleById, validateRegistry } from "seosnap";

console.log(ALL_RULES.length);                     // 251
console.log(getRuleById("core/meta-title"));       // the full RuleDefinition
console.log(validateRegistry().categoryCounts);    // rule count per category
Use crawling and fetching directly
import { fetchStaticPage, fetchRobotsTxt, fetchSitemap } from "seosnap";

const page     = await fetchStaticPage("https://example.com/about");
const robots   = await fetchRobotsTxt("https://example.com");
const sitemaps = await fetchSitemap("https://example.com/sitemap.xml");

Use in CI

runAudit returns a plain value — combine it with any assertion library, fail the build on score thresholds, or upload the JSON artifact to your reporting system.

import { runAudit, emitReport } from "seosnap";

const result = await runAudit({ url: process.env.SITE_URL!, maxPages: 10 });

await emitReport(result, { format: "json", outDir: "./ci-reports" });

if (result.overallScore < 70) {
  console.error(`SEO health score ${result.overallScore} is below threshold`);
  process.exit(1);
}

const criticalIssues = result.topIssues.filter(i => i.severity === "critical");
if (criticalIssues.length > 0) {
  console.error(`${criticalIssues.length} critical issue(s) found`);
  process.exit(1);
}

Further reading


License

MIT seosnap contributors

Keywords