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/next

v0.3.0

Published

Next.js adapter for GlowRank own-site content — headless <GlowRankArticle> native rendering in your own layout, plus a one-line App Router route handler with ISR/tag revalidation, sitemap, and connection verification.

Readme

@glowrank/next

Next.js (App Router) adapter for GlowRank own-site content: render GlowRank pages natively inside your own layout — your own markdown renderer on page.markdown, or the headless <GlowRankArticle> fragment — or mount a one-line route handler that serves complete documents. ISR, tag-based revalidation, a sitemap, and the connection-verification probe built in.

This content is yours — pages render on your origin, in your design, and canonical URLs point at your domain.

Native rendering (recommended)

Render each GlowRank page inside your own layout, with your own styles. No third-party document, no injected styles or fonts — you never serve markup you didn't choose. Two ways to do it; both share the same data flow (createClient → getPageBySlug → render) and the same generateMetadata helper.

Markdown-first (zero-trust)

Every page payload carries a first-class markdown field — a Markdown rendition derived server-side from the sanitised body and stored with it, so it always matches. Render it with the markdown renderer you already use, and there is no third-party HTML to trust at all — GlowRank owns the words; your renderer and design system own every element on the page:

// app/guides/[slug]/page.tsx  (the base path from your GlowRank connection)
import { notFound } from "next/navigation";
import { createClient, glowrankMetadata } from "@glowrank/next";
import Markdown from "react-markdown"; // or your own renderer/components

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

type Props = { params: Promise<{ slug: string }> };

export async function generateMetadata({ params }: Props) {
  const page = await gr.getPageBySlug((await params).slug);
  return page ? glowrankMetadata(page) : {};
}

export default async function Article({ params }: Props) {
  const page = await gr.getPageBySlug((await params).slug);
  if (!page) notFound();
  return (
    <article className="prose">
      <Markdown>{page.markdown ?? ""}</Markdown>
    </article>
  );
}

(If you want the page's JSON-LD too, page.jsonLd is on the payload — jsonLdScriptTag/escapeJsonLd from @glowrank/content build the script tag safely, or let <GlowRankArticle> below handle it.)

<GlowRankArticle> — the convenience alternative

No markdown pipeline in your app? <GlowRankArticle> renders the page's sanitised HTML as a semantic fragment — an <article> with the page body plus the page's JSON-LD script, nothing else. It still inherits whatever your stylesheet already does (so e.g. Tailwind's prose just works):

export default async function Article({ params }: Props) {
  const page = await gr.getPageBySlug((await params).slug);
  if (!page) notFound();
  return <GlowRankArticle page={page} className="prose" />;
}

<GlowRankArticle> is a plain server-component-compatible function (no hooks, no "use client"; import it from @glowrank/next alongside createClient). The body HTML is sanitized twice server-side by GlowRank (at write time and at the API boundary); see Trust model.

Metadata

glowrankMetadata(page) returns a Next Metadata object — title (seoTitle wins), description, and the canonical URL on your origin — for generateMetadata, as in the example above.

Where to mount it: your connection's base path

Each page's canonical URL (and every sitemap entry) is <your origin><basePath>/<slug>, where basePath is the one configured on your GlowRank connection (default /guides). Put the native page at that path, e.g. app/guides/[slug]/page.tsx. Mounted anywhere else (say /articles/[slug]), the page declares a canonical URL it does not serve, and search and answer engines are told to index a different URL.

Native pages do not answer the verification probe or serve the sitemap, and GlowRank does not publish to a connection until the probe at <basePath>/__glowrank-check echoes your siteKey. Add those two routes next to the page; static segments take precedence over [slug], and %5F is how the App Router spells a leading underscore (a plain _folder is private and never routed):

// app/guides/%5F_glowrank-check/route.ts   -> /guides/__glowrank-check
// app/guides/sitemap.xml/route.ts          -> /guides/sitemap.xml
import { createGlowRankHandler } from "@glowrank/next";

export const GET = createGlowRankHandler({ siteKey: process.env.GLOWRANK_SITE_KEY! });

(Or merge the sitemap into your root app/sitemap.ts; see Merging into your own sitemap.)

Why native rendering is the recommended path: it's your site serving your content in your styles — readers (and crawlers) see one coherent origin, not a third-party document embedded in it.

Zero-config alternative: the one-line route handler

Don't want to touch rendering at all? One handler serves complete, server-sanitised HTML documents (unstyled), plus the sitemap and verification probe:

npm install @glowrank/next
// app/guides/[[...slug]]/route.ts  (use the base path from your GlowRank connection)
import { createGlowRankHandler } from "@glowrank/next";

export const GET = createGlowRankHandler({ siteKey: process.env.GLOWRANK_SITE_KEY! });

That's it. The handler now serves, under /guides:

| Path | Response | | --- | --- | | /guides/<slug> | Full HTML document for a published GlowRank page | | /guides/sitemap.xml | Sitemap of all live GlowRank pages | | /guides/__glowrank-check | Verification probe (echoes your siteKey) | | anything else | 404 |

Requires next >= 14 (peer dependency) and Node ≥ 18.

ISR & revalidation

Every upstream fetch is made with next: { revalidate, tags: ['glowrank'] }:

  • Time-based (ISR): pages refresh automatically every revalidate seconds (default 300). Override per handler:

    export const GET = createGlowRankHandler({
      siteKey: process.env.GLOWRANK_SITE_KEY!,
      revalidate: 60, // seconds; or `false` to cache until tag revalidation
    });
  • On-demand: purge all GlowRank content instantly from a Server Action or webhook route:

    import { revalidateTag } from "next/cache";
    revalidateTag("glowrank"); // exported as GLOWRANK_CACHE_TAG

Responses also carry Cache-Control: public, s-maxage=<revalidate>, stale-while-revalidate so CDNs in front of your app behave consistently.

Verification (__glowrank-check)

When you click Verify in the GlowRank dashboard, GlowRank requests https://your-site.com/<basePath>/__glowrank-check and expects your siteKey echoed back — proving the SDK is mounted at the path configured on your connection. The handler answers this automatically (no API call involved), so verification passes as soon as the route above is deployed. If verification fails, check that the route directory matches your connection's base path exactly and that GLOWRANK_SITE_KEY is set in the deployed environment.

Merging into your own sitemap

Prefer one sitemap at the site root? Merge GlowRank entries into app/sitemap.ts instead:

import type { MetadataRoute } from "next";
import { createClient, glowrankSitemapEntries } from "@glowrank/next";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const client = createClient({ siteKey: process.env.GLOWRANK_SITE_KEY! });
  const glowrank = await glowrankSitemapEntries(client);
  return [{ url: "https://example.com" }, ...glowrank];
}

Not on the App Router?

Use @glowrank/content directly — the framework-agnostic core this adapter is built on (Express, Fastify, Remix, plain http, …).

Trust model

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

  • markdown (recommended). No markup to trust — render it with your own components. The zero-trust path.
  • html (sanitised). Allowlist sanitisation keeps only semantic content (headings, paragraphs, lists, emphasis, blockquotes, tables, code/pre, hr/br, http/https a links with forced rel="noopener noreferrer") and strips script/style/iframe/object/embed/form, all on* handlers, and javascript:/data: URLs. This is what createGlowRankHandler serves.

Both are safe to render; Markdown just gives you a format with no HTML to review.

License

MIT