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

sitewright-core

v0.19.0

Published

Shared runtime for Next.js + Payload CMS sites: image backup and versioning, public-form abuse brakes, real cookie consent, generated legal pages, SEO/GEO helpers, and an audit CLI that gates deploys.

Readme

sitewright-core

The shared runtime behind Sitewright websites: the parts every content-driven site needs, that are tedious to write once and worse to write again.

It is built for Next.js (App Router) + Payload CMS sites, and it exists because the same handful of things kept going wrong on real, deployed sites: a favicon that changed address on every deploy, a public form used to mail strangers, a cookie banner that was pure theatre, a cropped image with no way back, and a grey that failed contrast in the footer of every page.

The strings people read are in Spanish. Field labels, the consent banner, the generated legal pages and form errors are written for Spanish-speaking clients. The API, the types and the docs are in English.

npm install sitewright-core

Peer dependencies: next ≥ 16, payload ≥ 3.88, react ≥ 19, plus @payloadcms/ui and @vercel/blob if you use the panel components or the image backup.

What you get

sitewright-core — logic, no dependencies on your CMS

import { splitEvents, formatEventDate, parseEmbed, buttonColors } from 'sitewright-core'

// Something stops being upcoming when it *ends*, not when it starts — which is why a
// party happening tonight does not vanish from the page at lunchtime.
const { upcoming, past } = splitEvents(dates, Date.now())
formatEventDate(upcoming[0].startsAt, upcoming[0].endsAt) // "viernes, 5 de marzo, 23:30–05:00"

// Whatever the client pasted becomes a player, or nothing at all: SoundCloud, Mixcloud,
// YouTube and Bandcamp, normalised, with the platform link as a fallback.
parseEmbed('https://youtu.be/abc123')?.embedUrl

// Which ink reads on your accent is measurable, so it is measured.
buttonColors({ accent: '#99423b', accentSoft: '#a67800', ink: '#1e2a1a', ground: '#f3f2e1' })
// → { text: '#f3f2e1', hover: 'color-mix(in srgb, #99423b 85%, black)' }

Also here: resolveSiteUrl (one place decides your canonical domain), mediaUrl / mediaAlt / mediaSize for reading upload fields, slugify, alternateTones for alternating section backgrounds over the sections that actually render, escapeHtml, and the rate-limit primitives for public forms (requestIp, recordSubmission, exceedsGlobalCeiling).

sitewright-core/payload — collections and hooks

import { mediaCollection, usersCollection, createRevalidation } from 'sitewright-core/payload'

const { revalidator, globalRevalidator } = createRevalidation(['/', '/llms.txt'])

export default buildConfig({
  collections: [
    usersCollection(),
    mediaCollection({
      altExample: 'El equipo trabajando en el taller',
      revalidation: revalidator(),
    }),
  ],
})

mediaCollection brings the whole image story: every URL carries its modification date, so an edit is visible immediately instead of behind a year of cache; an untouched copy is stored on first upload; and a "back to the original" button undoes any crop through its own endpoint, because doing it in a save hook deadlocks the panel.

sitewright-core/ui — client components

import { ConsentProvider, ConsentedAnalytics } from 'sitewright-core/ui'
import { needsCookieBanner } from 'sitewright-core'

<ConsentProvider
  storageKey="my-site"
  showBanner={needsCookieBanner({ analyticsRequiresConsent: true })}
>
  {children}
  <ConsentedAnalytics requireConsent />
</ConsentProvider>

No third-party iframe is rendered before consent; each player shows a placeholder that asks in place, with a link to the platform for whoever says no. The components ship no styles: they render known class names (consent, embed, embed-blocked, …) so the look belongs to your stylesheet.

Legal pages, generated

import { legalNotice, privacyPolicy, cookiePolicy } from 'sitewright-core'

const profile = { modules: ['media', 'schedule', 'contact'], formFields: ['name', 'email', 'message'] }

legalNotice(settings, siteUrl, profile) // → [{ heading, paragraphs }, …]
privacyPolicy(settings, profile)
cookiePolicy(settings, profile)

Spanish notice, privacy and cookie policies written from the data the client filled in — and leaving out what they did not, because a privacy policy naming the wrong data controller is worse than one naming none. They are a solid draft, not legal advice.

The optional profile says what the site is made of: which modules it has and which fields its contact form asks. With it, the pages describe that site — the dates warning only where dates are announced, third-party players only where something is embedded, the form's real fields, and no form at all where there is none. Without it they keep every disclosure, in words that fit any site.

The audit

The package ships a CLI that checks a running site and exits non-zero when something fails, so it can gate a deploy:

npx sitewright-audit --url https://example.com --css src/app/styles.css --migrations src/migrations

Ten gates: canonical, sitemap and robots naming the same host and that host answering 200 instead of redirecting; the four security headers; JSON-LD that parses and whose @id references resolve; /llms.txt; the legal pages existing and being linked; no third-party iframe served before consent; images with alternative text and responsive sizes; WCAG AA contrast measured over your CSS tokens; page weight; and a database with no dev-mode marks and no pending migrations.

Every one of them is there because it went wrong on a live site.

License

MIT