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

ssrwire

v0.4.1

Published

Inspect streamed SSR HTML, SEO and social metadata timing, and crawler-specific delivery from the command line.

Readme

SSRWire

CI npm version MIT License

Inspect streamed SSR HTML, SEO and social metadata timing, and crawler-specific delivery from the command line.

SSRWire makes a real HTTP request for each selected user-agent profile, reads the response incrementally, and records when important SEO and social-preview signals become observable to its parser. It reports their elapsed time, observed byte position, and document location without launching a browser or executing JavaScript.

npx ssrwire https://example.com/product

Why this exists

Modern SSR output is not always one complete HTML document delivered at once:

  • metadata may arrive later than the first meaningful content;
  • a framework may intentionally stream metadata into <body> for a capable crawler while blocking for an HTML-limited bot;
  • browser, search-crawler, and social-crawler user agents may receive different titles, canonicals, social-preview metadata, robots directives, redirects, or statuses;
  • the same URL and user agent may receive inconsistent SSR output between requests;
  • an interrupted or oversized stream may never deliver the expected elements;
  • a working hydrated page can hide thin or incomplete source HTML.

SSRWire turns those behaviors into small, repeatable HTTP-level checks. It is not a browser, a JavaScript renderer, a packet capture, or a replacement for a site crawler.

Requirements

  • Node.js 22.12.0 or newer
  • No browser installation

Run it without installing:

npx ssrwire https://example.com/
npx ssrwire https://example.com/ https://example.com/pricing/

Or add it to a project:

npm install --save-dev ssrwire
npx ssrwire init
npx ssrwire check

Quick start

Create ssrwire.config.yml:

npx ssrwire init

Then run all configured targets:

npx ssrwire check
npx ssrwire check --format json --output reports/ssrwire.json
npx ssrwire check --format sarif --output reports/ssrwire.sarif

One-off checks need no config:

npx ssrwire check \
  https://example.com/ \
  https://example.com/pricing/ \
  --agent browser \
  --agent googlebot \
  --fail-on warning

The root command and check are equivalent, so npx ssrwire URL is the short form of npx ssrwire check URL.

Compare deployments

SSRWire can compare two JSON audits without making more network requests. Give the same logical target a stable id in each environment so reports can match across different origins:

# production.yml
targets:
  - id: home
    url: https://www.example.com/
  - id: pricing
    url: https://www.example.com/pricing/
# preview.yml
targets:
  - id: home
    url: https://preview.example.net/
  - id: pricing
    url: https://preview.example.net/pricing/

Capture and compare the already-redacted reports:

npx ssrwire check --config production.yml --format json --output production.json
npx ssrwire check --config preview.yml --format json --output preview.json

npx ssrwire compare production.json preview.json
npx ssrwire compare production.json preview.json \
  --format html \
  --output ssrwire-diff.html

The comparison classifies candidate-only warning/error findings and newly incomplete probes as regressions, resolved findings and material timing improvements as fixed, and response or metadata differences as neutral changes. A metadata change becomes a regression when it causes a candidate policy finding, such as required metadata disappearing or head-only crawler metadata moving into the body.

Timing regressions use per-agent medians and require both an absolute increase over 250 ms and a relative increase over 25% by default. Both floors are configurable on compare; small timing differences remain visible in the HTML waterfall without making CI noisy. --fail-on regression is the comparison default, while --fail-on never always exits successfully after valid reports are compared.

The HTML report is one self-contained, script-free file with synchronized baseline and candidate milestones. It never embeds raw response HTML. SSRWire does not create or update baseline files automatically.

Reports without target IDs match by exact target URL. IDs must be unique within one report, so an ID mismatch is shown as one removed and one added target instead of being guessed. Comparison requires the explicit schemaVersion: 1 audit contract emitted by SSRWire 0.4.1.

What it observes

For each target, agent, and configured sample, SSRWire captures:

  • response status, final URL, redirect chain, and an allowlisted response-header snapshot;
  • time to response headers, first response-body bytes, and completed body;
  • total bytes delivered to the stream parser and a body fingerprint;
  • title, meta description, canonical, meta robots, Open Graph, Twitter Card, H1, first main-content text, and JSON-LD blocks;
  • elapsed arrival time, observed byte position, and head/body location for each signal;
  • clean completion, timeout, network failure, invalid response, or configured byte-limit termination.

It then checks:

