npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

seosnap

v1.2.0

Published

Full-site SEO, Performance, Security & Agent-experience auditor for Node.js. Import it as a library (`import { runAudit } from "seosnap"`) or run it as a CLI. 251 rules, 0-100 scoring, terminal/JSON/Markdown/HTML reports.

Readme

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