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

@glowrank/content

v0.2.0

Published

Framework-agnostic client for the GlowRank public content API — fetch and server-render your GlowRank pages on your own origin.

Readme

@glowrank/content

Framework-agnostic client for the GlowRank public content API. Fetch the pages GlowRank generates for your business and server-render them on your domain, with any Node/JS stack — Express, Fastify, Remix, plain http, custom SSR.

This content is yours. Pages are served from your origin, canonical URLs point at your domain, and everything the API returns (Markdown, HTML, plain text, JSON-LD, metadata) is yours to keep — if you ever stop using GlowRank, the pages you've published stay yours.

Zero runtime dependencies. Node ≥ 18 (uses global fetch).

Install

npm install @glowrank/content

Recommended: render Markdown with your own components

Every page ships a markdown field — the recommended integration format. Render it with your own Markdown renderer and your own components, inside your own layout: you never have to trust our HTML. Your design system owns the look; GlowRank owns the words.

import { createClient } from "@glowrank/content";
import Markdown from "react-markdown"; // or markdown-it, marked, remark, …

const gr = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });

const page = await gr.getPageBySlug("lip-fillers"); // full payload, or null
if (page) {
  // Render `page.markdown` inside YOUR components / layout.
  render(<YourArticleLayout title={page.title}>
    <Markdown>{page.markdown ?? ""}</Markdown>
  </YourArticleLayout>);
}

The source of truth is sanitised HTML; markdown is derived from it server-side (deterministic, stored — no per-request conversion) so it always matches the page body. page.html remains available for the full-page handlers below and for the WordPress/proxy render paths.

Fastest path: full-page HTML handler

If you don't want to bring a Markdown renderer, renderPageHtml returns a complete, ready-to-serve HTML document. The body is server-sanitised (see Trust model).

import { createClient, renderPageHtml } from "@glowrank/content";

const gr = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });

const manifest = await gr.getManifest();        // live pages: id, slug, title, updatedAt
const page = await gr.getPageBySlug("lip-fillers"); // full payload, or null

Express (10 lines)

import express from "express";
import { createClient, renderPageHtml } from "@glowrank/content";

const app = express();
const gr = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });

app.get("/guides/__glowrank-check", (_req, res) => {
  const probe = gr.checkProbe();
  res.set(probe.headers).send(probe.body);
});
app.get("/guides/:slug", async (req, res) => {
  const page = await gr.getPageBySlug(req.params.slug);
  if (!page) return res.status(404).send("Not found");
  res.type("html").send(renderPageHtml(page));
});
app.listen(3000);

Plain http (10 lines)

import http from "node:http";
import { createClient, renderPageHtml } from "@glowrank/content";

const gr = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });

http.createServer(async (req, res) => {
  const slug = (req.url ?? "").replace(/^\/guides\//, "").split("?")[0];
  if (slug === "__glowrank-check") {
    const probe = gr.checkProbe();
    return res.writeHead(200, probe.headers).end(probe.body);
  }
  const page = await gr.getPageBySlug(slug);
  if (!page) return res.writeHead(404).end("Not found");
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(renderPageHtml(page));
}).listen(3000);

The verification probe

When you click Verify in the GlowRank dashboard, GlowRank fetches https://your-site.com<basePath>/__glowrank-check and expects your siteKey echoed in the response body — that proves the SDK is live at the path you configured. client.checkProbe() gives you the exact { body, headers } to respond with (see the examples above).

API

  • createClient({ siteKey, apiBase?, cacheTtlMs?, fetch? })
    • apiBase defaults to https://glowrank.io.
    • cacheTtlMs: in-memory response cache TTL (default 60_000 ms) so per-request usage doesn't hammer the API; pass 0 or false to disable.
    • fetch: custom fetch implementation (used by framework adapters).
  • client.getManifest(){ basePath, publicOrigin, items }. Throws GlowRankApiError (status 404) for an unknown siteKey.
  • client.getPage(id) / client.getPageBySlug(slug) → full page payload (markdown, html, text, title, seoTitle, metaDescription, jsonLd, targetPath, canonicalUrl, updatedAt) or null when not found. markdown is the recommended render target; html is server-sanitised.
  • client.checkProbe(){ body, headers } for the verification endpoint.
  • renderPageHtml(page, { lang?, extraHead? }) → a complete HTML document string. Composable pieces are exported too: renderPageHead, jsonLdScriptTag, escapeHtml.

Errors: 404s return null; network failures and 5xx responses throw a typed GlowRankApiError (status, url — with the siteKey redacted).

Using Next.js? Use @glowrank/next — a one-line route handler with ISR/tag revalidation built on this client.

Trust model

GlowRank content is model-generated, and the model reads inputs we don't fully control (your scraped site copy, review text). So we treat the output as untrusted and harden it before it ever reaches you — twice: once at write time and again at the API boundary.

  • markdown (recommended). No markup to trust at all — render it with your own renderer and components. This is why the field exists: a zero-trust integration path for teams who don't want to inject third-party HTML.
  • html (sanitised). Server-side allowlist sanitisation strips everything that isn't semantic content. What we keep: headings (h1h6), paragraphs, lists, strong/em/b/i, blockquotes, tables, code/pre, hr/br, and a links (http/https only, forced rel="noopener noreferrer"). What we strip: script, style, iframe, object, embed, form, every on* event handler, and javascript:/data: URLs. Images are not emitted today and are not allowlisted.

Both fields are safe to render. Markdown simply gives you a format with no HTML to review at all.

License

MIT