| Contract | Default finding | |---|---| | Unexpected status or configured final URL | Error | | Missing or explicit non-HTML Content-Type | Error / incomplete run | | Missing title | Error | | Missing description, canonical, H1, or main text | Warning | | Duplicate or conflicting title, description, canonical, or robots values | Warning | | Missing an enabled Open Graph or Twitter Card contract | Warning | | Invalid social metadata URL or conflicting scalar social metadata | Warning | | Invalid JSON-LD | Warning | | JSON-LD block/count exceeds the bounded analysis budget | Warning | | Critical or enabled social metadata in <body> for a profile that requires head metadata | Error | | Status, final URL, title, canonical, robots, or enabled social metadata drift between profiles | Warning | | Completion, status, final URL, or redirect-chain drift between samples | Warning | | Metadata value or document-location drift between complete samples | Warning | | Exact body fingerprint drift without metadata drift | Information | | First byte or required-signal arrival over a configured limit | Warning | | Timeout, truncation, network error, or another incomplete probe | Error / incomplete run |

Body-located metadata is not inherently an error. SSRWire only fails it for an agent whose profile declares requiresHeadMetadata: true. This matters for frameworks such as Next.js that can deliberately stream metadata differently for JavaScript-capable and HTML-limited bots.

Agent profiles

The default run uses:

| Key | Intended view | Requires metadata in <head> | |---|---|---:| | browser | Normal browser user agent | No | | googlebot | Googlebot user agent | No | | bingbot | Bingbot user agent | Yes | | twitterbot | X/Twitter link-preview user agent | Yes |

facebook is also built in and can be selected explicitly. A repeated CLI --agent list replaces the configured/default list for that run:

npx ssrwire https://example.com/ --agent googlebot --agent facebook

These profiles send user-agent strings; they do not prove how a real crawler will fetch, render, index, or cache a page. SSRWire does not perform crawler IP or reverse-DNS verification. requiresHeadMetadata is SSRWire's default audit policy for a profile, not a guarantee about that crawler's current parser or rendering capabilities; use a custom profile when your contract differs.

Generic robots directives apply to every profile. Matching googlebot and bingbot directives are combined with the generic directives, with restrictive rules winning. SSRWire keeps audiences separate for duplicate checks and warns when a crawler-specific permissive rule cannot relax a generic restriction.

Custom profiles are supported in configuration:

agents:
  - browser
  - key: internal-preview-bot
    label: Internal preview bot
    userAgent: ExamplePreviewBot/1.0
    requiresHeadMetadata: true

Social preview metadata

Every probe captures these bounded, ordered metadata signals when they arrive before completion or termination:

  • Open Graph: og:title, og:type, og:url, og:image, and og:description;
  • Twitter Card: twitter:card, twitter:title, twitter:description, and twitter:image.

SSRWire accepts either the conventional property attribute or a name attribute for those keys. Each captured value carries the same arrival time, observed byte position, and document location as the existing metadata signals. The terminal report shows a compact readiness table whenever social metadata is observed or required, while JSON retains every captured value.

Social policy is opt-in per target. openGraph: true requires the four basic Open Graph protocol properties: title, type, URL, and image. The twitterCard: true option requires twitter:card plus a usable title, description, and image; SSRWire prefers the corresponding twitter:* value and falls back to og:title, og:description, or og:image. These are explicit SSRWire audit contracts, not a claim that a social platform will render a particular preview.

Enabled contracts also participate in critical-signal timing, head/body policy, cross-agent drift, and repeated-sample stability checks. URL-valued fields must be absolute HTTP or HTTPS URLs. Open Graph permits multiple images, so SSRWire retains them in order without treating the array as a scalar conflict; the first non-empty image satisfies readiness.

When both options are false, SSRWire still records and reports raw social signals but emits no social-policy or social-drift findings. It does not fetch images, verify dimensions or media types, execute JavaScript, or simulate a platform's rendered preview.

Configuration

SSRWire automatically looks for ssrwire.config.yml, ssrwire.config.yaml, or ssrwire.config.json. An explicit --config path takes precedence.

targets:
  - id: home
    url: https://example.com/
    expectedStatus: 200
    expectedFinalUrl: https://example.com/
    require:
      title: true
      description: true
      canonical: true
      h1: true
      mainText: true
      openGraph: true
      twitterCard: true
    maxFirstByteMs: 1200
    maxCriticalMs: 2500

  - id: not-found
    url: https://example.com/not-found/
    expectedStatus: [404]
    require:
      title: true
      description: false
      canonical: false
      h1: true
      mainText: true
      openGraph: false
      twitterCard: false

agents:
  - browser
  - googlebot
  - bingbot
  - twitterbot

headers:
  x-preview-token: "${PREVIEW_TOKEN}"

timeoutMs: 15000
maxBytes: 10485760
maxRedirects: 10
repeat: 1

A target can also be a plain URL string when defaults are sufficient:

targets:
  - https://example.com/
  - https://example.com/pricing/

