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

@domandigital/seo

v0.1.3

Published

Route-fact primitives shared across Doman Digital and client properties: sitemap/indexability policy, per-page keyword targets, the internal-link graph, breadcrumb trail derivation, and a filesystem-vs-policy coverage validator.

Downloads

762

Readme

@domandigital/seo

Route-fact primitives shared across Doman Digital and client properties: sitemap and indexability policy, per-page keyword targets, the internal-link graph, breadcrumb trail derivation, and a filesystem-vs-policy coverage validator.

Read PRINCIPLES.md before wiring this into a new repo. This README is the API reference.

Why this exists

Four properties (Doman Digital, RMP Electrical, MMM Beauty, Sensphere) each grew their own version of the same route bookkeeping: which pages are indexable, which belong in the sitemap, what a page is trying to rank for, which pages should link to which. The recurring defect: nothing checked that every page on disk had a policy entry, so six live case studies (/work/* on domandigital.co.uk, including a page already earning its own search traffic) shipped indexable and simply never got submitted to the sitemap. Nobody noticed for weeks.

This package is the fix: pure functions over route data the consuming repo still owns. It does not know your CMS, your framework, or your route data. It only knows how to look entries up, derive edges between them, and flag when the two don't line up.

What's per-repo, on purpose: the actual RoutePolicyEntry[] array, the actual PageTarget[] list, the actual link declarations, Sanity queries, and every component. This package ships none of that. See ## Deliberately not in scope below.

Install

pnpm add @domandigital/seo

Public on npm, Apache-2.0, published with provenance from a tagged release. ESM and CJS builds ship together, each with its own types. Requires Node 20 or newer.

Upgrading a repo that still pins github:Doman-Digital/dd-seo#vX.Y.Z? Swap it for a semver range, and drop resolve.preserveSymlinks: true from vitest.config.ts if it was added for this package. See @domandigital/graph's README for why that workaround existed and why registry installs don't need it.

API stability

policy.ts, links.ts and validate.ts are exercised by a real consumer and are treated as stable for the rest of 0.1.x.

targets.ts and trail.ts are provisional. They're built and tested, but no repo has adopted them in production yet, so their shapes may change once a second consumer shows what they actually need. Pin exactly if you depend on them today.

API

policy.ts

getSitemapRoutes(policy: RoutePolicyEntry[]): RoutePolicyEntry[]
getRoutePolicy(policy: RoutePolicyEntry[], path: string): RoutePolicyEntry | undefined
isRouteIndexable(policy: RoutePolicyEntry[], path: string): boolean

isRouteIndexable defaults to true for an unknown route, deliberately. See the comment in src/policy.ts. The corresponding failure belongs to validateCoverage, which runs in CI, not at request time.

targets.ts

getTargetForRoute(targets: PageTarget[], routeKey: string): PageTarget | undefined
findKeywordCannibalization(targets: PageTarget[], allowlist?: string[]): KeywordCannibalization[]

links.ts: the internal-link graph

getRelatedLinks(declarations: LinkDeclaration[], routeKey: string, opts?: GetRelatedLinksOptions): RelatedLinks

A page declares supports: string[] (which money pages it should send authority to) and optionally a pillar for same-topic fallback linking. Calling getRelatedLinks on the money page returns every declaration that named it: the reverse edge, derived once, not declared twice. Calling it on the content page returns its own supports, topped up with pillar siblings if under opts.limit.

opts.limit caps linksTo only. opts.linkedFromLimit caps linkedFrom, independently: the two are unrelated lists (a page's own outbound picks versus every page that named it), so one option was never meant to cap both. Omit either for no cap.

trail.ts

getBreadcrumbTrail(labels: TrailLabel[], path: string): TrailEntry[]

Walks path segments, picking up the registered label at each level that has one. Feed the result to @domandigital/graph's buildBreadcrumbs. That's the one touchpoint between the two packages (see PRINCIPLES.md in dd-graph).

validate.ts

validateCoverage(input: ValidateCoverageInput): CoverageIssue[]

Pure: the caller does the filesystem enumeration (a per-router concern) and passes routesOnDisk in. Flags: a route on disk with no policy entry (the six-missing-case-studies bug), a sitemap-eligible policy entry with no corresponding page (an orphan), and, if moneyRoutes/targets are supplied, a money route with no keyword target declared.

Deliberately not in v0.1

The metadata builder (title/description/OG/hreflang generation). Doman Digital's lib/metadata.ts is mature and shaped around its own CMS overlay; RMP Electrical has its own version tuned to location pages. Extracting a shared metadata builder now would mean designing for two consumers whose needs haven't converged yet. Revisit once a second real consumer is on this package and the shape is clearer.