Model or dataset
seo-skills/seo-audit-skill avatar
seo-skills/seo-audit-skill

SEOmator ships 373 rules in its README and 332 in its description, and points package main at the Electron process

A comprehensive SEO audit command-line tool with 332 audit rules across 20 categories. Analyze any website for SEO best practices, Core Web Vitals, security headers, structured data, accessibility, and more.

450 stars62 forksTypeScriptMIT

At a glance

What is it?
An MIT TypeScript SEO auditor that runs as a CLI, an Electron dashboard and a Claude Code skill, with per-subresource checks and rendered-versus-raw DOM comparison. The audit coverage is genuinely broad and its not-measured policy is the right call. The packaging metadata has two broken entry points, the storage layer needs a native rebuild per runtime, and the last three releases shipped inside 28 hours.
Who is it for?
Use seomator if you want rendered-DOM and per-subresource checks that a request-only crawler cannot give you, and if your CI images already carry a Chromium-family browser. Do not import it as a library, because main resolves to a file the package does not ship and the exports map has no require condition.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The rule count is 373 in the README and 332 in the repository description

Two numbers for the same thing, one page apart.

The repository description says 332 audit rules across 20 categories. The README headline says 373 rules across 20 categories, the features list repeats 373, and the table of contents links to an anchor literally named categories--rules-373-total. The package manifest description also says 373.

So 332 appears once, in the metadata a visitor sees before opening the file, and 373 appears everywhere inside the project. Forty-one rules, or roughly one rule in eight and a half, are unaccounted for. There is no changelog entry in view that explains the difference, so a reader cannot tell whether the description is stale, whether the count is double-counted across the two formats, or whether the categories list itself moved.

That number is the project's headline claim and the first thing a buyer reads. It is also the kind of figure that is easy to let drift, because nothing in a build step appears to generate it into the repository description.

The related claim is better behaved. Every rule is described as shipping a specific fix suggestion, and unmeasurable checks are said to report as not measured rather than as fake passes. That is a stronger position than most tools in this category take, and it is consistent with how the measurement flags behave elsewhere on the page.

main points at the Electron process and files ships only dist

The manifest declares three entry points and one file allowlist, and they do not line up.

json
"bin": {
    "seomator": "./dist/cli.js"
  },
  "main": "./dist-electron/main/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": [
    "dist"
  ],

Three problems, each independent.

The bin entry is correct, which is why the documented `npm install -g @seomator/seo-audit` works.

main resolves to the Electron main process. That path sits in dist-electron, and files publishes only dist, so the file main points at is not in the tarball at all. Any consumer whose resolver honours main gets a missing module.

And the exports map offers only an import condition plus types. There is no require condition, so a CommonJS require of this package fails even though the package would otherwise be loadable. Modern Node resolution prefers exports over main, which means the exports entry is the one that governs in practice, and it excludes require.

The allowlist compounds both. Publishing only dist means the repository's other directories never reach npm, which for this project specifically matters: the top level contains a skill/ directory, a SKILL.md, a references/ directory, a docs/ directory, a reports/ directory and a .mcp.json. The README advertises that the tool ships as a Claude Code skill, and that skill is distributed by cloning the repository, not by installing the npm package.

better-sqlite3 is rebuilt for Node, rebuilt again for Electron, then re-signed

The SQLite storage described in the features, persistent crawl data with compression and audit history, is a native module. Two scripts exist to make it work, and they are not the same script.

json
"rebuild:electron": "electron-rebuild -f -w better-sqlite3 && node scripts/resign-native.mjs",
"rebuild:cli": "npm rebuild better-sqlite3 && node scripts/resign-native.mjs",

One rebuilds the native addon against Electron's ABI, the other against Node's. Neither is a no-op: they are required whenever you switch between the CLI and the desktop app, because a binary compiled for one runtime will not load in the other. The desktop setup path in the documentation says exactly this, telling you to compile the native module for Electron before launching the app.

Both paths then run resign-native.mjs. That step exists because a locally rebuilt macOS binary loses the signature the distribution had, so it has to be signed again before it will load. On macOS this is the difference between a working desktop app and one that refuses to start.

So an install that reads as one npm command has a native build requirement behind it, and the requirement changes shape depending on which of the three surfaces you intend to use. The CLI alone is the cheap case. The Electron app is the one that needs two rebuild scripts and a signing step, and the packaging scripts reflect that by calling install-app-deps before every electron-builder invocation.