Use the object form with a stable id when reports from different origins will be compared. IDs are 1–64 ASCII letters, digits, dots, underscores, or hyphens, and must start with a letter or digit.

Defaults:

  • expected status: 200;
  • title, description, canonical, H1, and main text: required;
  • Open Graph and Twitter Card contracts: disabled;
  • agents: browser, googlebot, bingbot, and twitterbot;
  • timeout: 15 seconds per probe;
  • response limit: 10 MiB;
  • redirect limit: 10;
  • samples per target and agent: 1, with an allowed range of 1–10.

Unknown configuration keys are rejected. URLs must be absolute HTTP or HTTPS URLs and cannot contain embedded credentials.

Protected previews

Header values can interpolate environment variables. Export them in the current shell or provide them through the CI secret store; SSRWire does not load .env files itself.

export PREVIEW_TOKEN="..."
npx ssrwire check

For an ephemeral override:

npx ssrwire https://preview.example.com/ \
  --header "x-preview-token: $PREVIEW_TOKEN"

CLI headers override a configured header with the same case-insensitive name. SSRWire rejects Accept-Encoding, Host, Content-Length, Connection, Transfer-Encoding, and User-Agent overrides. Custom headers are sent to the initial origin and same-origin redirects only; the first cross-origin redirect removes them for the rest of that probe. Request-header configuration is not serialized. The CLI and runAudit() redact known values and common URL/base64 encodings from the complete probe, including parsed HTML signals, response-header snapshots, redirects, and errors. A target can apply an unknown transformation before reflecting a secret, so review reports from untrusted targets before sharing them. Direct low-level probeUrl() callers should apply redactProbe() before persisting results.

The final response must declare text/html or application/xhtml+xml as its Content-Type; parameters such as charset=utf-8 are allowed. SSRWire does not sniff headerless, JSON, text, or binary responses for HTML-looking fragments.

To keep hostile or accidentally huge pages bounded, SSRWire retains at most 256 signals of each repeated metadata kind, including each supported social property, analyzes at most 64 JSON-LD blocks, and captures at most 1,048,576 characters from one JSON-LD block. Exceeding a JSON-LD analysis budget produces a dedicated warning rather than being mislabeled as invalid JSON. The configured response-byte limit remains the outer bound.

Timing interpretation

SSRWire reports when its own process observed bytes and parsed elements. That is useful for regression testing, but it is not a record of the application's original flush() calls or network packets.

CDNs, reverse proxies, compression, TLS, HTTP implementations, and local buffering can split or coalesce data before the process receives it. Agent profiles are requested separately, and network/cache variance can affect their times. Use timing thresholds with margin and compare repeated CI runs from a stable location. Treat byte positions as parser-observation offsets, not transfer-size or packet-boundary evidence. They count bytes delivered by the Fetch implementation to SSRWire; those bytes are post-content-decoding when a server ignores SSRWire's Accept-Encoding: identity request.

Repeated sampling

Use repeated sampling when one successful request does not prove that SSR output is stable:

npx ssrwire check --repeat 3

repeat is the total number of samples, not a retry count. SSRWire retains failures instead of replacing them with a later success. Samples for one target-agent pair run sequentially; different target-agent pairs may still run concurrently. SSRWire does not add delays, cache-busting parameters, or special cache headers.

The request count is targets × agents × repeat, plus redirect hops. Configured same-origin headers are sent for every sample and retain the existing cross-origin stripping and report-redaction behavior.

For repeated runs, terminal reports include individual sample numbers and a per-agent timing table. JSON retains every probe and adds per-agent stability summaries. The summaries report sample count, minimum, median, nearest-rank p95, maximum, and spread for available header, first-byte, required-signal, and completion timings. With small sample counts, nearest-rank p95 will often equal the maximum.

Timing spread alone is evidence, not a failure. Network and cache variation can change timings without changing the response contract. SSRWire warns when HTTP response evidence or streamed metadata changes across samples. Enabled social contracts are included in metadata stability; observed social tags remain evidence-only when their contracts are disabled. Exact body-hash variation by itself is informational because timestamps, nonces, and other legitimate dynamic values commonly change source HTML.

CLI reference

ssrwire [urls...] [options]
ssrwire check [urls...] [options]
ssrwire compare <baseline.json> <candidate.json> [options]
ssrwire init [path] [--force]

Check options:

| Option | Purpose | |---|---| | -c, --config <path> | Use a specific YAML or JSON config | | -a, --agent <name> | Select a built-in agent; repeatable | | -H, --header "Name: value" | Add/override a request header; repeatable | | --timeout <ms> | Override request timeout | | --max-bytes <bytes> | Override response-body limit | | --max-redirects <count> | Override redirect limit | | --repeat <count> | Run 1–10 sequential samples per URL and agent | | -f, --format <format> | terminal, json, or sarif | | -o, --output <path> | Write the report to a file | | --fail-on <level> | error, warning, or never | | --no-color | Disable terminal color |

