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

@portablecore/seo-audit

v0.1.0

Published

Platform-agnostic SEO and AEO auditing: polite sitemap-seeded crawler, structured page observations, and severity-ranked findings written for non-technical readers

Readme

@portablecore/seo-audit

Platform-agnostic SEO and AEO auditing. Crawls a site from what it advertises about itself, records structured observations, and turns those into severity-ranked findings written for a non-technical reader.

Companion to @portablecore/seo. That package is how a Portable property gets SEO right by construction. This one is how any property, including ones we do not control, gets measured.

Usage

npx seo-audit https://example.org
npx seo-audit https://example.org --out data --delay 3000 --cooldown 180000
npx seo-audit --analyze data/crawl.json
import { auditSite, renderTextReport } from "@portablecore/seo-audit";

const report = await auditSite("https://example.org");
console.log(renderTextReport(report));

Crawling and analysis are separable on purpose. A polite pass over a rate-limited site takes twenty minutes, so the raw observations are written to crawl.json and the rules can be re-run against them for free with --analyze.

Design notes

Only crawls what the site advertises. Discovery reads robots.txt, follows whatever Sitemap: it declares, and falls back to the conventional locations. Following links instead would measure something other than what the site claims about itself.

Refuses to report on a contaminated crawl. A bot-check interstitial returns HTTP 200 and carries its own noindex. Fed to the rules unrecognized, it manufactures a critical finding that the entire site is deindexed. analyzeCrawl throws ContaminatedCrawlError when interstitials reached the page set or when coverage falls below 90%, rather than publishing confident nonsense.

This is not hypothetical. It is what happened auditing a Cloudflare-fronted WordPress site in July 2026, and the first pass produced 59 false criticals before detection was added.

Polite by default. Three seconds between requests, three-minute cooldown on a challenge, retrying the same URL rather than moving on. A crawl is a favor the site is doing you: being slow costs minutes, being rude costs a penalty window that invalidates the whole run.

Remedies are platform-aware. "Add Person schema" is a plugin setting on WordPress, a template edit in Next.js, and sometimes not possible at all on a hosted builder. The crawler detects the stack and the rules tailor the fix text accordingly.

Zero runtime dependencies. HTML extraction is anchored regex rather than a DOM parser, so the package runs anywhere without a lockfile negotiation.

What it checks

| Concern | Rules | | --- | --- | | Crawlability | site-wide disallow, high Crawl-delay, blocked AI crawlers | | Indexability | noindex in sitemap, canonical mismatch, non-200 and redirecting sitemap URLs | | Metadata | missing or duplicate H1, brand repeated in title, over-long titles, missing descriptions, duplicate titles, missing lang | | Structured data and AEO | missing Organization schema, missing Person schema, missing Event schema, unparseable JSON-LD, missing llms.txt | | Content | archive bloat, thin pages | | Performance | heavy HTML, missing alt attributes, slow responses |

Rule titles are noun phrases rather than sentences so they read correctly at any count, including one. The output is meant to be pasted into a document a client will read.

Configuration

await auditSite("https://example.org", {
  config: {
    brand: "Example School",        // inferred from title templates when omitted
    peopleSections: ["directory"],   // sections wanting Person schema
    archiveSections: ["category"],   // sections that should not be in a sitemap
    eventSections: ["events"],       // sections wanting Event schema
    thinWordCount: 300,
  },
  fetch: { delayMs: 3000, cooldownMs: 180_000 },
  analyze: { minCoverage: 0.9 },
});

Sections come from the sitemap-index child that listed a URL (page-sitemap.xml becomes page), falling back to the first path segment for flat sitemaps.