For CI this is the part to budget for. A cached node_modules from a different runtime will not work, and the failure will look like a module load error rather than a missing step.

Core Web Vitals come from whatever browser the machine already has

The features list attributes Core Web Vitals measurement to Playwright. The installation note says something different and more consequential: the CLI automatically uses your system Chrome, Chromium, or Edge browser, and no additional browser installation is required if you have Chrome installed.

So Playwright is the driver and your installed browser is the browser. The measurement covers the usual five, LCP, CLS, FCP, TTFB and INP, taken against a real rendering engine rather than a downloaded one.

That is a genuine advantage for most people, since there is no 300 MB browser download and no version to keep current. It also means the numbers are a property of the machine as much as of the site. Chrome, Chromium and Edge are three different builds that can report different lab values for the same URL, and the page does not say which engine produced a given result or offer a way to record it.

Two flags make the measurement situation more explicit rather than less. `--no-cwv` skips Core Web Vitals entirely, which the quickstart recommends as the faster path. `--simulate-interaction` scrolls and clicks the page so INP can be measured, and the option table states the result is reported as synthetic and unscored. That is the honest labelling, since a scripted click cannot reproduce real interaction latency.

For a tool whose headline is a score, that distinction matters. A score that mixes a synthetic INP with measured lab metrics would be misleading, so excluding it is the right call.

The remaining measurement hooks are --mobile, a second render at a mobile viewport plus mobile-first parity checks, which the table scopes to single-page audits.

The quickstart ends its crawl example mid-flag while the table documents --max-pages

The crawl example in the quickstart is cut off:

bash
# Crawl multiple pages
seomator audit https://example.com --crawl --max-pag

The line ends at --max-pag with no closing token. The option reference further down documents the real flag as --max-pages with the alias -m and a default of 10, so the intent is clear, but a reader copying the quickstart block verbatim gets an unrecognised option.

The reference table is otherwise the reliable part of the command surface, and it is more informative than the examples. Defaults are stated for every option: console output format, all categories, crawl off, max pages 10, concurrency 3, and a request timeout of 30000 milliseconds.

Three options are worth reading closely because they change what a run means rather than how it looks.

`--json` is marked deprecated in favour of --format json, so a script written against the older flag still works and is on borrowed time.

`--json-report` writes the legacy JSON report to .seomator/reports/ in addition to the primary output. Legacy, plural reports, and a separate SQLite store: two report artifacts coexist, and the flag that produces the old one is kept rather than removed.

`--no-save` skips storing the audit in history, which is the switch that matters on a shared or continuous-integration machine, since the default is to persist.

Audits are written to ~/.seomator/audits.db by default, and three flags govern that

The storage default is worth stating on its own, because it is the one behaviour of this tool that persists data you did not ask it to.

Audits are stored in `~/.seomator/audits.db` by default, and that database is what `seomator report`, `seomator compare` and the desktop app all read. So the CLI, the history commands and the Electron dashboard share one store, which is what makes the score history and trend charts work.

Three separate controls exist over it. `SEOMATOR_HOME` relocates the data to another directory, which is the right answer for a container or a shared runner. `[output] save = false` in seomator.toml opts a project out, which is the right answer for a repository that should not accumulate audit state. And `--no-save` skips storing the run, which is the right answer for a one-off.

That the project-level opt-out is a TOML setting rather than a flag is a reasonable split, since a repo should not need a flag on every invocation to stop writing files. The configuration layer is described as project-level with presets and inheritance, and the v4.1.0 release is titled around the config layer doing what it documents.

Alongside the database sits the legacy reports directory, .seomator/reports/, which --json-report writes to. So a default run touches one location and an opt-in run touches two, and the older of the two is described as legacy without a removal note.

Three releases in 28 hours, and the manifest is already past the newest tag

The recent release history compresses into two days.

v4.0.0 landed on 2026-09-04 at 08:35, titled one verdict, one ordering, and a local dashboard. v4.1.0 followed on 2026-09-05 at 12:12, titled the config layer does what it documents. v5.0.0 landed the same day at 12:20, titled the score means more than it did.