Config-file targets and CLI URLs are combined, with exact duplicate URLs removed.

Comparison options:

| Option | Purpose | |---|---| | -f, --format <format> | terminal, json, or self-contained html | | -o, --output <path> | Write the comparison to a file | | --fail-on <level> | regression or never | | --timing-regression-ms <ms> | Absolute median slowdown floor; default 250 | | --timing-regression-percent <percent> | Relative median slowdown floor; default 25 | | --no-color | Disable terminal color |

Reports and exit codes

  • terminal: compact sample, social-readiness, aggregate-timing, and finding tables for local use.
  • json: structured machine-readable evidence, including every probe and timing signal plus repeated-run stability summaries.
  • sarif: findings suitable for GitHub Code Scanning and other SARIF 2.1.0 consumers.
  • comparison html: a script-free deployment summary and per-agent wire waterfall suitable for a CI artifact.

Exit codes are stable:

  • 0: the run completed and passed the selected policy;
  • 1: the run completed but crossed the --fail-on threshold;
  • 2: configuration/setup failure or incomplete probe evidence.

For compare, exit 1 means at least one regression was introduced under the default policy, while invalid or unreadable reports use exit 2. Neutral changes and fixed findings do not fail comparison.

check --fail-on never suppresses policy failures, but it never converts an incomplete probe into a pass.

GitHub Actions

Copy examples/github-actions.yml into the site repository and commit examples/ssrwire.config.yml as ssrwire.config.yml. Store preview credentials as repository or environment secrets. The example deliberately withholds those credentials from pull-request runs because the checked-out configuration is controlled by that pull request; credentialed checks run only after trusted code reaches the protected branch or through a manual dispatch. Keep PR targets public and credential-free. If the main audit requires ${PREVIEW_TOKEN}, point the PR step at a separate credential-free configuration or remove the PR trigger.

The workflow always keeps the SARIF file as a downloadable artifact. It also uploads findings to Code Scanning for public repositories. Private/internal repositories can remove the public-only condition after GitHub Code Security is enabled for that repository.

This repository's own CI tests Node.js 22.12.0 and 24, runs the packaged CLI smoke test on macOS and Windows, validates the npm tarball, and builds and executes the Docker image. It contains no automatic npm publishing job; npm releases are made manually from a local interactive terminal.

Docker

The image is a small, browser-free Node.js runtime and runs as the non-root node user:

docker build -t ssrwire .
docker run --rm ssrwire https://example.com/

Run a mounted configuration:

docker run --rm \
  --env PREVIEW_TOKEN \
  --volume "$PWD/ssrwire.config.yml:/work/ssrwire.config.yml:ro" \
  --workdir /work \
  ssrwire check

Programmatic API

The package exports the probe, parser, analysis, and reporter primitives used by the CLI:

import { loadConfig, renderJson, runAudit } from "ssrwire";

const config = await loadConfig({ urls: ["https://example.com/"], repeat: 3 });
const audit = await runAudit(config);
process.stdout.write(renderJson(audit));

Comparisons use the same primitives as the CLI:

import { readFile } from "node:fs/promises";
import {
  compareAudits,
  parseAuditReportText,
  renderComparisonHtml,
} from "ssrwire";

const [baselineJson, candidateJson] = await Promise.all([
  readFile("production.json", "utf8"),
  readFile("preview.json", "utf8"),
]);
const baseline = parseAuditReportText(baselineJson, "baseline audit report");
const candidate = parseAuditReportText(candidateJson, "candidate audit report");
const comparison = compareAudits(baseline, candidate);
const html = renderComparisonHtml(comparison);

The JSON report's top-level version is the SSRWire software version. schemaVersion separately identifies the persisted report contract used by offline comparison.

Scope

SSRWire does not execute JavaScript, inspect a hydrated DOM, render social previews, fetch social images, measure Core Web Vitals, discover URLs, validate indexing, bypass access controls, perform load testing, or emulate a crawler's rendering pipeline. Use RoutePlay for server HTML versus a cold browser versus real client-side navigation. Use RouteLint for route discovery, indexability, and technical SEO policy.

Run SSRWire only against targets you are authorized to inspect. Keep target lists and repeat counts intentionally small. It is a consistency sampler, not a load generator.

Development

npm ci
npm run check

See CONTRIBUTING.md, SECURITY.md, CHANGELOG.md, and PUBLISHING.md.

MIT licensed. Built by Niko M..