So a major version shipped eight minutes after a minor version on the same afternoon, and a major version shipped a day after a new major line began. There is no patch tag in between. The release names are also written with inconsistent prefixes: v5.0.0 and v4.1.0 carry a v, while 4.0.0 does not.

Those three titles are unusually informative for a changelog, incidentally. Each names a single behaviour change in plain language, which tells a reader more about the direction than a list of fixed issues would. The local dashboard in v4.0.0 is the `seomator serve` command, which opens http://127.0.0.1:7360 and takes a --port flag where 0 means pick a free port, plus --no-open to suppress the browser.

The more actionable fact is that the manifest declares version 5.1.0 while the newest published tag is v5.0.0. Whatever changed between them is on main and not in a release. The last push was 2026-09-22, roughly two and a half weeks after the final tag, so the gap has had time to widen.

For anyone pinning a CI job, v5.0.0 is what exists and 5.1.0 is what the source claims to be.

Four agent-facing files at the root, and one of them is lowercase claude.md

The repository root is unusually dense with agent-facing files. There is AGENTS.md, claude.md in lowercase, SKILL.md, and .mcp.json, plus TODOS.md and a scripts/ directory that generates icons and design tokens.

The lowercase filename is the detail worth flagging. Claude Code's convention for its project instruction file is CLAUDE.md in capitals, and case matters on Linux and in a Git checkout, so a lowercase claude.md is not the file that convention picks up. Whether that matters here depends on whether the file is meant for the tool or for humans reading the repository, and nothing in the visible text says which.

Having both AGENTS.md and a CLAUDE-style file is defensible in 2026, since agents disagree on which they read, but the two should at least be cased consistently.

None of these files reach npm. The allowlist publishes only dist, so skill/, references/, docs/, reports/ and .mcp.json stay in the repository. That reinforces the distribution split: the CLI installs from npm, and the skill installs from a clone.

The last naming detail is the branding. The repository is seo-skills/seo-audit-skill, the npm package is @seomator/seo-audit, and the homepage is a page on seomator.com offering a hosted web audit, which the README recommends in a callout above the fold. Three names for one tool, with an MIT package underneath and a commercial hosted front end beside it.

Editorial conclusion

Use seomator if you want rendered-DOM and per-subresource checks that a request-only crawler cannot give you, and if your CI images already carry a Chromium-family browser. Do not import it as a library, because main resolves to a file the package does not ship and the exports map has no require condition. Verify first the rule count you are being sold against, since 373 and 332 both appear for the same release, and set SEOMATOR_HOME or pass --no-save before auditing third-party sites from a shared machine.

Frequently asked questions

How many SEO audit rules does seomator implement?

The README, its table of contents anchor and the package description all say 373 rules across 20 categories, while the repository description says 332. Nothing visible in the project reconciles the 41-rule difference, so the count to quote when comparing tools is the one in the README.

Does seomator need a browser installation to measure Core Web Vitals?

No separate browser is installed. The CLI uses your system Chrome, Chromium or Edge through Playwright, and the documentation notes that no additional browser is required if you have Chrome. Because the engine is whatever is on the machine, lab values can differ between Chrome, Chromium and Edge. The --simulate-interaction flag reports INP as synthetic and unscored.

Where does seomator store audit history?

Audits go to ~/.seomator/audits.db by default, and that store is what seomator report, seomator compare and the desktop app read. Set SEOMATOR_HOME to relocate it, set [output] save = false in seomator.toml to opt a project out, or pass --no-save to skip storing a single run. A separate legacy JSON report path lives in .seomator/reports/ behind --json-report.

Why does seomator need a native rebuild?

Its SQLite layer is a native module, so it must be compiled against the runtime that loads it. rebuild:cli uses npm rebuild for Node, rebuild:electron uses electron-rebuild for Electron, and both then run a resign script so the rebuilt binary is signed on macOS. Switching between the CLI and the desktop app means rerunning the other one.

How do I install and run the seomator audit?

Install globally with npm install -g @seomator/seo-audit, then run seomator audit https://example.com. Useful flags include --format json for CI output, -c to select categories, --crawl with --max-pages for multi-page runs, --no-cwv to skip Core Web Vitals for speed, and -o to write an HTML report.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. seo-skills/seo-audit-skill on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/seo-skills-seo-audit-skill.svg)](https://hysenlabs.com/projects/seo-skills-seo-audit-skill)